Rust-Fehler E0593: closure is expected to take a single 2-tuple as argument
E0593 bedeutet: Du hast ein Closure (eine anonyme Funktion) oder eine Funktion an etwas anderes übergeben, etwa an map, sort_by oder thread::spawn, und die Anzahl seiner Parameter passt nicht dazu, wie es aufgerufen wird. Der mit Abstand häufigste Fall: Ein Iterator gibt deinem Closure ein Tupel, und du hast zwei Parameter geschrieben. Meistens reicht ein einziges Klammerpaar, und rustc schreibt dir die Lösung sogar hin. Rufst du das Closure selbst mit der falschen Anzahl von Werten auf, ist das stattdessen E0057. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.
Die Fehlermeldung
Abschnitt betitelt „Die Fehlermeldung“error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments --> src/main.rs:6:10 |6 | .map(|i, name| format!("{}. {name}", i + 1)) // ❌ zwei Parameter | ^^^ --------- takes 2 distinct arguments | | | expected closure that takes a single 2-tuple as argument |help: change the closure to accept a tuple instead of individual arguments |6 - .map(|i, name| format!("{}. {name}", i + 1)) // ❌ zwei Parameter6 + .map(|(i, name)| format!("{}. {name}", i + 1)) // ❌ zwei Parameter |Auf Deutsch heißt das ungefähr: „Vom Closure wird erwartet, dass es ein einzelnes 2-Tupel als Argument nimmt, aber es nimmt 2 getrennte Argumente.“ Das help schlägt vor, das Closure so zu ändern, dass es ein Tupel annimmt. Dieses Programm erzeugt den Fehler:
fn main() { let names = vec!["Ferris", "Corro", "Rusty"]; let lines: Vec<String> = names .iter() .enumerate() .map(|i, name| format!("{}. {name}", i + 1)) // ❌ zwei Parameter .collect(); println!("{:?}", lines);}E0593 gibt es auch mit ein paar anderen ersten Zeilen. Sie bedeuten alle „falsche Anzahl von Parametern“:
error[E0593]: closure is expected to take 2 arguments, but it takes 1 argumenterror[E0593]: closure is expected to take 0 arguments, but it takes 1 argumenterror[E0593]: function is expected to take 1 argument, but it takes 2 argumentserror[E0593]: closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argumentVon oben nach unten: Erwartet werden 2 Argumente, aber das Closure nimmt 1. Erwartet werden 0, aber es nimmt 1. Eine Funktion soll 1 Argument nehmen, nimmt aber 2. Und umgekehrt: Erwartet werden 2 getrennte Argumente, aber das Closure nimmt ein Tupel.
Was sie bedeutet
Abschnitt betitelt „Was sie bedeutet“Schau dir die beiden Markierungen unter Zeile 6 an:
^^^zeigt aufmap. Das ist der Teil, der dein Closure aufruft, und er entscheidet, wie viele Werte er übergibt.---------zeigt auf deine Parameterliste|i, name|. Das hast du versprochen anzunehmen.
Die beiden passen nicht zusammen. enumerate() macht aus jedem Element ein Tupel, (index, item), und map gibt dieses Tupel als ein Argument an dein Closure weiter. Dein Closure verlangt aber zwei einzelne.
Stell dir einen Briefkastenschlitz vor, der immer nur einen Umschlag annimmt. Du kannst zwei Briefe in den Umschlag stecken (ein Tupel), aber zwei lose Briefe nimmt der Schlitz nicht. |(i, name)| sagt: „Gib mir den Umschlag, ich mache ihn gleich hier auf.“
Das Wort „closure“ oder „function“ in der Meldung verrät dir, was du übergeben hast: ein Closure, das du direkt an Ort und Stelle geschrieben hast, oder eine benannte fn wie in .map(add).
Warum Rust das macht
Abschnitt betitelt „Warum Rust das macht“Jede Methode, die ein Closure annimmt, legt genau fest, wie sie es aufruft. map will so etwas wie FnMut(Self::Item) -> B: ein Argument, nämlich das Element. Bei enumerate() ist das Element (usize, &&str). Das ist ein einziger Wert, der eben ein Tupel ist. sort_by will FnMut(&T, &T) -> Ordering: zwei Argumente. thread::spawn will FnOnce() -> T: gar keins.
Rust teilt ein Tupel nie von selbst in einzelne Argumente auf, und es packt einzelne Argumente nie in ein Tupel. Dadurch bleibt die Regel einfach: Was in der Signatur der Methode steht, bekommt dein Closure. Sobald du diese Signaturen lesen kannst, weißt du bei jeder Iterator-Methode vorher, was sie dir gibt.
Mehr über die Fn-Traits dahinter erfährst du unter Closure-Traits, und mehr über Tupel unter Ein Tupel destrukturieren.
So behebst du den Fehler
Abschnitt betitelt „So behebst du den Fehler“Lösung 1: Das Tupel direkt in der Parameterliste auspacken
Abschnitt betitelt „Lösung 1: Das Tupel direkt in der Parameterliste auspacken“Nimm diese Lösung, wenn die Meldung „a single 2-tuple“ sagt. Diese Lösung brauchst du am häufigsten. Setz Klammern: |(i, name)| nimmt ein Tupel und gibt seinen beiden Teilen Namen. Das funktioniert bei enumerate, bei zip und wenn du über eine Map iterierst:
use std::collections::BTreeMap;
fn main() { let names = vec!["Ferris", "Corro", "Rusty"];
// enumerate liefert pro Element ein Tupel (index, name) let lines: Vec<String> = names .iter() .enumerate() .map(|(i, name)| format!("{}. {name}", i + 1)) .collect(); println!("{:?}", lines);
// auch beim Iterieren über eine Map gibt es pro Element ein Tupel (key, value) let mut stock = BTreeMap::new(); stock.insert("apples", 3); stock.insert("pears", 0); stock.insert("plums", 5); let total: i32 = stock.iter().map(|(_name, count)| *count).sum(); println!("{total} Stück Obst");}Ausgabe:
["1. Ferris", "2. Corro", "3. Rusty"]8 Stück ObstDas _ am Anfang von _name sagt Rust: „Ich weiß, dass ich das nicht benutze.“ Du kannst auch einfach _ schreiben. Wie dieselbe Idee bei zip aussieht, zeigt die E0057-Seite.
Lösung 2: So viele Parameter schreiben, wie die Methode übergibt
Abschnitt betitelt „Lösung 2: So viele Parameter schreiben, wie die Methode übergibt“Nimm diese Lösung, wenn die Meldung „expected to take N arguments“ sagt (es werden N Argumente erwartet). Schau nach, was die Methode übergibt, und schreib genau so viele Parameter:
sort_byvergleicht zwei Elemente:|a, b|.for_each,mapundfilterübergeben ein Element:|item|, oder|_|, wenn du es nicht brauchst.thread::spawnübergibt nichts:||, meistens alsmove ||.
use std::thread;
fn main() { // sort_by vergleicht immer zwei Elemente: zwei Parameter let mut scores = vec![42, 7, 99, 15]; scores.sort_by(|a, b| b.cmp(a)); println!("{:?}", scores);
// for_each gibt ein Element weiter: ein Parameter (nimm _, wenn du ihn nicht brauchst) let tasks = vec!["schreiben", "testen", "ausliefern"]; tasks.iter().for_each(|task| println!("Zu tun: {task}")); tasks.iter().for_each(|_| print!("*")); println!();
// thread::spawn ruft das Closure ohne Werte auf: null Parameter let id = 1; let handle = thread::spawn(move || format!("Worker {id} ist fertig")); println!("{}", handle.join().unwrap());}Ausgabe:
[99, 42, 15, 7]Zu tun: schreibenZu tun: testenZu tun: ausliefern***Worker 1 ist fertigUm Daten in einen Thread zu bringen, fügst du keinen Parameter hinzu. Du fängst sie mit move ein, wie id oben. Siehe Nebenläufigkeit und Sortieren und Suchen.
Lösung 3: Eine benannte Funktion einpacken oder ein Tupel nehmen lassen
Abschnitt betitelt „Lösung 3: Eine benannte Funktion einpacken oder ein Tupel nehmen lassen“Nimm diese Lösung, wenn die Meldung „function is expected to take …“ sagt. Du hast eine Funktion über ihren Namen übergeben, etwa .map(add), und ihre Parameter passen nicht. Hier sind die Elemente Paare, aber add nimmt zwei Zahlen:
error[E0593]: function is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments --> src/main.rs:7:48 |1 | fn add(a: i32, b: i32) -> i32 { | ----------------------------- takes 2 distinct arguments...7 | let sums: Vec<i32> = pairs.into_iter().map(add).collect(); | --- ^^^ expected function that takes a single 2-tuple as argument | | | required by a bound introduced by this callHier erwartet map eine Funktion, die ein 2-Tupel nimmt, aber add nimmt zwei getrennte Argumente. Du kannst die Funktion in ein kleines Closure einpacken, das das Tupel auspackt und sie aufruft. Oder du schreibst eine Variante, die das Tupel direkt annimmt:
fn add(a: i32, b: i32) -> i32 { a + b}
// eine Variante, die das Paar als ein Tupel nimmtfn add_pair((a, b): (i32, i32)) -> i32 { a + b}
fn main() { let pairs = vec![(1, 2), (3, 4), (10, 20)];
// einpacken: ein Tupel rein, zwei Argumente raus let sums: Vec<i32> = pairs.iter().map(|&(a, b)| add(a, b)).collect(); println!("{:?}", sums);
// oder eine Funktion übergeben, die schon ein Tupel nimmt let sums2: Vec<i32> = pairs.into_iter().map(add_pair).collect(); println!("{:?}", sums2);}Ausgabe:
[3, 7, 30][3, 7, 30]Das & in |&(a, b)| steht da, weil .iter() jedes Paar als &(i32, i32) ausleiht. Das Muster kopiert die beiden Zahlen heraus. Meistens ist der Wrapper die bessere Wahl: add bleibt eine normale Funktion mit zwei Argumenten, die der Rest deines Codes aufrufen kann.
Erwartet deine eigene generische Funktion Fn(i32) -> i32 und du übergibst ein add mit zwei Argumenten, bekommst du „function is expected to take 1 argument, but it takes 2 arguments“. Die Lösung ist ein Wrapper, der den fehlenden Wert ergänzt. Er steht in Lösung 3 von E0057.
Wo du ihn oft siehst
Abschnitt betitelt „Wo du ihn oft siehst“HashMap::retain (genau andersherum). Die meisten Map-Methoden geben dir ein Tupel, aber retain übergibt Schlüssel und Wert als zwei getrennte Argumente. stock.retain(|(name, count)| *count > 0) ergibt deshalb „closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument“. Lösung: Lass die Klammern weg, stock.retain(|_name, count| *count > 0). Vec::retain nimmt wie gewohnt ein Element.
Den Parameter vergessen. tasks.iter().for_each(|| println!("task")) ergibt „closure is expected to take 1 argument, but it takes 0 arguments“, und rustc schlägt |_| vor. Lösung: |_|, oder |task|, wenn du den Wert benutzen willst.
Threads. thread::spawn(|id| ...) ergibt „closure is expected to take 0 arguments, but it takes 1 argument“. Lösung: thread::spawn(move || ...), und id fängst du von außen ein.
Ein Parameter mit Funktionszeiger. Nimmt deine Funktion f: fn(i32) -> i32 (einen einfachen Funktionszeiger, kein generisches F: Fn), dann ergibt ein add mit zwei Argumenten stattdessen E0308, mit dem Label „incorrect number of function parameters“ (falsche Anzahl von Funktionsparametern). Die Lösung ist dieselbe: ein Wrapper-Closure wie |x| add(x, 10). Closures, die nichts einfangen, kannst du als fn-Zeiger übergeben.
Verwandte Fehler
Abschnitt betitelt „Verwandte Fehler“- E0057: closure takes N arguments but M were supplied: Du rufst ein Closure selbst mit der falschen Anzahl von Werten auf.
- E0631: type mismatch in closure arguments: Die Anzahl stimmt, aber ein Parameter hat den falschen Typ, oft
i32, wo der Iterator&i32liefert. - E0061: function takes N arguments but M were supplied: eine normale Funktion mit der falschen Anzahl von Argumenten aufrufen.
- E0308: mismatched types: Das bekommst du bei einem
fn(...)-Zeiger als Parameter, oder bei|(a, b)|, wenn die Elemente gar keine Tupel sind. - E0599: no method found: taucht manchmal direkt nach E0593 auf, zum Beispiel als „the method
collectexists … but its trait bounds were not satisfied“. Behebe zuerst E0593, dann verschwindet er. - Alle Compilerfehler: die vollständige Liste der Fehlerseiten.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. names.iter().enumerate().map(|i, name| ...) ergibt E0593. Wie lautet die Lösung?
2. Wie viele Parameter braucht das Closure für scores.sort_by(...)?
3. thread::spawn(|id| println!("{id}")) ergibt E0593. Wie bringst du id in den Thread?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- E0593 – Index der Rust-Fehlercodes (auf Englisch): die offizielle Erklärung dieses Fehlers.
- Closures – Das Rust-Buch: Closure-Syntax, wie Parametertypen abgeleitet werden, und die
Fn-Traits. - As input parameters – Rust by Example (auf Englisch): wie der
Fn-Bound einer Funktion bestimmt, welches Closure sie annimmt. Iterator::enumerate– Doku der Standardbibliothek (auf Englisch): zeigt, dass jedes Element zu einem Tupel(index, value)wird.HashMap::retain– Doku der Standardbibliothek (auf Englisch): die Map-Methode, deren Closure Schlüssel und Wert als zwei Argumente bekommt.