Rust 错误 E0057:this function takes N arguments but M were supplied(闭包参数个数不对)
E0057 的意思是:你调用闭包时传的参数个数不对,要么太少,要么太多。它是 E0061 的闭包版本,报错信息几乎一模一样。修起来通常很快:看看闭包的 |...|,每个参数传一个值就行。迭代器里还常见两个近亲 E0593 和 E0631,它们来自同一种混淆,所以本页也一并讲到。这个系列的其他错误,请看 Rust 编译错误详解。
error[E0057]: this function takes 2 arguments but 1 argument was supplied --> src/main.rs:3:17 |3 | let total = add(1); // ❌ add wants two numbers | ^^^--- argument #2 of type `i32` is missing |note: closure defined here --> src/main.rs:2:15 |2 | let add = |a: i32, b: i32| a + b; | ^^^^^^^^^^^^^^^^help: provide the argument |3 | let total = add(1, /* i32 */); // ❌ add wants two numbers | +++++++++++第一行的意思是“这个函数接收 2 个参数,但只传了 1 个”。help 建议你“把缺的参数补上”。
产生这个错误的程序是这样的:
fn main() { let add = |a: i32, b: i32| a + b; let total = add(1); // ❌ add wants two numbers println!("{total}");}参数传多了,错误代码也一样,只是标注变成“unexpected argument”(多余的参数),help 变成“remove the extra argument”(去掉多余的参数):
error[E0057]: this function takes 1 argument but 2 arguments were supplied --> src/main.rs:3:5 |3 | greet("Ferris", "Corro"); | ^^^^^ ------- unexpected argument #2 of type `&'static str` |note: closure defined here这个错误是什么意思
Section titled “这个错误是什么意思”读法和函数的报错一样:第一行是在数个数(闭包要 2 个,你给了 1 个),调用下面的标注指出缺了或多了哪个参数,help: 告诉你该把它放在哪里。/* i32 */ 只是一个占位注释,告诉你这里要什么类型。把它换成一个真正的值。
是 E0057 还是 E0061? 两者的第一行一样,靠 note: 区分:
closure defined here:你调用的是闭包,这是 E0057。callable defined here:你调用的是一个带约束(比如F: Fn(i32))的泛型f,这也是 E0057。function defined here:普通的fn、方法或函数指针(let f: fn(i32, i32) -> i32 = add;),这是 E0061。
不管是哪个,修法都一样:让调用和参数列表对上。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”闭包的参数列表和函数的一样是固定的。|a: i32, b: i32| 每次都正好接收两个 i32。Rust 没有默认参数,也没有“想少传就少传”这回事。就算你省略了类型,rustc 也会根据闭包体和它第一次被调用的方式推断出来,从那以后就定死了。
在底层,闭包实现了某个 Fn trait,带有一组固定的参数类型,比如 Fn(i32, i32) -> i32。编译器会拿每一次调用和这组类型对照,所以参数个数不对的调用根本进不了运行中的程序。闭包和它的参数怎么工作,见闭包和闭包 trait。
修复 1:让调用和闭包的参数对上
Section titled “修复 1:让调用和闭包的参数对上”几乎每次都选这个。跳到 note: 指出的位置,数一数两根竖线 |...| 之间有几个参数,每个参数传一个值。如果你手上的值比参数多,就多调用几次:
fn main() { let add = |a: i32, b: i32| a + b; let greet = |name: &str| println!("你好,{name}!");
let total = add(1, 2); println!("{total}");
greet("Ferris"); greet("Corro"); // 每个名字调用一次}输出:
3你好,Ferris!你好,Corro!如果闭包本来就该接收别的个数,那就改闭包。这样也完全可以,只要每一处调用都和它一致。
修复 2:元组:一个元组就是一个参数
Section titled “修复 2:元组:一个元组就是一个参数”涉及元组时选这个。|(name, score)| 和 |name, score| 看起来很像,却是两个不同的闭包。前者接收一个参数,也就是一个元组,再把它解开;后者接收两个参数。
用两个值去调用接收元组的闭包,会得到 E0057,rustc 会建议你加上括号:
error[E0057]: function takes 1 argument but 2 arguments were supplied --> src/main.rs:3:5 |3 | show("Ferris", 90); | ^^^^ |note: closure parameter defined herehelp: wrap these arguments in parentheses to construct a tuple |3 | show(("Ferris", 90)); | + +help 的意思是“用括号把这些参数包起来,组成一个元组”。
反过来的错误在迭代器里天天见。zip、enumerate 和遍历 map 时,每个元素都会以一个元组的形式交给你的闭包。写成 .map(|name, score| ...) 会得到另一个错误代码 E0593:
error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments --> src/main.rs:7:10 |7 | .map(|name, score| format!("{name}: {score}")) | ^^^ ------------- 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 |7 - .map(|name, score| format!("{name}: {score}"))7 + .map(|(name, score)| format!("{name}: {score}"))它说的是:这里需要一个“接收单个二元组”的闭包,你的闭包却接收两个独立的参数。
两种情况修法一样:在元组所在的位置加上括号。
fn main() { let names = vec!["Ferris", "Corro"]; let scores = vec![90, 75];
// zip 每次给出一个 (name, score) 元组:在参数列表里把它解开 let lines: Vec<String> = names .iter() .zip(scores.iter()) .map(|(name, score)| format!("{name}: {score}")) .collect(); println!("{:?}", lines);
// 接收元组的闭包,要用一个元组来调用 let show = |(name, score): (&str, i32)| println!("{name} 得了 {score} 分"); show(("Ferris", 90));}输出:
["Ferris: 90", "Corro: 75"]Ferris 得了 90 分.enumerate().map(|(i, item)| ...) 和 map.iter().for_each(|(key, value)| ...) 也是同样的道理。参见迭代器和元组。
修复 3:包一层闭包来适配
Section titled “修复 3:包一层闭包来适配”当你把一个函数或闭包传给另一个函数(也就是“高阶函数”),而两边的参数个数对不上时,选这个。下面的 apply 想要一个只接收一个 i32 的东西,可 add 接收两个。这又会得到 E0593:
error[E0593]: function is expected to take 1 argument, but it takes 2 arguments --> src/main.rs:10:26 | 5 | fn add(a: i32, b: i32) -> i32 { | ----------------------------- takes 2 arguments...10 | println!("{}", apply(add, 5)); | ----- ^^^ expected function that takes 1 argument | | | required by a bound introduced by this call意思是:这里需要一个接收 1 个参数的函数,而 add 接收 2 个。
不用改 add。用一个小闭包把它包起来,让这个闭包的形状符合 apply 的要求,缺的那个值由它自己补上:
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x)}
fn add(a: i32, b: i32) -> i32 { a + b}
fn main() { let bonus = 10; // 接收 1 个参数,再用 2 个参数调用 add println!("{}", apply(|x| add(x, bonus), 5));
// 接收 1 个元组,再用它的两部分调用 add let pairs = vec![(1, 2), (3, 4)]; let sums: Vec<i32> = pairs.into_iter().map(|(a, b)| add(a, b)).collect(); println!("{:?}", sums);}输出:
15[3, 7]这个包装闭包从外面的代码里借用了 bonus,这正是闭包最擅长的事。直接按名字传函数(apply(double, 5))只有在它的参数本来就对得上时才行。参见闭包 trait。
在泛型函数里。 fn call_twice<F: Fn(i32)>(f: F) 里调用 f(2, 3),会得到 E0057,并带有指向 Fn(i32) 的 note: callable defined here。这个约束就是参数列表。修复:调用 f(2);如果真的需要两个值,就把约束改成 Fn(i32, i32)(这样每个调用方传入的闭包都必须接收两个参数)。
装箱的闭包。 写了 let f: Box<dyn Fn(i32, i32) -> i32> = Box::new(|a, b| a + b);,再调用 f(1),会得到 E0057,并带有 note: implementation defined here。修复:两个值都传进去,f(1, 2)。
排序。 v.sort_by(|a| a.cmp(a)) 会得到 E0593:“closure is expected to take 2 arguments, but it takes 1 argument”(闭包应该接收 2 个参数,它却只接收 1 个)。sort_by 每次比较两个元素。修复:v.sort_by(|a, b| a.cmp(b))。参见排序。
个数对了,类型错了。 nums.iter().map(|x: i32| x * 2) 会得到 E0631:“type mismatch in closure arguments”(闭包参数类型不匹配),因为 .iter() 给出的是 &i32。rustc 的 help 会建议改成 |x: &i32|。最简单的修法:去掉类型注解,写成 |x| x * 2。
解开的东西根本不是元组。 在普通的 Vec<i32> 上写 v.iter().map(|(a, b)| a + b),会得到 E0308:“expected integer, found (_, _)”(需要整数,却找到了元组)。修复:只有元素真的是成对的值时,才用 |(a, b)|。
- E0061:参数个数不对:普通函数、方法和函数指针的同一种报错。
- E0308:类型不匹配:个数对了,但某个参数(或元组模式)的类型不对。
- E0373:闭包可能比当前函数活得更久:另一个常见的闭包错误,和线程里、被返回的闭包所捕获的变量有关。
- E0599:找不到方法:可能和 E0631 一起出现,因为签名不对的闭包会让整条迭代器链都出问题。
- E0593(传入的闭包或函数参数个数不对)和 E0631(闭包参数类型不匹配):本页提到的两个近亲。
- 所有编译错误:错误页面的完整列表。
小测验
1. let double = |x: i32| x * 2; 被这样调用:double(2, 3)。rustc 会报什么?
2. names.iter().zip(ages.iter()).map(|n, a| ...) 编译不过。该怎么修?
3. E0057 和 E0061 都会说 this function takes 2 arguments but 1 argument was supplied。怎么看出你调用的是闭包?
得分: 0 / 3
- E0057 — Rust error codes index(英文):这个错误的官方解释。
- 闭包:可以捕获其环境的匿名函数 — Rust 程序设计语言:闭包语法、参数的类型推断,以及
Fn系列 trait。 - 闭包 — 通过例子学 Rust:简短可运行的闭包示例,包括把闭包传给函数。
std::ops::Fn— 标准库文档(英文):Fn(i32) -> i32这样的约束怎样描述闭包的参数。- E0593 — Rust error codes index(英文):“传入的闭包参数个数不对”这个错误的官方解释。