Salta ai contenuti

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.

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 others
note: 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>`.

Leggi il messaggio dall’alto in basso:

  1. ^^^^^ sotto total: è la variabile di cui Rust non è riuscito a decidere il tipo.
  2. --- sotto sum: «type must be known at this point» (il tipo deve essere noto a questo punto). sum può restituire molti tipi diversi, quindi Rust ha bisogno della risposta qui.
  3. La nota: the type must implement `Sum<&i32>`. È la regola che Rust ha trovato: «total deve essere un tipo che si può ottenere sommando valori &i32».
  4. L’elenco dell’help: i tipi che sanno essere «una somma di qualcosa». Sono tanti: tutti i tipi numerici, Duration, persino Option e Result. 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 un impl che 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.

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.

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.

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: 3
▶ Provalo tu

Stesso collect(), due risultati diversi: il tipo che scrivi è davvero l’istruzione. Puoi lasciare a Rust la parte interna con _, come in let list: Vec<_> = ....

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
▶ Provalo tu

.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 arguments

Annota 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 Lisbona
200 200 42
300 sta in un u8? false
▶ Provalo tu

String::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 centesimi
▶ Provalo tu

Non 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.

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

  • 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

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