Zum Inhalt springen

Rust-Fehler E0631: type mismatch in closure arguments (falscher Parametertyp)

E0631 bedeutet: Dein Closure (eine anonyme Funktion) hat die richtige Anzahl von Parametern, aber mindestens einer hat den falschen Typ. In neun von zehn Fällen ist der Unterschied ein einzelnes &: Du hast |x: i32| geschrieben, aber der Iterator leiht dir ein &i32. Die schnellste Lösung ist oft, die Typangabe einfach zu löschen und Rust den Typ selbst herausfinden zu lassen. Diese Seite zeigt dir zuerst diese Lösung und erklärt dann, woher das & kommt – damit du es beim nächsten Mal vorhersehen kannst. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.

error[E0631]: type mismatch in closure arguments
--> src/main.rs:3:43
|
3 | let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ die Elemente sind &i32
| ^^^ -------- found signature defined here
| |
| expected due to this
|
= note: expected closure signature `fn(&{integer}) -> _`
found closure signature `fn(i32) -> _`
note: required by a bound in `map`
help: consider adjusting the signature so it borrows its argument
|
3 | let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); // ❌ die Elemente sind &i32
| +

„Type mismatch in closure arguments“ heißt so viel wie „Typen der Closure-Argumente passen nicht“. Das help schlägt vor, die Signatur so anzupassen, dass das Closure sein Argument ausleiht. Dieses Programm erzeugt den Fehler:

fn main() {
let prices = vec![120, 45, 300];
let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ die Elemente sind &i32
println!("{:?}", doubled);
}

Direkt danach gibt rustc noch einen zweiten, längeren Fehler aus: the method `collect` exists for struct `Map<...>`, but its trait bounds were not satisfied (E0599). Der ist nur eine Folge davon. Behebst du E0631, verschwindet er.

Die beiden note-Zeilen sind das Herzstück der Meldung. Lies sie als „die Methode wollte dies, du hast das geschrieben“:

  • expected fn(&{integer}) -> _ (erwartet): map ruft dein Closure mit einer Referenz auf eine Ganzzahl auf. {integer} heißt „irgendein Ganzzahltyp, den Rust noch nicht festgelegt hat“, und _ heißt „beliebiger Rückgabetyp“.
  • found fn(i32) -> _ (gefunden): Dein Closure sagt, es nimmt ein i32 als Wert.

Leg beide nebeneinander, und der einzige Unterschied ist das &. Die help:-Zeile zeigt dir die Lösung mit einem einzigen Zeichen.

Denk an eine Garderobe. Du gibst deinen Mantel ab und bekommst eine Marke zurück. .iter() funktioniert genauso: Es gibt dir nicht die Elemente selbst, sondern Marken dafür (Referenzen). Ein Closure, das unbedingt den Mantel selbst haben will, nimmt keine Marke an.

Jede Iterator-Methode legt genau fest, welchen Typ sie an dein Closure übergibt. Und dieser Typ hängt davon ab, wie du den Iterator erzeugt hast:

  • v.iter() leiht jedes Element aus: &T.
  • v.into_iter() (auf einem Vec, der dir gehört) gibt jedes Element weg: T.
  • filter und find leihen dir das Element noch einmal aus. Auf .iter() bekommst du also &&T.
  • sort_by leiht dir zwei Elemente aus: &T und &T.

Lässt du die Parametertypen weg, setzt Rust sie anhand der Methode ein, und dann passen sie immer. Schreibst du selbst einen Typ hin, nimmt Rust dich beim Wort und prüft ihn. Rust kopiert den Wert nicht heimlich und macht aus einer Referenz keinen eigenen Wert, denn das würde ändern, was dein Code tut (und bei einem String kostet eine Kopie Speicher und Zeit). Die Lektion Iteratoren zeigt die drei Wege zu einem Iterator, und Borrowing (Ausleihen) erklärt Referenzen.

Probier diese Lösung zuerst. Closure-Parameter brauchen fast nie einen Typ: Rust weiß genau, was map, filter und sort_by übergeben. Lösch : i32, und der Code kompiliert:

