跳转到内容

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

读法和函数的报错一样:第一行是在数个数(闭包要 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。

不管是哪个,修法都一样:让调用和参数列表对上。

闭包的参数列表和函数的一样是固定的。|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 here
help: 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)| ...) 也是同样的道理。参见迭代器和元组。

当你把一个函数或闭包传给另一个函数(也就是“高阶函数”),而两边的参数个数对不上时,选这个。下面的 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)|。

小测验

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