Zum Inhalt springen

Rust-Fehler E0282: type annotations needed (Typangabe nötig)

E0282 bedeutet: Rust hat versucht, einen Typ selbst herauszufinden, und nicht genug Hinweise gefunden. Mit deiner Logik ist alles in Ordnung. Der Compiler braucht nur von dir den Namen eines Typs, meist mit ein paar zusätzlichen Zeichen. Diese Seite zeigt dir zuerst die Lösung und erklärt dann, wie Rusts Typ-Raten (die Typinferenz) funktioniert. So weißt du, wann du nachhelfen musst. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.

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?
| ++++++++

Dieses Programm erzeugt den Fehler:

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

Der Kommentar fragt: ein Vec von … was?

Die Worte „type annotations needed“ (Typannotationen nötig) tauchen auch bei drei anderen Codes auf. Sie bedeuten dasselbe, und die Lösungen auf dieser Seite funktionieren für alle:

  • E0283: .collect() oder .sum() ohne Zieltyp. rustc ergänzt eine Notiz wie the type must implement `FromIterator<&str>` oder `Sum<&i32>`, weil viele Typen als Ergebnis in Frage kommen.
  • E0284: "42".parse().unwrap() ohne Zieltyp. Die Notiz lautet cannot satisfy `<_ as FromStr>::Err == _`. Das ist eine furchteinflößende Art zu fragen: „In was soll ich das parsen?“
  • E0790: let settings = Default::default();. Die Überschrift lautet cannot call associated function on trait without specifying the corresponding `impl` type: Rust weiß nicht, wessen Standardwert du willst.

Welchen Code du bekommst, hängt von der genauen Zeile ab, und er kann sich ändern, wenn du mehr Code hinzufügst. let age = "42".parse().unwrap(); ergibt zum Beispiel E0284. Ruft die nächste Zeile aber age.pow(2) auf, wird daraus E0282. Mach dir um die Nummer keine Sorgen. Schau auf die Variable, auf die rustc zeigt.

Schau dir die Markierungen unter Zeile 2 an:

  1. ^^^^^^^^^^ unter mut scores: Das ist die Variable, deren Typ Rust nicht herausfinden konnte.
  2. ---------- unter Vec::new(): „type must be known at this point“, also „der Typ muss an dieser Stelle bekannt sein“. Vec::new() erzeugt eine leere Liste, aber eine leere Liste von was? Zahlen? Namen? Rust muss es wissen, um die richtige Art von Speicher vorzusehen.
  3. Die Hilfe schlägt let mut scores: Vec<T> vor. T ist hier nur ein Platzhalter. Du ersetzt ihn durch einen echten Typ, etwa Vec<u32>.

Stell dir ein Paket bei der Post vor, bei dem das Feld „Inhalt“ leer geblieben ist. Der Mitarbeiter kann es nicht verschicken, bis jemand etwas hineinschreibt. Du kannst es selbst ausfüllen – oder etwas ins Paket legen, damit er sieht, was drin ist.

Jeder Wert in Rust hat genau einen Typ, und der steht beim Kompilieren fest. Meistens schreibst du ihn nicht hin, weil Rust deinen Code liest und ihn selbst herausfindet. let count = 5; ist ein i32. let name = String::from("Ferris"); ist ein String. Das ist Typinferenz, und die Lektion Variablen zeigt mehr davon.

Die Inferenz nutzt Hinweise aus der ganzen Funktion, nicht nur aus einer Zeile. Rufst du später scores.push(90) auf, ist das ein Hinweis: Der Vec enthält Zahlen. Übergibst du scores an eine Funktion, die &[u32] nimmt, ist das ein noch besserer Hinweis. E0282 tritt auf, wenn es gar keine Hinweise gibt, oder wenn Rust den Typ braucht, bevor es beim Hinweis ankommt (zum Beispiel, um eine Methode nachzuschlagen).

Manche Funktionen sind in ihrem Rückgabetyp generisch: parse, collect, sum, Default::default, Vec::new, None. Sie können viele verschiedene Typen liefern, also kann Rust keinen für dich aussuchen. Es rät nicht, denn ein falscher Tipp würde das Verhalten deines Programms stillschweigend ändern. Unter Generics siehst du, wie eine Funktion viele Typen zurückgeben kann.

Das ist in den meisten Fällen die richtige Wahl. Schreib : Typ hinter den Variablennamen. Das ist kurz und dokumentiert deinen Code gleich für die nächste Person, die ihn liest:

fn main() {
let scores: Vec<u32> = Vec::new();
let age: u32 = "42".parse().unwrap();
let words: Vec<&str> = "rot grün blau".split(' ').collect();
let winner: Option<String> = None;
println!("Erfasste Punktzahlen: {}", scores.len());
println!("Nächstes Jahr wirst du {}", age + 1);
println!("{:?}", words);
println!("{:?}", winner);
}

Ausgabe:

Erfasste Punktzahlen: 0
Nächstes Jahr wirst du 43
["rot", "grün", "blau"]
None
▶ Selbst ausprobieren

Du musst nicht immer den vollständigen Typ schreiben. let words: Vec<_> = ...collect(); heißt: „ein Vec, und was drinsteckt, findest du selbst heraus“. Das _ bittet Rust, diesen Teil auszufüllen. Mehr zum Hinschreiben von Typen unter Datentypen.

Nimm das, wenn es keine Variable zum Annotieren gibt, zum Beispiel mitten in einer Kette von Iterator-Aufrufen. Du schreibst den Typ mit ::<Typ> direkt an den Funktionsaufruf. ::<> heißt „Turbofish“, weil es ein bisschen wie ein Fisch aussieht:

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!("Gesamt: {total}");
}

