Rust-Fehler E0283: type annotations needed (sum, collect und into)
E0283 bedeutet: Rust weiß, was dein Wert können muss, aber viele Typen können das, und Rust sucht sich keinen davon für dich aus. Meistens begegnet dir der Fehler direkt nach .sum(), .collect() oder .into(). Er gehört zu den freundlichsten Fehlern überhaupt: Du nennst den Typ, den du willst, und bist fertig. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.
Die Fehlermeldung
Abschnitt betitelt „Die Fehlermeldung“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(); | ++++++++++++„Type annotations needed“ heißt „Typangaben nötig“. Das help am Ende schlägt vor, total einen ausdrücklichen Typ zu geben. Dieses Programm erzeugt den Fehler:
fn main() { let prices = vec![250, 120, 99]; // Preise in Cent let total = prices.iter().sum(); println!("Total: {total} cents");}.collect() und .into() ergeben denselben Fehler mit einer anderen Note. Bei let words = text.split(' ').collect(); lautet sie the type must implement `FromIterator<&str>`. Bei let name = "Ferris".into(); lautet sie the type must implement `From<&str>`.
Was sie bedeutet
Abschnitt betitelt „Was sie bedeutet“Lies die Meldung von oben nach unten:
^^^^^untertotal: Das ist die Variable, deren Typ Rust nicht festlegen konnte.---untersum: „type must be known at this point“ (der Typ muss an dieser Stelle bekannt sein).sumkann viele verschiedene Typen zurückgeben, also braucht Rust die Antwort genau hier.- Die Note:
the type must implement `Sum<&i32>`(der Typ mussSum<&i32>implementieren). Das ist die Regel, die Rust gefunden hat: „totalmuss ein Typ sein, der sich durch Addieren von&i32-Werten bilden lässt.“ - Die help-Liste: die Typen, die wissen, wie man „eine Summe von etwas“ ist. Davon gibt es viele: jeder Zahlentyp,
Duration, sogarOptionundResult. Rust zeigt acht und sagt „and 88 others“ (und 88 weitere). Genau das meint man mit multiple impls satisfy this bound: Mehr als ein Typ hat einimpl, das die Regel erfüllt.
Stell dir vor, du bestellst im Café „irgendwas zu trinken“. Das ist eine echte Regel (es muss trinkbar sein), aber Tee, Saft und Wasser erfüllen sie alle. Die Bedienung kann nichts bringen, bis du dich entscheidest. Bei E0283 hält Rust das Tablett in der Hand und wartet auf deine Antwort.
Warum Rust das macht
Abschnitt betitelt „Warum Rust das macht“sum, collect und into sind generisch in ihrem Rückgabetyp. Der Typ, den du verlangst, entscheidet, was die Funktion tut. Der Trait (Sum, FromIterator, From) ist nur die Regel. Wenn also nichts Rust den Typ verrät, hat es eine Regel, aber keine eindeutige Antwort.
Und die Antwort ist wichtig. Sammelst du dieselben Wörter in einen Vec<&str>, bleiben Duplikate erhalten. Sammelst du sie in ein HashSet<&str>, fallen sie weg. Eine Summe als u8 läuft bei 255 über, als u64 nicht. Würde Rust den Typ für dich wählen, könnte eine winzige Änderung an anderer Stelle still und leise verändern, wie sich dein Programm verhält. Rust fragt lieber einmal nach.
Wie Rust Typen selbst herausfindet (Typableitung, auf Englisch „type inference“), erklärt die E0282-Seite. Deshalb wiederholen wir es hier nicht. Die Traits hinter diesen Methoden findest du unter Traits und Generics.
E0282, E0283, E0284: ein Problem, drei Codes
Abschnitt betitelt „E0282, E0283, E0284: ein Problem, drei Codes“Alle drei sagen „type annotations needed“, und für dich bedeuten sie dasselbe: Schreib den Typ hin. Der Code verrät nur, was Rust gerade tat, als es nicht weiterkam:
- E0282: Rust hat überhaupt keinen Hinweis auf den Typ, oder es brauchte den Typ, bevor es den Hinweis erreicht hat (zum Beispiel, um eine Methode wie
.len()zu finden). - E0283 (diese Seite): Rust hat einen Hinweis in Form einer Trait-Regel (
must implement `Sum<&i32>`), aber viele Typen erfüllen diese Regel. - E0284: Die Regel betrifft einen Typ, der zum unbekannten Typ gehört, etwa den Fehlertyp von
parse(cannot satisfy `<_ as FromStr>::Err == _`). Rust kann ihn erst prüfen, wenn es den Haupttyp kennt.
Der Code kann sich sogar ändern, während du tippst. let words = text.split(' ').collect(); gefolgt von println!("{:?}", words) ist E0283. Änderst du die nächste Zeile in words.len(), wird daraus E0282, weil Rust jetzt den Typ braucht, um die Methode zu finden. Die Lösungen unten funktionieren für alle drei.
So behebst du den Fehler
Abschnitt betitelt „So behebst du den Fehler“Lösung 1: Die Variable annotieren
Abschnitt betitelt „Lösung 1: Die Variable annotieren“Nimm diese Lösung in den meisten Fällen. Schreib : Typ hinter den Variablennamen. Das ist kurz und sagt der nächsten Person, die den Code liest, genau, was du baust:
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!("Summe: {total} Cent"); println!("{:?}", list); println!("Verschiedene Tags: {}", unique.len());}Ausgabe:
Summe: 469 Cent["rust", "web", "rust", "cli"]Verschiedene Tags: 3Dasselbe collect(), zwei verschiedene Ergebnisse: Der Typ, den du hinschreibst, ist wirklich die Anweisung. Das Innere kannst du mit _ Rust überlassen, etwa in let list: Vec<_> = ....
Lösung 2: Den Turbofish an sum und collect benutzen
Abschnitt betitelt „Lösung 2: Den Turbofish an sum und collect benutzen“Nimm diese Lösung, wenn es keine Variable zum Annotieren gibt: in einem println!, mitten in einer Iterator-Kette oder als Funktionsargument. Schreib den Typ mit ::<Typ> direkt an den Aufruf (das ist der „Turbofish“):
fn main() { let prices = vec![250, 120, 99]; println!("Summe: {} Cent", prices.iter().sum::<u32>());
let line = "tea,cake,soup"; let items = line.split(',').collect::<Vec<_>>(); let initials = line.split(',').map(|s| &s[..1]).collect::<String>();
println!("{:?}", items); println!("Initialen: {initials}");}Ausgabe:
Summe: 469 Cent["tea", "cake", "soup"]Initialen: tcsDie Wörter bleiben hier auf Englisch, weil &s[..1] das erste Byte herausschneidet und Umlaute zwei Bytes belegen. .product::<u64>() funktioniert genauso. Lösung 1 und Lösung 2 sind gleich richtig, also nimm die, die sich besser liest.
Lösung 3: Bei .into() den Zieltyp nennen (der Turbofish klappt hier nicht)
Abschnitt betitelt „Lösung 3: Bei .into() den Zieltyp nennen (der Turbofish klappt hier nicht)“Nimm diese Lösung bei .into() und .try_into(). Es ist verlockend, "Ferris".into::<String>() zu schreiben, aber das ergibt einen anderen Fehler, denn der Typparameter gehört zum Trait Into, nicht zur Methode:
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 argumentsDie Meldung sagt: Die Methode nimmt 0 generische Argumente, aber du hast 1 übergeben. Annotiere stattdessen die Variable, oder ruf From direkt auf dem Zieltyp auf. Das liest sich auch schön:
fn main() { let name: String = "Ferris".into(); let city = String::from("Lissabon");
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} wohnt in {city}"); println!("{big} {also_big} {byte}"); println!("Passt 300 in ein u8? {}", too_big.is_ok());}Ausgabe:
Ferris wohnt in Lissabon200 200 42Passt 300 in ein u8? falseString::from(...) und u64::from(...) nennen den Zieltyp gleich am Anfang der Zeile. Deshalb ziehen viele sie .into() vor, wenn es sonst keinen Hinweis gibt. Siehe Konvertierungen.
Lösung 4: Den Wert dort benutzen, wo sein Typ feststeht
Abschnitt betitelt „Lösung 4: Den Wert dort benutzen, wo sein Typ feststeht“Nimm diese Lösung, wenn der Wert sowieso an eine Stelle mit bekanntem Typ geht, etwa an einen Funktionsparameter oder ein Struct-Feld. Rust liest die ganze Funktion, also reicht diese spätere Verwendung:
fn print_receipt(total: u32, items: Vec<&str>) { println!("{} Artikel, {} Cent", items.len(), total);}
fn main() { let prices = vec![250, 120, 99]; let total = prices.iter().sum(); // u32, dank print_receipt let items = "tea,cake,soup".split(',').collect(); // Vec<&str>, aus demselben Grund print_receipt(total, items);}Ausgabe:
3 Artikel, 469 CentNicht jede Verwendung zählt. if prices.iter().sum() > 400 ist trotzdem E0283, denn > kann viele Typpaare vergleichen. Den Wert an einen generischen Parameter wie impl Display zu übergeben, hilft auch nicht, weil viele Typen Display sind. Die Verwendung muss einen ganz bestimmten Typ nennen.
Wo du ihn oft siehst
Abschnitt betitelt „Wo du ihn oft siehst“.collect() direkt in println!. println!("{:?}", line.split(',').collect()); ergibt E0283, und rustc schlägt die Lösung selbst vor: .collect::<Vec<_>>().
.try_into().unwrap(). let small = big.try_into().unwrap(); ergibt E0283 mit the type must implement `TryFrom<i64>`. Lösung: let small: u8 = big.try_into().unwrap(); oder u8::try_from(big).unwrap().
Default::default() an eine generische Funktion übergeben. Mit fn show(value: impl Debug) ist der Aufruf show(Default::default()) E0283: Rust kann nicht erkennen, wessen Standardwert du willst. Lösung: show(Settings::default()) oder show(u32::default()). Steht let s = Default::default(); allein in einer Zeile, bekommst du stattdessen E0790. Siehe Default.
Deine eigene generische Funktion. Kommt ein Typparameter nur im Rückgabetyp vor, wie bei fn make<T: Default>() -> T, dann ist let v = make(); E0283. Lösung: make::<Settings>() oder let v: Settings = make();.
Verwandte Fehler
Abschnitt betitelt „Verwandte Fehler“- E0282: type annotations needed, wenn Rust überhaupt keinen Hinweis auf den Typ hat (oder ihn für einen Methodenaufruf braucht).
- E0284: type annotations needed mit
cannot satisfy `<_ as FromStr>::Err == _`, fast immer durch.parse(). - E0790:
Default::default()(oder eine andere Trait-Funktion) aufgerufen, ohne zu sagen, für welchen Typ. - E0277: Du hast einen Typ genannt, aber er lässt sich so nicht bauen, etwa bei
let n: Vec<i32> = "1 2".split(' ').collect();. - E0107: falsche Anzahl generischer Argumente. Den bekommst du bei
.into::<String>(). - Alle Compilerfehler: die vollständige Liste der Fehlerseiten.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Was sagt dir die Note the type must implement Sum<&i32> bei E0283?
2. Welche Zeile behebt let name = "Ferris".into();?
3. Warum ergeben dieselben Wörter in Vec<&str> und in HashSet<&str> gesammelt unterschiedliche Ergebnisse?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- E0283 – Index der Rust-Fehlercodes (auf Englisch): die offizielle Erklärung, mit dem klassischen Beispiel: Soll aus umgedrehten
chars()einVec<char>oder einStringwerden? - Eine Reihe von Elementen mit Iteratoren verarbeiten – Das Rust-Buch: wie
sumundcollecteinen Iterator verbrauchen, mit annotierten Beispielen. Iterator::collect– Doku der Standardbibliothek (auf Englisch): zeigt den Annotations-Stil und den Turbofish-Stil nebeneinander.Sum– Doku der Standardbibliothek (auf Englisch): der Trait hinter.sum()und die vollständige Liste der Typen, die ihn implementieren.Into– Doku der Standardbibliothek (auf Englisch): warum duFromimplementierst und.into()aufrufst, und wie der Zieltyp gewählt wird.