Pular para o conteúdo

Erro E0401 do Rust: can't use generic parameters from outer item (fn interna)

O E0401 quer dizer que uma função escrita dentro de outra função tentou usar o tipo genérico da função de fora, como T, ou o Self do impl em que ela está. Parece que deveria funcionar, porque a função interna está ali, dentro da de fora. Mas em Rust uma fn aninhada é um item separado, que não enxerga o T de fora. É uma surpresa comum quando você organiza uma função genérica puxando uma função auxiliar para dentro dela, e a correção leva um minuto. Esta página faz parte do guia de erros do compilador.

error[E0401]: can't use generic parameters from outer item
--> src/main.rs:2:20
|
1 | fn print_all<T: std::fmt::Display>(items: &[T]) {
| - type parameter from outer item
2 | fn show(item: &T) { // ❌ inner `fn` can't see the outer `T`
| ---- ^ use of generic parameter from outer item
| |
| generic parameter used in this inner function
|
= note: nested items are independent from their parent item for everything except for privacy and name resolution
help: try introducing a local generic parameter here
|
2 | fn show<T>(item: &T) { // ❌ inner `fn` can't see the outer `T`
| +++

Em português: “não é possível usar parâmetros genéricos do item de fora”. A ajuda sugere declarar um parâmetro genérico local: fn show<T>.

Este é o programa que gera o erro:

fn print_all<T: std::fmt::Display>(items: &[T]) {
fn show(item: &T) { // ❌ inner `fn` can't see the outer `T`
println!("- {}", item);
}
for item in items {
show(item);
}
}
fn main() {
print_all(&["apples", "pears"]);
}

(O comentário quer dizer “a fn interna não enxerga o T de fora”. Ele fica em inglês porque aparece na saída do compilador.)

O rustc aponta para três lugares:

  1. O - embaixo do T na linha 1: “type parameter from outer item” (parâmetro de tipo do item de fora). Este é o T que pertence a print_all.
  2. O ^ embaixo do T na linha 2: “use of generic parameter from outer item” (uso de um parâmetro genérico do item de fora). A função interna show tenta usar esse T.
  3. O ---- embaixo de show: “generic parameter used in this inner function” (parâmetro genérico usado nesta função interna). Esta é a função que não tem permissão para isso.

A note: resume a regra numa linha: itens aninhados são independentes do item pai, a não ser pela privacidade e pela resolução de nomes. Colocar fn show dentro de print_all só esconde o nome dela do resto do arquivo. Isso não dá a show acesso a nada que pertence a print_all: nem ao T, nem aos parâmetros, nem às variáveis locais.

É como um quarto de hóspedes numa casa. O quarto fica dentro da casa, mas o hóspede não ganha a chave do seu quarto só porque dorme no fim do corredor.

Uma função genérica como print_all<T> é um molde. O Rust gera uma cópia separada para cada T com que você a chama: uma para &str, uma para i32, e assim por diante. Já uma fn aninhada é compilada uma vez só, como um item próprio, exatamente como se você a tivesse escrito no topo do arquivo. Então, dentro de show, não existe um único T a que ela poderia se referir.

O Rust poderia ter feito as funções aninhadas se copiarem junto com a função pai, mas aí elas se comportariam de um jeito muito diferente de todas as outras fn. Em vez disso, a regra é simples: uma fn interna só sabe o que você passa para ela, pelos seus próprios parâmetros genéricos e argumentos. Se você quer algo que enxergue o código em volta, o Rust tem as closures para isso. Veja Genéricos e Closures.

Correção 1: Dê à função interna o seu próprio parâmetro genérico

Seção intitulada “Correção 1: Dê à função interna o seu próprio parâmetro genérico”

Escolha esta na maioria das vezes. Declare <T> também na função interna, com os mesmos bounds (restrições) de que ela precisa. A ajuda do rustc sugere fn show<T>, mas se você acrescentar só o <T>, recebe um erro novo, o E0277 (`T` doesn't implement `std::fmt::Display`, T não implementa Display), porque o novo T ainda não tem bound nenhum. Copie os bounds:

use std::fmt::Display;
fn print_all<T: Display>(items: &[T]) {
fn show<T: Display>(item: &T) {
println!("- {}", item);
}
for item in items {
show(item);
}
}
fn main() {
print_all(&["maçãs", "peras"]);
print_all(&[3, 7]);
}

Saída:

- maçãs
- peras
- 3
- 7
▶ Experimente você mesmo

O T de dentro é um parâmetro novo que por acaso tem o mesmo nome. Quando print_all chama show(item), o Rust preenche esse parâmetro com o T de fora. Você poderia chamá-lo de U para deixar isso mais claro.

Escolha esta quando a função auxiliar é pequena, ou quando ela também precisa das variáveis da função de fora. Uma closure não é um item separado, então ela enxerga tudo em volta: o T de fora, os parâmetros da função e as variáveis locais. Aqui a closure também usa label, que uma fn aninhada nunca conseguiria alcançar:

use std::fmt::Display;
fn print_all<T: Display>(label: &str, items: &[T]) {
let show = |item: &T| println!("{}: {}", label, item);
for item in items {
show(item);
}
}
fn main() {
print_all("fruta", &["maçãs", "peras"]);
}

Saída:

fruta: maçãs
fruta: peras
▶ Experimente você mesmo

As closures não podem ser genéricas por conta própria, mas aqui não precisam ser: dentro de print_all<T>, &T já é um tipo concreto em cada cópia da função. Veja Closures.

Correção 3: Mova a função para fora (ou para dentro do impl)

Seção intitulada “Correção 3: Mova a função para fora (ou para dentro do impl)”

Escolha esta quando a função auxiliar é útil por si só, ou quando o erro é sobre Self. Dentro de um método, uma fn aninhada também não pode usar Self, porque ela fica fora do impl do mesmo jeito:

error[E0401]: can't use `Self` from outer item
--> src/main.rs:7:24
|
5 | impl Inventory {
| ---- `Self` type implicitly declared here, by this `impl`
6 | fn report(&self) {
7 | fn count(inv: &Self) -> usize { // ❌ inner `fn` can't see `Self`
| ----- ^^^^ use of `Self` from outer item
| |
| `Self` used in this inner function
|
= note: nested items are independent from their parent item for everything except for privacy and name resolution
help: refer to the type directly here instead
|
7 - fn count(inv: &Self) -> usize { // ❌ inner `fn` can't see `Self`
7 + fn count(inv: &Inventory) -> usize { // ❌ inner `fn` can't see `Self`
|

Aqui o rustc diz que não dá para usar o Self do item de fora, mostra que o Self foi declarado implicitamente por aquele impl, e sugere escrever o tipo diretamente: &Inventory.

O programa com erro:

struct Inventory {
items: Vec<String>,
}
impl Inventory {
fn report(&self) {
fn count(inv: &Self) -> usize { // ❌ inner `fn` can't see `Self`
inv.items.len()
}
println!("{} items", count(self));
}
}
fn main() {
let inv = Inventory { items: vec!["rope".to_string()] };
inv.report();
}

A ajuda do rustc (escrever &Inventory) funciona. Mas normalmente a correção mais limpa é subir count um nível, para que ela vire um método próprio. Ali, Self e self funcionam normalmente:

struct Inventory {
items: Vec<String>,
}
impl Inventory {
fn count(&self) -> usize {
self.items.len()
}
fn report(&self) {
println!("{} itens", self.count());
}
}
fn main() {
let inv = Inventory {
items: vec!["corda".to_string(), "lanterna".to_string()],
};
inv.report();
}

Saída:

2 itens
▶ Experimente você mesmo

Para uma função auxiliar genérica como show, “mover para fora” quer dizer uma fn show<T: Display>(item: &T) no nível de cima do arquivo, que outras funções também podem reaproveitar. Veja Structs e Funções em Rust.

Uma struct ou um enum dentro de uma função genérica. fn group<T>(items: Vec<T>) com struct Pair { a: T, b: T } lá dentro dá E0401 em cada T. Correção: deixe o tipo interno genérico também, struct Pair<T> { a: T, b: T }, ou mova-o para o nível de cima.

Um const que depende de T. const SIZE: usize = std::mem::size_of::<T>(); dentro de uma função genérica também é E0401, porque um const também é um item. Correção: use um let: let size = std::mem::size_of::<T>();.

Uma função interna que usa uma variável local. É a mesma ideia, só que com um valor em vez de um tipo, e com outro código: E0434 (can't capture dynamic environment in a fn item, não é possível capturar o ambiente dinâmico num item fn). Correção: passe o valor como argumento, ou use uma closure (Correção 2).

  • E0411: Self usado fora de qualquer impl ou trait, por exemplo numa fn new() -> Self no nível de cima.
  • E0277: o trait bound não é satisfeito, que é o que você recebe depois de fn show<T> sem copiar o T: Display.
  • E0425: um valor ou tipo não encontrado, por exemplo um tipo que você nunca declarou.
  • E0107: número errado de argumentos genéricos, outro erro com genéricos.
  • E0434: uma fn interna usa uma variável local da função de fora, mostrado acima.
  • Todos os erros do compilador: a lista completa de páginas de erro.

Teste o que aprendeu

1. Dentro de fn print_all<T>(...), por que uma fn show(item: &T) aninhada não pode usar T?

2. Qual função auxiliar dentro de fn print_all<T: Display> pode usar o T de fora e as variáveis locais de fora?

3. Você segue a ajuda do rustc e escreve fn show<T>(item: &T), e depois imprime item com {}. O que acontece?

Pontuação: 0 / 3