fn main() {
let prices = vec![120, 45, 300];
let ages = vec![12, 34, 17, 50];
let mut scores = vec![42, 7, 99, 15];
// keine Typangaben: Rust nimmt, was der Iterator wirklich liefert
let doubled: Vec<i32> = prices.iter().map(|p| p * 2).collect();
let adults: Vec<&i32> = ages.iter().filter(|age| **age >= 18).collect();
scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", doubled);
println!("{:?}", adults);
println!("{:?}", scores);
}

Ausgabe:

[240, 90, 600]
[34, 50]
[99, 42, 15, 7]
▶ Selbst ausprobieren

p * 2 funktioniert mit einem &i32, weil die Rechenoperatoren in Rust auch Referenzen annehmen. In filter ist age ein &&i32, deshalb folgt **age beiden Referenzen bis zur Zahl. Vergleiche wie b.cmp(a) funktionieren direkt mit Referenzen.

Lösung 2: Den Typ hinschreiben, den die Methode wirklich übergibt

Abschnitt betitelt „Lösung 2: Den Typ hinschreiben, den die Methode wirklich übergibt“

Nimm diese Lösung, wenn der Typ sichtbar bleiben soll, zum Beispiel damit eine lange Kette leichter zu lesen ist. Folge dem help: von rustc und ergänze das &. Du kannst das & auch ins Muster schreiben: |&p| nimmt die Referenz auseinander und gibt dir eine einfache Kopie der Zahl (das klappt bei Copy-Typen wie Ganzzahlen):

fn main() {
let prices = vec![120, 45, 300];
let ages = vec![12, 34, 17, 50];
let mut scores = vec![42, 7, 99, 15];
// die echten Typen hinschreiben: & bei .iter(), && in filter
let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect();
let adults: Vec<&i32> = ages.iter().filter(|age: &&i32| **age >= 18).collect();
scores.sort_by(|a: &i32, b: &i32| b.cmp(a));
// oder die Zahl mit einem &-Muster herauskopieren
let tax: Vec<i32> = prices.iter().map(|&p| p / 10).collect();
println!("{:?}", doubled);
println!("{:?}", adults);
println!("{:?}", scores);
println!("{:?}", tax);
}

Ausgabe:

[240, 90, 600]
[34, 50]
[99, 42, 15, 7]
[12, 4, 30]
▶ Selbst ausprobieren

Achte auf den Unterschied: |p: &i32| heißt „p ist eine Referenz“, während |&p| heißt „ich bekomme eine Referenz, gib mir, was dahintersteckt“.

Nimm diese Lösung, wenn du wirklich die Werte willst und keine Referenzen. Dafür gibt es zwei Wege:

  • .iter().copied() macht aus jedem &i32 ein i32. Das nimmst du für kleine Copy-Typen wie Zahlen, char und bool. (.cloned() macht dasselbe für Typen wie String, indem es klont.)
  • .into_iter() auf einem Vec gibt die Elemente selbst heraus. Danach ist der Vec verbraucht. Nimm das also, wenn du ihn nicht mehr brauchst.
fn main() {
let prices = vec![120, 45, 300];
// .copied() macht aus &i32-Elementen i32-Elemente
let doubled: Vec<i32> = prices.iter().copied().map(|p: i32| p * 2).collect();
println!("{:?}", doubled);
// .into_iter() auf einem Vec gibt die Elemente selbst heraus (und verbraucht den Vec)
let names = vec![String::from("ferris"), String::from("corro")];
let loud: Vec<String> = names.into_iter().map(|name: String| name.to_uppercase()).collect();
println!("{:?}", loud);
}

Ausgabe:

[240, 90, 600]
["FERRIS", "CORRO"]
▶ Selbst ausprobieren

Brauchst du names danach noch, bleib bei .iter() und nimm stattdessen &String. Wenn du Werte aus einem Vec herausbewegen willst, den du nur ausgeliehen hast, ist das ein anderer Fehler: E0507.

Lösung 4: Eine benannte Funktion nehmen lassen, was der Iterator liefert

