Salta ai contenuti

Errore Rust E0401: can't use generic parameters from outer item (T della funzione esterna)

E0401 significa che una funzione scritta dentro un’altra funzione ha provato a usare il tipo generico di quella esterna, come T, oppure il Self dell’impl in cui si trova. Sembra che dovrebbe funzionare, perché la funzione interna sta proprio lì dentro quella esterna. Ma in Rust una fn annidata è un item separato che non vede il T esterno. È una sorpresa comune quando metti in ordine una funzione generica spostandoci dentro un helper, e si sistema in un minuto. Fa parte della guida agli errori del compilatore.

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`
| +++

Il compilatore dice: non puoi usare i parametri generici dell’item esterno. Questo è il programma che produce l’errore:

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"]);
}

rustc ti indica tre punti:

  1. Il - sotto T alla riga 1: «type parameter from outer item» (parametro di tipo dell’item esterno). È il T che appartiene a print_all.
  2. Il ^ sotto T alla riga 2: «use of generic parameter from outer item» (uso del parametro generico dell’item esterno). La funzione interna show cerca di usare quel T.
  3. Il ---- sotto show: «generic parameter used in this inner function». È la funzione a cui non è permesso.

La note: dice la regola in una riga: gli item annidati sono indipendenti dal loro genitore. Mettere fn show dentro print_all nasconde solo il suo nome al resto del file. Non dà a show accesso a niente di ciò che appartiene a print_all: né il suo T, né i suoi parametri, né le sue variabili locali.

È come una camera per gli ospiti in una casa condivisa. La camera sta dentro la casa, ma l’ospite non riceve la chiave della tua camera da letto solo perché dorme in fondo al corridoio.

Una funzione generica come print_all<T> è un modello. Rust ne crea una copia separata per ogni T con cui la chiami: una per &str, una per i32 e così via. Una fn annidata invece viene compilata una volta sola, come item a sé, esattamente come se l’avessi scritta in cima al file. Quindi dentro show non c’è un unico T a cui potrebbe riferirsi.

Rust avrebbe potuto far copiare le funzioni annidate insieme al loro genitore, ma così si comporterebbero in modo molto diverso da ogni altra fn. La regola invece è semplice: una fn interna conosce solo ciò che le dai tu, tramite i suoi parametri generici e i suoi argomenti. Se vuoi qualcosa che veda il codice intorno, Rust ha le closure apposta. Vedi Generici in Rust e Closure in Rust.

Soluzione 1: dai alla funzione interna il suo parametro generico

Sezione intitolata “Soluzione 1: dai alla funzione interna il suo parametro generico”

Scegli questa nella maggior parte dei casi. Dichiara <T> anche sulla funzione interna, con gli stessi vincoli che le servono. L’help di rustc suggerisce fn show<T>, ma se aggiungi solo <T> ottieni un nuovo errore, E0277 (`T` doesn't implement `std::fmt::Display`), perché il nuovo T non ha ancora vincoli. Copia anche i vincoli:

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(&["mele", "pere"]);
print_all(&[3, 7]);
}

Output:

- mele
- pere
- 3
- 7
▶ Provalo tu

Il T interno è un parametro nuovo che per caso ha lo stesso nome. Quando print_all chiama show(item), Rust lo riempie con il T esterno. Potresti chiamarlo U per renderlo più chiaro.

Scegli questa quando l’helper è piccolo, o quando gli servono anche le variabili della funzione esterna. Una closure non è un item separato, quindi vede tutto ciò che ha intorno: il T esterno, i parametri della funzione e le sue variabili locali. Qui la closure usa anche label, che una fn annidata non potrebbe mai raggiungere:

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("frutta", &["mele", "pere"]);
}

Output:

frutta: mele
frutta: pere
▶ Provalo tu

Le closure non possono essere generiche per conto loro, ma qui non serve: dentro print_all<T>, &T indica già un tipo concreto per ogni copia della funzione. Vedi Closure in Rust.

Soluzione 3: sposta la funzione fuori (o dentro l’impl)

Sezione intitolata “Soluzione 3: sposta la funzione fuori (o dentro l’impl)”

Scegli questa quando l’helper è utile anche da solo, o quando l’errore riguarda Self. Dentro un metodo, nemmeno una fn annidata può usare Self, perché sta fuori dall’impl nello stesso modo:

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`
|

Il programma che non compila:

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();
}

L’help di rustc (scrivere &Inventory) funziona. Ma di solito la correzione più pulita è spostare count su di un livello, così diventa un metodo a sé. Lì Self e self funzionano come sempre:

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

Output:

2 oggetti
▶ Provalo tu

Per un helper generico come show, «spostarlo fuori» significa una fn show<T: Display>(item: &T) al livello più esterno, che anche altre funzioni possono riusare. Vedi Struct in Rust e Funzioni in Rust.

Una struct o un enum dentro una funzione generica. fn group<T>(items: Vec<T>) con dentro struct Pair { a: T, b: T } dà E0401 su ogni T. Correzione: rendi generico anche il tipo interno, struct Pair<T> { a: T, b: T }, oppure spostalo al livello più esterno.

Una const che dipende da T. Anche const SIZE: usize = std::mem::size_of::<T>(); dentro una funzione generica dà E0401, perché anche una const è un item. Correzione: usa un let: let size = std::mem::size_of::<T>();.

Una funzione interna che usa una variabile locale. È la stessa idea con un valore invece di un tipo, ma con un codice diverso: E0434 (can't capture dynamic environment in a fn item). Correzione: passa il valore come argomento, oppure usa una closure (soluzione 2).

  • E0411: Self usato fuori da qualsiasi impl o trait, per esempio in una fn new() -> Self al livello più esterno.
  • E0277: il vincolo di trait non è soddisfatto, quello che ottieni dopo fn show<T> se non copi T: Display.
  • E0425: non trova un valore o un tipo, per esempio un tipo che non hai mai dichiarato.
  • E0107: numero sbagliato di argomenti generici, un altro sbaglio con i generici.
  • E0434: una fn interna usa una variabile locale della funzione esterna, vedi sopra.
  • Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.

Verifica veloce

1. Dentro fn print_all<T>(...), perché una fn show(item: &T) annidata non può usare T?

2. Quale helper dentro fn print_all<T: Display> può usare il T esterno e le variabili locali esterne?

3. Segui l'help di rustc e scrivi fn show<T>(item: &T), poi stampi item con {}. Che cosa succede?

Punteggio: 0 / 3