Errore Rust E0283: type annotations needed per sum, collect e into
E0283 significa che Rust sa cosa deve saper fare il tuo valore, ma ci sono molti tipi che sanno farlo, e Rust non ne sceglie uno al posto tuo. Di solito lo incontri subito dopo .sum(), .collect() o .into(). È uno degli errori più facili da risolvere: dici il tipo che vuoi, e hai finito. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0283]: type annotations needed --> src/main.rs:3:9 |3 | let total = prices.iter().sum(); | ^^^^^ --- type must be known at this point | = note: the type must implement `Sum<&i32>` = help: the following types implement trait `Sum<A>`: `Duration` implements `Sum<&'a Duration>` `Duration` implements `Sum` `Option<T>` implements `Sum<Option<U>>` `Result<T, E>` implements `Sum<Result<U, E>>` `Saturating<u128>` implements `Sum<&'a Saturating<u128>>` `Saturating<u128>` implements `Sum` `Saturating<u16>` implements `Sum<&'a Saturating<u16>>` `Saturating<u16>` implements `Sum` and 88 othersnote: required by a bound in `std::iter::Iterator::sum`help: consider giving `total` an explicit type |3 | let total: /* Type */ = prices.iter().sum(); | ++++++++++++Il compilatore ti dice: «servono annotazioni di tipo». Il tipo deve implementare Sum<&i32>, ti elenca alcuni dei tipi possibili e ti suggerisce di dare a total un tipo esplicito.
Ecco il programma che lo provoca:
fn main() { let prices = vec![250, 120, 99]; // prices in cents let total = prices.iter().sum(); println!("Total: {total} cents");}(Il commento dice: prezzi in centesimi.)
.collect() e .into() danno lo stesso errore con una nota diversa. Per let words = text.split(' ').collect(); la nota è the type must implement `FromIterator<&str>`. Per let name = "Ferris".into(); è the type must implement `From<&str>`.
Cosa significa
Sezione intitolata “Cosa significa”Leggi il messaggio dall’alto in basso:
^^^^^sottototal: è la variabile di cui Rust non è riuscito a decidere il tipo.---sottosum: «type must be known at this point» (il tipo deve essere noto a questo punto).sumpuò restituire molti tipi diversi, quindi Rust ha bisogno della risposta qui.- La nota:
the type must implement `Sum<&i32>`. È la regola che Rust ha trovato: «totaldeve essere un tipo che si può ottenere sommando valori&i32». - L’elenco dell’help: i tipi che sanno essere «una somma di qualcosa». Sono tanti: tutti i tipi numerici,
Duration, persinoOptioneResult. Rust ne mostra otto e dice «and 88 others» (e altri 88). È questo che si intende con multiple impls satisfy this bound: più di un tipo ha unimplche rispetta la regola.
Immagina di chiedere al bar «qualcosa da bere». È una regola vera (dev’essere bevibile), ma vanno bene tè, succo e acqua. Il cameriere non può portarti niente finché non scegli. E0283 è Rust con il vassoio in mano che aspetta la tua risposta.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”sum, collect e into sono generici nel loro tipo di ritorno. È il tipo che chiedi tu a decidere cosa fa la funzione. Il trait (Sum, FromIterator, From) è solo la regola. Quindi, quando niente dice a Rust il tipo, c’è una regola ma non un’unica risposta.
E la risposta conta. Raccogliere le stesse parole in un Vec<&str> tiene i doppioni, ma raccoglierle in un HashSet<&str> li toglie. Sommare in un u8 va in overflow a 255, mentre un u64 no. Se Rust scegliesse un tipo per te, una piccola modifica da un’altra parte potrebbe cambiare in silenzio il comportamento del programma. Rust preferisce chiedere una volta.
Il modo in cui Rust deduce i tipi da solo (l’inferenza di tipo) è spiegato nella pagina di E0282, quindi qui non lo ripetiamo. Per i trait dietro questi metodi, vedi Trait e Generici.
E0282, E0283, E0284: un problema, tre codici
Sezione intitolata “E0282, E0283, E0284: un problema, tre codici”Tutti e tre dicono «type annotations needed», e per te vogliono dire la stessa cosa: scrivi il tipo. Il codice ti dice solo cosa stava facendo Rust quando si è bloccato:
- E0282: Rust non ha nessun indizio sul tipo, oppure gli serviva il tipo prima di arrivare all’indizio (per esempio, per cercare un metodo come
.len()). - E0283 (questa pagina): Rust ha un indizio sotto forma di regola di un trait (
must implement `Sum<&i32>`), ma molti tipi rispettano quella regola. - E0284: la regola riguarda un tipo che appartiene al tipo sconosciuto, come il tipo di errore di
parse(cannot satisfy `<_ as FromStr>::Err == _`). Rust non può controllarla finché non conosce il tipo principale.
Il codice può persino cambiare mentre scrivi. let words = text.split(' ').collect(); seguito da println!("{:?}", words) è E0283. Cambia la riga dopo in words.len() e diventa E0282, perché ora Rust ha bisogno del tipo per trovare il metodo. Le soluzioni qui sotto valgono per tutti e tre.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: annota la variabile
Sezione intitolata “Soluzione 1: annota la variabile”Scegli questa strada quasi sempre. Aggiungi : Tipo dopo il nome della variabile. È breve, e dice esattamente al prossimo lettore cosa stai costruendo:
use std::collections::HashSet;
fn main() { let prices = vec![250, 120, 99]; let total: u32 = prices.iter().sum();
let tags = "rust,web,rust,cli"; let list: Vec<&str> = tags.split(',').collect(); let unique: HashSet<&str> = tags.split(',').collect();
println!("Totale: {total} centesimi"); println!("{:?}", list); println!("Tag unici: {}", unique.len());}Output:
Totale: 469 centesimi["rust", "web", "rust", "cli"]Tag unici: 3Stesso collect(), due risultati diversi: il tipo che scrivi è davvero l’istruzione. Puoi lasciare a Rust la parte interna con _, come in let list: Vec<_> = ....
Soluzione 2: usa il turbofish su sum e collect
Sezione intitolata “Soluzione 2: usa il turbofish su sum e collect”Scegli questa strada quando non c’è una variabile da annotare: dentro un println!, in mezzo a una catena di iteratori, o come argomento di una funzione. Metti il tipo sulla chiamata con ::<Tipo> (il «turbofish»):
fn main() { let prices = vec![250, 120, 99]; println!("Totale: {} centesimi", prices.iter().sum::<u32>());
let line = "pane,torta,zuppa"; let items = line.split(',').collect::<Vec<_>>(); let initials = line.split(',').map(|s| &s[..1]).collect::<String>();
println!("{:?}", items); println!("Iniziali: {initials}");}Output:
Totale: 469 centesimi["pane", "torta", "zuppa"]Iniziali: ptz.product::<u64>() funziona allo stesso modo. Le Soluzioni 1 e 2 sono corrette allo stesso modo, quindi scegli quella che si legge meglio.
Soluzione 3: per .into(), nomina il tipo di destinazione (il turbofish non funziona)
Sezione intitolata “Soluzione 3: per .into(), nomina il tipo di destinazione (il turbofish non funziona)”Scegli questa strada per .into() e .try_into(). Viene la tentazione di scrivere "Ferris".into::<String>(), ma è un altro errore, perché il parametro di tipo appartiene al trait Into, non al metodo:
error[E0107]: method takes 0 generic arguments but 1 generic argument was supplied --> src/main.rs:2:25 |2 | let name = "Ferris".into::<String>(); | ^^^^ expected 0 generic argumentsAnnota invece la variabile, oppure chiama direttamente From sul tipo di destinazione, che si legge bene anche così:
fn main() { let name: String = "Ferris".into(); let city = String::from("Lisbona");
let small: u8 = 200; let big: u64 = small.into(); let also_big = u64::from(small);
let level: i64 = 42; let byte: u8 = level.try_into().unwrap(); let too_big = u8::try_from(300_i64);
println!("{name} vive a {city}"); println!("{big} {also_big} {byte}"); println!("300 sta in un u8? {}", too_big.is_ok());}Output:
Ferris vive a Lisbona200 200 42300 sta in un u8? falseString::from(...) e u64::from(...) dicono il tipo di destinazione proprio all’inizio della riga, quindi molti li preferiscono a .into() quando non c’è nessun altro indizio. Vedi Conversioni.
Soluzione 4: usa il valore dove il suo tipo è fissato
Sezione intitolata “Soluzione 4: usa il valore dove il suo tipo è fissato”Scegli questa strada quando il valore finisce comunque in un posto con un tipo noto, come un parametro di funzione o un campo di struct. Rust legge l’intera funzione, quindi quell’uso successivo basta:
fn print_receipt(total: u32, items: Vec<&str>) { println!("{} articoli, {} centesimi", items.len(), total);}
fn main() { let prices = vec![250, 120, 99]; let total = prices.iter().sum(); // u32, grazie a print_receipt let items = "pane,torta,zuppa".split(',').collect(); // Vec<&str>, stesso motivo print_receipt(total, items);}Output:
3 articoli, 469 centesimiNon tutti gli usi contano. if prices.iter().sum() > 400 dà ancora E0283, perché > può confrontare molte coppie di tipi. Nemmeno passare il valore a un parametro generico come impl Display aiuta, perché molti tipi sono Display. L’uso deve nominare un solo tipo esatto.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”.collect() direttamente dentro println!. println!("{:?}", line.split(',').collect()); dà E0283, e rustc stesso suggerisce la soluzione: .collect::<Vec<_>>().
.try_into().unwrap(). let small = big.try_into().unwrap(); dà E0283 con the type must implement `TryFrom<i64>`. Soluzione: let small: u8 = big.try_into().unwrap(); oppure u8::try_from(big).unwrap().
Default::default() passato a una funzione generica. Con fn show(value: impl Debug), la chiamata show(Default::default()) è E0283: Rust non sa di chi vuoi il valore predefinito. Soluzione: show(Settings::default()) oppure show(u32::default()). Da solo su una riga, let s = Default::default(); dà invece E0790; vedi Trait comuni.
Una tua funzione generica. Se un parametro di tipo compare solo nel tipo di ritorno, come in fn make<T: Default>() -> T, allora let v = make(); è E0283. Soluzione: make::<Settings>() oppure let v: Settings = make();.
Errori correlati
Sezione intitolata “Errori correlati”- E0282: servono annotazioni di tipo, quando Rust non ha nessun indizio sul tipo (o gli serve per chiamare un metodo).
- E0284: servono annotazioni di tipo con
cannot satisfy `<_ as FromStr>::Err == _`, quasi sempre per colpa di.parse(). - E0790:
Default::default()(o un’altra funzione di trait) chiamata senza dire quale tipo. - E0277: hai nominato un tipo, ma non si può costruire in quel modo, come
let n: Vec<i32> = "1 2".split(' ').collect();. - E0107: numero sbagliato di argomenti generici, che è quello che ti dà
.into::<String>(). - Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Cosa ti dice la nota di E0283 the type must implement Sum<&i32>?
2. Quale riga corregge let name = "Ferris".into();?
3. Perché raccogliere le stesse parole dà risultati diversi in Vec<&str> e in HashSet<&str>?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0283 — Rust error codes index (in inglese): la spiegazione ufficiale, con l’esempio classico: i
chars()al contrario devono diventare unVec<char>o unaString? - Processing a Series of Items with Iterators — The Rust Book (in inglese): come
sumecollectconsumano un iteratore, con esempi annotati. Iterator::collect— documentazione della libreria standard (in inglese): mostra fianco a fianco lo stile con l’annotazione e quello con il turbofish.Sum— documentazione della libreria standard (in inglese): il trait dietro.sum(), con l’elenco completo dei tipi che lo implementano.Into— documentazione della libreria standard (in inglese): perché si implementaFrome si chiama.into(), e come viene scelto il tipo di destinazione.