Abschnitt betitelt „Lösung 4: Eine benannte Funktion nehmen lassen, was der Iterator liefert“

Nimm diese Lösung, wenn du eine Funktion über ihren Namen übergibst, etwa .map(shout). Dann steht in der Meldung „function“ statt „closure“:

error[E0631]: type mismatch in function arguments
--> src/main.rs:7:51
|
1 | fn shout(word: &String) -> String {
| --------------------------------- found signature defined here
...
7 | let loud: Vec<String> = words.into_iter().map(shout).collect();
| --- ^^^^^ expected due to this
| |
| required by a bound introduced by this call
|
= note: expected function signature `fn(&str) -> _`
found function signature `fn(&String) -> _`

Die Elemente sind &str, aber shout will &String. Eine gute Gewohnheit: Lass Funktionen, die Text nur lesen, &str nehmen. Dann akzeptieren sie String-Literale, &String und mehr. Wenn der Iterator ein zusätzliches & hinzufügt (wie filter), pack den Aufruf in ein kurzes Closure. In einem normalen Funktionsaufruf entfernt Rust überzählige & für dich (das nennt man Deref Coercion), aber nicht, wenn du die Funktion über ihren Namen übergibst:

// &str nehmen: funktioniert mit String-Literalen, &String und mehr
fn shout(word: &str) -> String {
word.to_uppercase()
}
fn is_long(word: &str) -> bool {
word.len() > 4
}
fn main() {
let words = vec!["crab", "rustacean", "ferris"];
// die Elemente sind &str, also lässt sich shout über den Namen übergeben
let loud: Vec<String> = words.iter().copied().map(shout).collect();
println!("{:?}", loud);
// filter liefert &&str: ein kleines Closure lässt Rust die Umwandlung übernehmen
let long: Vec<&str> = words.iter().copied().filter(|w| is_long(w)).collect();
println!("{:?}", long);
}

Ausgabe:

["CRAB", "RUSTACEAN", "FERRIS"]
["rustacean", "ferris"]
▶ Selbst ausprobieren

Die Wörter bleiben hier auf Englisch, weil is_long mit .len() die Bytes zählt und Umlaute zwei Bytes belegen. Mehr zu &str und String findest du unter Strings.

Sortieren mit Typangaben. scores.sort_by(|a: i32, b: i32| b.cmp(&a)) ergibt E0631 mit expected closure signature `for<'a, 'b> fn(&'a {integer}, &'b {integer}) -> _`. Der Teil for<'a, 'b> sagt nur „für jede beliebige Ausleihe“. Lösung: |a, b| b.cmp(a). Siehe Sortieren und Suchen.

Map-Einträge mit eigenen Typen. stock.iter().map(|(name, count): (String, i32)| ...) auf einer HashMap<String, i32> ergibt expected closure signature `fn((&String, &_)) -> _`. .iter() auf einer Map leiht Schlüssel und Wert aus. Lösung: Lass die Annotation weg, oder schreib (&String, &i32).

Deine eigene generische Funktion. Bekommt fn apply<F: Fn(i32) -> i32>(f: F, x: i32) ein Closure |x: f64| x / 2.0, meldet rustc expected closure signature `fn(i32) -> _` und found closure signature `fn(f64) -> _` und zeigt auf den Bound. Lösung: Lass das Closure i32 nehmen (und wandle innen um, x as f64 / 2.0), oder ändere den Bound, wenn du wirklich f64 meintest. Siehe Closure-Traits.

for_each mit String. words.iter().for_each(|w: String| ...) auf einem Vec<&str> erwartet fn(&&str). Ein &str ist kein String. Lösung: |w|, oder |w: &&str|.

Kurzer Check

1. let v = vec![1, 2, 3]; Welchen Typ bekommt das Closure in v.iter().map(|x| ...)?

2. Die Note sagt expected closure signature fn(&{integer}) -> _, found closure signature fn(i32) -> _. Was ist die schnellste Lösung?

3. Was macht |&p| p * 2 in prices.iter().map(...)?

Punkte: 0 / 3