Ausgabe:

[4, 15, 7]
Gesamt: 26
▶ Selbst ausprobieren

Die drei häufigsten Turbofish-Aufrufe sind parse::<i32>(), collect::<Vec<_>>() und sum::<i32>(). Lösung 1 und Lösung 2 sind gleich richtig; let total: u32 = numbers.iter().sum(); erledigt dasselbe. Nimm, was sich besser liest.

Lösung 3: Den Wert benutzen, damit Rust den Typ ableiten kann

Abschnitt betitelt „Lösung 3: Den Wert benutzen, damit Rust den Typ ableiten kann“

Nimm das, wenn der Fehler auftaucht, weil dein Code noch nicht fertig ist. Oft legst du einen leeren Vec an, und der Code, der ihn füllt, kommt erst später. Sobald du den Wert benutzt, hat Rust seinen Hinweis, und der Fehler verschwindet – ganz ohne Annotation:

fn print_team(names: &[String]) {
for name in names {
println!("- {name}");
}
}
fn main() {
let mut team = Vec::new(); // Typ unbekannt ... noch
team.push(String::from("Ferris")); // jetzt weiß Rust: Vec<String>
team.push(String::from("Corro"));
print_team(&team);
}

Ausgabe:

- Ferris
- Corro
▶ Selbst ausprobieren

Die Reihenfolge spielt ein wenig mit. Der Hinweis muss kommen, bevor Rust den Typ braucht, um eine Methode aufzurufen. Ein Closure (eine anonyme Funktion) wie let shout = |s| s.to_uppercase(); scheitert trotzdem, selbst wenn du in der nächsten Zeile shout("hi") aufrufst. Rust muss nämlich wissen, was s ist, um .to_uppercase() zu finden. Dort schreibst du den Typ hin: |s: &str| s.to_uppercase(). Siehe Closures.

Lösung 4: Den Typ für None und Default::default() nennen

Abschnitt betitelt „Lösung 4: Den Typ für None und Default::default() nennen“

Nimm das für Werte, die überhaupt keinen Hinweis mitbringen. None verrät nicht, was es enthalten würde, und Default::default() verrät nicht, wessen Standardwert du willst. Schreib den Typ in den Ausdruck:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
muted: bool,
}
fn main() {
let fastest_lap = None::<f64>; // statt: None
let settings = Settings::default(); // statt: Default::default()
let backup: Settings = Default::default(); // eine Annotation geht auch
println!("{:?}", fastest_lap);
println!("{:?}", settings);
println!("Lautstärke im Backup: {}", backup.volume);
}

Ausgabe:

None
Settings { volume: 0, muted: false }
Lautstärke im Backup: 0
▶ Selbst ausprobieren

Settings::default() ist die klarste Wahl: Es liest sich als „die Standard-Settings“. None::<f64> ist praktisch, aber let fastest_lap: Option<f64> = None; (Lösung 1) ist üblicher. Siehe Option.

Text in eine Zahl parsen. let guess = line.trim().parse().unwrap(); ergibt E0284 (oder E0282, wenn du als Nächstes eine Methode auf guess aufrufst). parse kann ein i32, ein u8, ein f64 und vieles mehr liefern. Lösung: let guess: i32 = ... oder .parse::<i32>(). Später guess mit einer i32-Variable zu vergleichen, reicht hier nicht, denn parse braucht seinen Typ direkt beim Aufruf.

.collect(). Es kann einen Vec, einen String, ein HashSet, eine HashMap … bauen, deshalb muss es immer das Ziel kennen. Lösung: let words: Vec<_> = ...collect(); oder .collect::<Vec<_>>(). Siehe Iteratoren.

.sum() und .product(). nums.iter().sum() ergibt E0283 mit the type must implement `Sum<&i32>`. Die Summe von i32-Werten könnte ein i32 sein, aber auch andere Zahlentypen können Summen sein. Lösung: let total: i32 = nums.iter().sum(); oder .sum::<i32>().

Eine leere Collection in einem Struct-Konstruktor. let cache = HashMap::new(); in einer new()-Funktion, die du noch nicht fertig geschrieben hast. Lösung: Schreib den vollständigen Typ einmal hin (HashMap<String, u32>), oder gib den Wert in einem Struct zurück, dessen Feldtyp schon deklariert ist. Das ist ein Hinweis, den Rust nutzen kann.

  • E0308: mismatched types. Das passiert, wenn deine Annotation und der Wert nicht zusammenpassen, etwa bei let age: u32 = "42";.
  • E0277: trait bound not satisfied. Du hast einen Typ angegeben, aber er lässt sich so nicht bauen, etwa bei let n: Vec<i32> = "1 2".split(' ').collect(); (das ist ein Vec<&str>; parse zuerst jedes Element).
  • E0599: no method found, zum Beispiel wenn du let n: u32 annotierst und dann eine String-Methode wie .trim() darauf aufrufst.
  • E0004: non-exhaustive patterns, ein weiterer Fehler, bei dem rustc die Lösung für dich hinschreibt.
  • Alle Compilerfehler: die vollständige Liste der Fehlerseiten.

Kurzer Check

1. Welche Zeile behebt E0282 bei let ids = Vec::new();, wenn der Vec u64-Werte enthalten soll?

2. Warum braucht let n = "7".parse().unwrap(); einen Typ, obwohl der Text eindeutig eine Zahl ist?

3. Was bedeutet das _ in .collect::<Vec<_>>()?

Punkte: 0 / 3