Salta ai contenuti

Errore Rust E0282: type annotations needed (servono annotazioni di tipo)

E0282 significa che Rust ha provato a capire un tipo da solo e non ha trovato abbastanza indizi. La tua logica non ha niente che non va. Il compilatore ha solo bisogno che tu gli dica un tipo, di solito con pochi caratteri in più. Questa pagina ti mostra prima la soluzione e poi ti spiega come funziona il modo in cui Rust indovina i tipi (l’inferenza di tipo), così saprai quando dargli una mano. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.

error[E0282]: type annotations needed for `Vec<_>`
--> src/main.rs:2:9
|
2 | let mut scores = Vec::new(); // a Vec of... what?
| ^^^^^^^^^^ ---------- type must be known at this point
|
help: consider giving `scores` an explicit type, where the type for type parameter `T` is specified
|
2 | let mut scores: Vec<T> = Vec::new(); // a Vec of... what?
| ++++++++

Il compilatore dice: «servono annotazioni di tipo per Vec<_>». Questo è il programma che lo produce (il commento chiede: un Vec di… cosa?):

fn main() {
let mut scores = Vec::new(); // a Vec of... what?
println!("Scores recorded: {}", scores.len());
}

Le parole «type annotations needed» compaiono anche con altri tre codici. Significano la stessa cosa, e le soluzioni di questa pagina funzionano per tutti:

  • E0283: .collect() o .sum() senza un tipo di destinazione. rustc aggiunge una nota come the type must implement `FromIterator<&str>` o `Sum<&i32>`, perché il risultato potrebbe essere di molti tipi.
  • E0284: "42".parse().unwrap() senza un tipo di destinazione. La nota dice cannot satisfy `<_ as FromStr>::Err == _`, che è un modo spaventoso per dire «convertire in cosa?».
  • E0790: let settings = Default::default();. Il titolo dice cannot call associated function on trait without specifying the corresponding `impl` type: Rust non sa di chi è il valore predefinito che vuoi.

Il codice che ottieni dipende dalla riga esatta, e può cambiare se aggiungi altro codice. Per esempio, let age = "42".parse().unwrap(); dà E0284, ma se la riga dopo chiama age.pow(2), diventa E0282. Non preoccuparti del numero; guarda la variabile che indica rustc.

Guarda i segni sotto la riga 2:

  1. I ^^^^^^^^^^ sotto mut scores: questa è la variabile di cui Rust non è riuscito a capire il tipo.
  2. I ---------- sotto Vec::new(): «type must be known at this point» (il tipo deve essere noto a questo punto). Vec::new() crea una lista vuota, ma una lista vuota di cosa? Numeri? Nomi? Rust deve saperlo per riservare il giusto tipo di spazio.
  3. L’help suggerisce let mut scores: Vec<T>. Qui T è solo un segnaposto. Lo sostituisci con un tipo vero, come Vec<u32>.

Pensa a un pacco all’ufficio postale con il campo «contenuto» lasciato in bianco. L’impiegato non può spedirlo finché qualcuno non ci scrive qualcosa. Puoi scriverlo tu, oppure mettere qualcosa nel pacco così l’impiegato lo vede.

Ogni valore in Rust ha un solo tipo preciso, deciso in fase di compilazione. Quasi sempre non lo scrivi, perché Rust legge il tuo codice e lo ricava. let count = 5; è un i32. let name = String::from("Ferris"); è una String. Questa è l’inferenza di tipo, e la lezione Variabili te ne mostra di più.

L’inferenza usa indizi da tutta la funzione, non da una sola riga. Se più avanti chiami scores.push(90), è un indizio: il Vec contiene numeri. Se passi scores a una funzione che prende &[u32], è un indizio ancora migliore. E0282 compare quando non c’è nessun indizio, o quando Rust ha bisogno del tipo prima di arrivare all’indizio (per esempio, per cercare un metodo).

Alcune funzioni sono generiche nel tipo di ritorno: parse, collect, sum, Default::default, Vec::new, None. Possono produrre tipi molto diversi, quindi Rust non può sceglierne uno per te. Non tira a indovinare, perché un tentativo sbagliato cambierebbe in silenzio il comportamento del programma. Vedi Generici per capire come una funzione possa restituire tanti tipi diversi.

Sceglila nella maggior parte dei casi. Aggiungi : Tipo dopo il nome della variabile. È breve, e in più documenta il codice per chi lo leggerà dopo:

fn main() {
let scores: Vec<u32> = Vec::new();
let age: u32 = "42".parse().unwrap();
let words: Vec<&str> = "rosso verde blu".split(' ').collect();
let winner: Option<String> = None;
println!("Punteggi registrati: {}", scores.len());
println!("L'anno prossimo avrai {} anni", age + 1);
println!("{:?}", words);
println!("{:?}", winner);
}

Output:

Punteggi registrati: 0
L'anno prossimo avrai 43 anni
["rosso", "verde", "blu"]
None
▶ Provalo tu

Non devi sempre scrivere il tipo completo. let words: Vec<_> = ...collect(); significa «un Vec, e cosa c’è dentro decidilo tu». Il _ chiede a Rust di completare quella parte. Trovi di più su come scrivere i tipi in Tipi di dati.

Scegli questa quando non c’è nessuna variabile da annotare, per esempio a metà di una catena di chiamate di iteratori. Metti il tipo direttamente sulla chiamata di funzione con ::<Tipo>. Lo chiamano «turbofish» perché ::<> somiglia un po’ a un pesce:

