Skip to content

Rust Error E0593: Closure Is Expected to Take a Single 2-Tuple as Argument

E0593 means you handed a closure (or a function) to something else, like map, sort_by or thread::spawn, and its number of parameters doesn’t match how it will be called. The most common case by far: an iterator gives your closure one tuple, and you wrote two parameters. It’s a one-character fix most of the time, and rustc usually writes it for you. If you’re the one calling the closure with the wrong number of values, that’s E0057 instead. For the other errors in this series, see Rust compiler errors.

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)) // ❌ two parameters
| ^^^ --------- 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)) // ❌ two parameters
6 + .map(|(i, name)| format!("{}. {name}", i + 1)) // ❌ two parameters
|

This is the program that produces it:

fn main() {
let names = vec!["Ferris", "Corro", "Rusty"];
let lines: Vec<String> = names
.iter()
.enumerate()
.map(|i, name| format!("{}. {name}", i + 1)) // ❌ two parameters
.collect();
println!("{:?}", lines);
}

E0593 also comes with a few other first lines. They all mean “wrong number of parameters”:

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

Look at the two markers under line 6:

  • ^^^ points at map. That’s the one calling your closure, and it decides how many values it passes.
  • --------- points at your parameter list |i, name|. That’s what you promised to accept.

They don’t agree. enumerate() turns every item into one tuple, (index, item), and map passes that tuple to your closure as one argument. Your closure asks for two separate ones.

Think of a mail slot that takes one envelope at a time. You can put two letters inside the envelope (a tuple), but the slot won’t take two loose letters. |(i, name)| says “give me the envelope, and I’ll open it right here”.

The word “closure” or “function” in the message tells you what you passed: a closure written in place, or a named fn like .map(add).

Every method that takes a closure says exactly how it will call it. map wants something like FnMut(Self::Item) -> B: one argument, the item. For enumerate() the item is (usize, &&str). That’s one value that happens to be a tuple. sort_by wants FnMut(&T, &T) -> Ordering: two arguments. thread::spawn wants FnOnce() -> T: none at all.

Rust never splits a tuple into separate arguments for you, and never packs separate arguments into a tuple. That keeps the rule simple: what the method’s signature says is what your closure receives. Once you can read these signatures, you can predict what any iterator method hands you.

You can read more about the Fn traits behind this in Closure traits, and about tuples in Destructuring a tuple.

Fix 1: Unpack the tuple inside the parameter list

Section titled “Fix 1: Unpack the tuple inside the parameter list”

Choose this when the message says “a single 2-tuple”. This is the fix you’ll need most often. Add parentheses: |(i, name)| takes one tuple and gives its two parts names. It works for enumerate, zip, and iterating over a map:

use std::collections::BTreeMap;
fn main() {
let names = vec!["Ferris", "Corro", "Rusty"];
// enumerate gives one (index, name) tuple per item
let lines: Vec<String> = names
.iter()
.enumerate()
.map(|(i, name)| format!("{}. {name}", i + 1))
.collect();
println!("{:?}", lines);
// iterating a map also gives one (key, value) tuple per item
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} pieces of fruit");
}

Output:

["1. Ferris", "2. Corro", "3. Rusty"]
8 pieces of fruit
▶ Try it Yourself

The _ at the start of _name tells Rust “I know I’m not using this”. You can also write just _. The same idea for zip is shown on the E0057 page.

Fix 2: Give the method the number of parameters it calls with

Section titled “Fix 2: Give the method the number of parameters it calls with”

Choose this when the message says “expected to take N arguments”. Check what the method passes, then write exactly that many parameters:

  • sort_by compares two items: |a, b|.
  • for_each, map, filter pass one item: |item|, or |_| if you don’t need it.
  • thread::spawn passes nothing: ||, usually move ||.
use std::thread;
fn main() {
// sort_by compares two items at a time: two parameters
let mut scores = vec![42, 7, 99, 15];
scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", scores);
// for_each hands over one item: one parameter (use _ if you don't need it)
let tasks = vec!["write", "test", "ship"];
tasks.iter().for_each(|task| println!("todo: {task}"));
tasks.iter().for_each(|_| print!("*"));
println!();
// thread::spawn calls the closure with nothing: zero parameters
let id = 1;
let handle = thread::spawn(move || format!("worker {id} done"));
println!("{}", handle.join().unwrap());
}

Output:

[99, 42, 15, 7]
todo: write
todo: test
todo: ship
***
worker 1 done
▶ Try it Yourself

To pass data into a thread, don’t add a parameter. Capture it with move, like id above. See Concurrency and Sorting.

Fix 3: Wrap a named function, or make it take a tuple

Section titled “Fix 3: Wrap a named function, or make it take a tuple”

Choose this when the message says “function is expected to take…”. You passed a function by name, like .map(add), and its parameters don’t fit. Here the items are pairs, but add takes two numbers:

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

You can wrap the function in a small closure that unpacks the tuple and calls it, or write a version that takes the tuple directly:

fn add(a: i32, b: i32) -> i32 {
a + b
}
// a version that takes the pair as one tuple
fn add_pair((a, b): (i32, i32)) -> i32 {
a + b
}
fn main() {
let pairs = vec![(1, 2), (3, 4), (10, 20)];
// wrap: one tuple in, two arguments out
let sums: Vec<i32> = pairs.iter().map(|&(a, b)| add(a, b)).collect();
println!("{:?}", sums);
// or pass a function that already takes one tuple
let sums2: Vec<i32> = pairs.into_iter().map(add_pair).collect();
println!("{:?}", sums2);
}

Output:

[3, 7, 30]
[3, 7, 30]
▶ Try it Yourself

The & in |&(a, b)| is there because .iter() lends each pair as &(i32, i32), and the pattern copies the two numbers out. The wrapper is usually the better choice: add stays a normal two-argument function that the rest of your code can call.

If your own generic function wants Fn(i32) -> i32 and you pass a two-argument add, you get “function is expected to take 1 argument, but it takes 2 arguments”. The fix is a wrapper that fills in the extra value, shown in E0057’s Fix 3.

HashMap::retain (the other way around). Most map methods hand you a tuple, but retain passes key and value as two separate arguments. So stock.retain(|(name, count)| *count > 0) gives “closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument”. Fix: drop the parentheses, stock.retain(|_name, count| *count > 0). Vec::retain takes one item, as usual.

Forgetting the parameter. tasks.iter().for_each(|| println!("task")) gives “closure is expected to take 1 argument, but it takes 0 arguments”, and rustc suggests |_|. Fix: |_|, or |task| if you want to use it.

Threads. thread::spawn(|id| ...) gives “closure is expected to take 0 arguments, but it takes 1 argument”. Fix: thread::spawn(move || ...) and capture id from outside.

A function pointer parameter. If your function takes f: fn(i32) -> i32 (a plain function pointer, not a generic F: Fn), passing a two-argument add gives E0308 instead, with the label “incorrect number of function parameters”. Same fix: a wrapper closure like |x| add(x, 10). Closures that don’t capture anything can be passed as fn pointers.

Quick check

1. names.iter().enumerate().map(|i, name| ...) gives E0593. What is the fix?

2. How many parameters does the closure for scores.sort_by(...) need?

3. thread::spawn(|id| println!("{id}")) gives E0593. How do you get id into the thread?

Score: 0 / 3