Zum Inhalt springen

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.

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 Parameter
6 + .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 argument
error[E0593]: closure is expected to take 0 arguments, but it takes 1 argument
error[E0593]: function is expected to take 1 argument, but it takes 2 arguments
error[E0593]: closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument

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

Schau dir die beiden Markierungen unter Zeile 6 an:

  • ^^^ zeigt auf map. 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).

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.

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 Obst
▶ Selbst ausprobieren

Das _ 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_by vergleicht zwei Elemente: |a, b|.
  • for_each, map und filter übergeben ein Element: |item|, oder |_|, wenn du es nicht brauchst.
  • thread::spawn übergibt nichts: ||, meistens als move ||.
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: schreiben
Zu tun: testen
Zu tun: ausliefern
***
Worker 1 ist fertig
▶ Selbst ausprobieren

Um 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 call

Hier 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 nimmt
fn 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]
▶ Selbst ausprobieren

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.

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.

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