fn main() {
let input = "4 15 7";
let numbers = input
.split(' ')
.map(|s| s.parse::<u32>().unwrap())
.collect::<Vec<_>>();
let total = numbers.iter().sum::<u32>();
println!("{:?}", numbers);
println!("Totale: {total}");
}

Output:

[4, 15, 7]
Totale: 26
▶ Provalo tu

I tre turbofish più comuni sono parse::<i32>(), collect::<Vec<_>>() e sum::<i32>(). La Soluzione 1 e la Soluzione 2 sono corrette allo stesso modo; let total: u32 = numbers.iter().sum(); fa lo stesso lavoro. Scegli quella che si legge meglio.

Soluzione 3: Usa il valore, così Rust può dedurre il tipo

Sezione intitolata “Soluzione 3: Usa il valore, così Rust può dedurre il tipo”

Scegli questa quando l’errore compare perché il tuo codice non è ancora finito. Spesso crei un Vec vuoto, e il codice che lo riempie arriva dopo. Appena usi il valore, Rust ha il suo indizio e l’errore sparisce, senza bisogno di annotazioni:

fn print_team(names: &[String]) {
for name in names {
println!("- {name}");
}
}
fn main() {
let mut team = Vec::new(); // tipo sconosciuto... per ora
team.push(String::from("Ferris")); // ora Rust lo sa: Vec<String>
team.push(String::from("Corro"));
print_team(&team);
}

Output:

- Ferris
- Corro
▶ Provalo tu

L’ordine conta un po’. L’indizio deve arrivare prima che Rust abbia bisogno del tipo per chiamare un metodo. Una closure come let shout = |s| s.to_uppercase(); fallisce anche se chiami shout("hi") nella riga successiva, perché Rust deve sapere cos’è s per trovare .to_uppercase(). Lì scrivi il tipo: |s: &str| s.to_uppercase(). Vedi Closure.

Soluzione 4: Indica il tipo per None e Default::default()

Sezione intitolata “Soluzione 4: Indica il tipo per None e Default::default()”

Scegli questa per i valori che non portano nessun indizio. None non dice cosa conterrebbe, e Default::default() non dice di chi è il valore predefinito che vuoi. Scrivi il tipo dentro l’espressione:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
muted: bool,
}
fn main() {
let fastest_lap = None::<f64>; // invece di: None
let settings = Settings::default(); // invece di: Default::default()
let backup: Settings = Default::default(); // funziona anche un'annotazione
println!("{:?}", fastest_lap);
println!("{:?}", settings);
println!("Volume di riserva: {}", backup.volume);
}

Output:

None
Settings { volume: 0, muted: false }
Volume di riserva: 0
▶ Provalo tu

Settings::default() è la scelta più chiara: si legge come «le Settings predefinite». None::<f64> è comodo, ma let fastest_lap: Option<f64> = None; (Soluzione 1) è più comune. Vedi Option.

Convertire testo in un numero. let guess = line.trim().parse().unwrap(); dà E0284 (o E0282 se subito dopo chiami un metodo su guess). parse può produrre un i32, un u8, un f64 e molti altri. Soluzione: let guess: i32 = ... oppure .parse::<i32>(). Confrontare più avanti guess con una variabile i32 qui non basta (passarlo a una funzione che prende un i32 invece sì), quindi la soluzione più semplice è indicare il tipo proprio nella chiamata. E0284 spiega questo caso nel dettaglio.

.collect(). Può costruire un Vec, una String, un HashSet, una HashMap… quindi ha sempre bisogno di sapere la destinazione. Soluzione: let words: Vec<_> = ...collect(); oppure .collect::<Vec<_>>(). Vedi Altri metodi comuni.

.sum() e .product(). nums.iter().sum() dà E0283 con the type must implement `Sum<&i32>`. Sommare degli i32 potrebbe dare un i32, ma anche altri tipi numerici possono essere delle somme. Soluzione: let total: i32 = nums.iter().sum(); oppure .sum::<i32>().

Una collezione vuota nel costruttore di una struct. let cache = HashMap::new(); in una funzione new() che non hai ancora finito di scrivere. Soluzione: scrivi una volta il tipo completo (HashMap<String, u32>), oppure restituiscila in una struct il cui campo ha già un tipo dichiarato: è un indizio che Rust può usare.

  • E0308: mismatched types. Succede quando la tua annotazione e il valore non sono d’accordo, come in let age: u32 = "42";.
  • E0277: trait bound not satisfied. Hai dato un tipo, ma non si può costruire in quel modo, come let n: Vec<i32> = "1 2".split(' ').collect(); (quello è un Vec<&str>; prima converti ogni elemento).
  • E0599: no method found, per esempio quando annoti let n: u32 e poi ci chiami sopra un metodo di String come .trim().
  • E0004: non-exhaustive patterns, un altro errore in cui rustc scrive la soluzione per te.
  • Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.

Verifica veloce

1. Quale riga risolve E0282 su let ids = Vec::new(); se il Vec deve contenere valori u64?

2. Perché let n = "7".parse().unwrap(); ha bisogno di un tipo, anche se il testo è chiaramente un numero?

3. Cosa significa il _ in .collect::<Vec<_>>()?

Punteggio: 0 / 3