Zum Inhalt springen

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.

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

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

Lies die Meldung von oben nach unten:

  1. ^^^^^ unter total: Das ist die Variable, deren Typ Rust nicht festlegen konnte.
  2. --- unter sum: „type must be known at this point“ (der Typ muss an dieser Stelle bekannt sein). sum kann viele verschiedene Typen zurückgeben, also braucht Rust die Antwort genau hier.
  3. Die Note: the type must implement `Sum<&i32>` (der Typ muss Sum<&i32> implementieren). Das ist die Regel, die Rust gefunden hat: „total muss ein Typ sein, der sich durch Addieren von &i32-Werten bilden lässt.“
  4. Die help-Liste: die Typen, die wissen, wie man „eine Summe von etwas“ ist. Davon gibt es viele: jeder Zahlentyp, Duration, sogar Option und Result. 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 ein impl, 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.

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.

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.

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: 3
▶ Selbst ausprobieren

Dasselbe 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: tcs
▶ Selbst ausprobieren

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

Die 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 Lissabon
200 200 42
Passt 300 in ein u8? false
▶ Selbst ausprobieren

String::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 Cent
▶ Selbst ausprobieren

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

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

  • 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

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