Rust 错误 E0593:closure is expected to take a single 2-tuple as argument(闭包参数形式不对)
E0593 的意思是:你把一个闭包(或函数)交给了别的东西,比如 map、sort_by 或 thread::spawn,可它的参数个数和对方调用它的方式对不上。目前最常见的情况是:迭代器给你的闭包一个元组,你却写了两个参数。大多数时候只要改一个字符,而且 rustc 通常会替你写好修法。如果是你自己调用闭包时传的值个数不对,那就是 E0057。这个系列的其他错误,请看 Rust 编译错误详解。
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 parameters6 + .map(|(i, name)| format!("{}. {name}", i + 1)) // ❌ two parameters |第一行的意思是“闭包应该接收一个二元组作为参数,它却接收了 2 个独立的参数”。help 建议你“把闭包改成接收一个元组,而不是几个单独的参数”。
产生这个错误的程序是这样的:
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 还有另外几种第一行。它们说的都是“参数个数不对”:
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 argument依次是:闭包应该接收 2 个参数却只接收 1 个;应该接收 0 个却接收了 1 个;函数应该接收 1 个却接收了 2 个;应该接收 2 个独立的参数,却接收了一个二元组。
这个错误是什么意思
Section titled “这个错误是什么意思”看第 6 行下面的两个标记:
^^^指向map。它是调用你的闭包的那一方,由它决定传几个值。---------指向你的参数列表|i, name|。这是你承诺要接收的东西。
两边对不上。enumerate() 把每个元素变成一个元组 (index, item),map 再把这个元组作为一个参数传给你的闭包。而你的闭包要的是两个单独的参数。
可以想象一个一次只能塞进一个信封的投信口。信封里可以装两封信(元组),但投信口不收两封散着的信。|(i, name)| 的意思是:“把信封给我,我当场拆开”。
信息里的“closure”或“function”告诉你传进去的是什么:是就地写的闭包,还是像 .map(add) 这样有名字的 fn。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”每个接收闭包的方法都写清楚了它会怎样调用这个闭包。map 要的是 FnMut(Self::Item) -> B 这样的东西:一个参数,就是元素本身。对于 enumerate(),元素是 (usize, &&str),它是一个值,只不过恰好是个元组。sort_by 要的是 FnMut(&T, &T) -> Ordering:两个参数。thread::spawn 要的是 FnOnce() -> T:一个参数都没有。
Rust 从不替你把元组拆成几个单独的参数,也从不把几个单独的参数打包成元组。这样规则就很简单:方法签名写的是什么,你的闭包收到的就是什么。等你能看懂这些签名,就能预料到任何迭代器方法会交给你什么。
这背后的 Fn 系列 trait,详见闭包 trait;元组的相关内容见解构元组。
修复 1:在参数列表里解开元组
Section titled “修复 1:在参数列表里解开元组”当信息里写着“a single 2-tuple”(一个二元组)时,选这个。这是你最常用到的修法。加上括号:|(i, name)| 接收一个元组,并给它的两个部分起名字。它适用于 enumerate、zip 和遍历 map:
use std::collections::BTreeMap;
fn main() { let names = vec!["Ferris", "Corro", "Rusty"];
// enumerate 为每个元素给出一个 (index, name) 元组 let lines: Vec<String> = names .iter() .enumerate() .map(|(i, name)| format!("{}. {name}", i + 1)) .collect(); println!("{:?}", lines);
// 遍历 map 时,每个元素也是一个 (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} 个水果");}输出:
["1. Ferris", "2. Corro", "3. Rusty"]一共 8 个水果_name 开头的 _ 是在告诉 Rust:“我知道我没用它”。你也可以只写一个 _。zip 的同类写法见 E0057 页面。
修复 2:按方法调用时传的个数来写参数
Section titled “修复 2:按方法调用时传的个数来写参数”当信息里写着“expected to take N arguments”(应该接收 N 个参数)时,选这个。先查清方法会传什么,再写上正好那么多的参数:
sort_by比较两个元素:|a, b|。for_each、map、filter传一个元素:|item|,不需要它时写|_|。thread::spawn什么都不传:||,通常写成move ||。
use std::thread;
fn main() { // sort_by 每次比较两个元素:两个参数 let mut scores = vec![42, 7, 99, 15]; scores.sort_by(|a, b| b.cmp(a)); println!("{:?}", scores);
// for_each 每次交出一个元素:一个参数(不需要时用 _) let tasks = vec!["write", "test", "ship"]; tasks.iter().for_each(|task| println!("待办:{task}")); tasks.iter().for_each(|_| print!("*")); println!();
// thread::spawn 调用闭包时什么都不传:零个参数 let id = 1; let handle = thread::spawn(move || format!("{id} 号工作线程完成了")); println!("{}", handle.join().unwrap());}输出:
[99, 42, 15, 7]待办:write待办:test待办:ship***1 号工作线程完成了想把数据传进线程,不要加参数,而是像上面的 id 那样用 move 捕获它。参见并发和排序。
修复 3:包装有名字的函数,或者让它接收元组
Section titled “修复 3:包装有名字的函数,或者让它接收元组”当信息里写着“function is expected to take…”(函数应该接收……)时,选这个。你按名字传了一个函数,比如 .map(add),而它的参数对不上。下面的元素是一对一对的值,add 却接收两个数字:
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你可以用一个小闭包把函数包起来,由它解开元组再调用函数;也可以写一个直接接收元组的版本:
fn add(a: i32, b: i32) -> i32 { a + b}
// 把一对值当作一个元组来接收的版本fn add_pair((a, b): (i32, i32)) -> i32 { a + b}
fn main() { let pairs = vec![(1, 2), (3, 4), (10, 20)];
// 包一层:进来一个元组,出去两个参数 let sums: Vec<i32> = pairs.iter().map(|&(a, b)| add(a, b)).collect(); println!("{:?}", sums);
// 或者传一个本来就接收一个元组的函数 let sums2: Vec<i32> = pairs.into_iter().map(add_pair).collect(); println!("{:?}", sums2);}输出:
[3, 7, 30][3, 7, 30]|&(a, b)| 里的 & 是因为 .iter() 以 &(i32, i32) 的形式借出每一对值,这个模式把两个数字复制出来。通常包一层是更好的选择:add 仍然是个普通的双参数函数,代码的其他地方照样能调用它。
如果你自己写的泛型函数要求 Fn(i32) -> i32,而你传进去的是双参数的 add,就会得到“function is expected to take 1 argument, but it takes 2 arguments”(函数应该接收 1 个参数,它却接收 2 个)。修法是包一层闭包,把多出来的那个值补上,见 E0057 的修复 3。
HashMap::retain(正好反过来)。 大多数 map 方法给你的是元组,但 retain 会把键和值作为两个单独的参数传入。所以 stock.retain(|(name, count)| *count > 0) 会得到“closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument”(闭包应该接收 2 个独立的参数,它却接收一个二元组)。修复:去掉括号,stock.retain(|_name, count| *count > 0)。Vec::retain 则和平常一样,接收一个元素。
忘了写参数。 tasks.iter().for_each(|| println!("task")) 会得到“closure is expected to take 1 argument, but it takes 0 arguments”(闭包应该接收 1 个参数,它却接收 0 个),rustc 会建议写成 |_|。修复:写 |_|,如果要用这个元素就写 |task|。
线程。 thread::spawn(|id| ...) 会得到“closure is expected to take 0 arguments, but it takes 1 argument”(闭包应该接收 0 个参数,它却接收 1 个)。修复:写成 thread::spawn(move || ...),从外面捕获 id。
函数指针类型的参数。 如果你的函数接收的是 f: fn(i32) -> i32(普通的函数指针,而不是泛型 F: Fn),传入双参数的 add 得到的会是 E0308,标注为“incorrect number of function parameters”(函数参数个数不对)。修法一样:包一层闭包,比如 |x| add(x, 10)。不捕获任何变量的闭包可以当作 fn 指针传入。
- E0057:闭包接收 N 个参数,却传了 M 个:你自己调用闭包时传的值个数不对。
- E0631:闭包参数类型不匹配:个数对了,但某个参数类型不对,常见的是迭代器给的是
&i32,你写的却是i32。 - E0061:函数接收 N 个参数,却传了 M 个:调用普通函数时参数个数不对。
- E0308:类型不匹配:参数是
fn(...)指针时会得到它;元素根本不是元组却写了|(a, b)|,也会得到它。 - E0599:找不到方法:有时紧跟在 E0593 后面出现,比如“the method
collectexists … but its trait bounds were not satisfied”(方法collect存在,但不满足它的 trait 约束)。先修好 E0593,它就会消失。 - 所有编译错误:错误页面的完整列表。
小测验
1. names.iter().enumerate().map(|i, name| ...) 报了 E0593。该怎么修?
2. scores.sort_by(...) 的闭包需要几个参数?
3. thread::spawn(|id| println!("{id}")) 报了 E0593。怎样把 id 传进线程?
得分: 0 / 3
- E0593 — Rust error codes index(英文):这个错误的官方解释。
- 闭包:可以捕获其环境的匿名函数 — Rust 程序设计语言:闭包语法、参数类型怎样推断,以及
Fn系列 trait。 - 作为输入参数 — 通过例子学 Rust:函数的
Fn约束怎样决定它接受什么样的闭包。 Iterator::enumerate— 标准库文档(英文):说明每个元素都会变成一个(index, value)元组。HashMap::retain— 标准库文档(英文):闭包把键和值作为两个参数接收的 map 方法。