跳转到内容

Rust 错误 E0061:this function takes N arguments but M were supplied(参数个数不对)

E0061 的意思是:你调用函数时传的参数个数不对,要么太少,要么太多。这是 Rust 里最友好的错误之一,因为 rustc 会准确告诉你缺了哪个参数、多了哪个参数,以及它应该是什么类型。从 Python、JavaScript 或 C# 转过来的人经常遇到它,因为在那些语言里,默认参数和重载都很常见。Rust 两者都没有,这一页会告诉你该怎么做。这个系列里的其他错误,见 Rust 编译错误详解。

error[E0061]: this function takes 2 arguments but 1 argument was supplied
--> src/main.rs:6:5
|
6 | greet("Ferris"); // ❌ greet wants two arguments
| ^^^^^---------- argument #2 of type `&str` is missing
|
note: function defined here
--> src/main.rs:1:4
|
1 | fn greet(name: &str, greeting: &str) {
| ^^^^^ --------------
help: provide the argument
|
6 | greet("Ferris", /* &str */); // ❌ greet wants two arguments
| ++++++++++++

下面这段程序会产生这个错误:

fn greet(name: &str, greeting: &str) {
println!("{greeting}, {name}!");
}
fn main() {
greet("Ferris"); // ❌ greet wants two arguments
}

(注释保留英文,因为 rustc 会把这一行原样打印在错误信息里,意思是“greet 需要两个参数”。)

参数太多时也是同一个错误代码,标注会变成 “unexpected argument”(多余的参数),help 会变成 “remove the extra argument”(删掉多余的参数):

error[E0061]: this function takes 2 arguments but 3 arguments were supplied
--> src/main.rs:6:13
|
6 | let a = area(3, 4, 5);
| ^^^^ - unexpected argument #3 of type `{integer}`

如果是用点号调用的方法,第一行会写 “this method takes…”。如果是 Rgb(255, 0) 这样的元组结构体,它会写 “this struct takes 3 arguments”。每次都是同一个错误。

这条信息分三部分,每一部分都有用:

  1. 第一行在数数:函数要 2 个参数,你给了 1 个。
  2. 你调用处下面的标注点出了问题:“argument #2 of type &str is missing”(缺少第 2 个参数,类型是 &str)。
  3. note: 跳到函数的定义,并在你漏掉的参数 greeting: &str 下面画了线。

help: 这一行显示参数该放在哪里。/* &str */ 只是一个告诉你类型的占位符;它是注释,原样粘贴进去是编译不过的。在那里放一个真正的值,比如 "Hello"。

这就像一台咖啡机,既要放杯子又要投币。只投币不会给你半杯咖啡,它根本就不会启动。

在 Rust 里,函数签名是一份精确的契约:就是这些参数,按这个顺序,是这些类型。每次调用都必须和它一致。Rust 没有默认参数(greeting = "Hello"),没有重载(两个都叫 greet、参数不同的函数),也没有能接收“任意数量”参数的函数。想写默认值会得到 error: parameter defaults are not supported(不支持参数默认值),再写一个 greet 会得到 E0428,“the name greet is defined multiple times”(greet 这个名字被定义了多次)。

听起来很严格,但这是值得的。读到 greet(name, greeting) 时,你能确切知道运行的是哪个函数、它收到了什么。给函数加一个参数时,编译器会列出所有需要更新的调用,一个都不会漏掉。(像 println! 这样确实能接收任意数量值的东西是宏,! 就是这个意思。见 宏。)

参数的用法在 函数 一课里有讲解。

几乎每次都该选这个办法。打开 note: 指向的位置,看看参数列表,按顺序每个参数传一个值:

fn greet(name: &str, greeting: &str) {
println!("{greeting},{name}!");
}
fn main() {
greet("Ferris", "你好");
greet("Corro", "早上好");
}

输出:

你好,Ferris!
早上好,Corro!
▶ 动手试一试

如果你刚改了函数,结果每处调用都坏了,那是编译器在帮你:把它列出的每处调用都改好,或者想一想,这个新参数是不是应该是可选的(修复 3)。

如果函数有 self 参数,就选这个办法。方法通常写成 value.method(args) 来调用。点号前面的值会填进 self,所以不算在参数个数里。如果你改写成 Type::method(args),就没有东西去填 self 了:

error[E0061]: this function takes 2 arguments but 1 argument was supplied
--> src/main.rs:13:5
|
13 | Account::deposit(50);
| ^^^^^^^^^^^^^^^^ -- argument #1 of type `&mut Account` is missing
|
help: provide the argument
|
13 | Account::deposit(/* &mut Account */, 50);
| +++++++++++++++++++

注意它是怎么数的。写成 Account::deposit(...) 时,rustc 说这个函数要 2 个参数,因为 &mut self 也算一个。写成 acc.deposit() 时,它说这个方法要 1 个参数,因为点号已经提供了 self。只要每个参数都给齐了,两种写法都能用:

struct Account {
balance: i64,
}
impl Account {
fn deposit(&mut self, amount: i64) {
self.balance += amount;
}
}
fn main() {
let mut acc = Account { balance: 0 };
acc.deposit(50); // 平常的写法
Account::deposit(&mut acc, 25); // 同一个调用,完整写出来
println!("余额:{}", acc.balance);
}

输出:

余额:75
▶ 动手试一试

日常代码里用点号的写法。需要把方法本身传到别处时,Type::method(&x, a) 就很有用,比如 team.iter().map(Player::label)(示例)。如果函数根本没有 self(比如 new),就只能写成 Type::function(...) 来调用。见 结构体 和 E0424。

修复 3:有时用不到的参数,用 Option<T>

Section titled “修复 3:有时用不到的参数,用 Option<T>”

如果某个参数通常有一个合理的默认值,就选这个办法。把它改成 Option,传 None 表示“用默认值”,再用 unwrap_or 补上:

fn connect(host: &str, port: u16, timeout_secs: Option<u64>) {
let timeout = timeout_secs.unwrap_or(30);
println!("正在连接 {host}:{port}(超时 {timeout} 秒)");
}
fn main() {
connect("localhost", 8080, None);
connect("example.com", 443, Some(5));
}

输出:

正在连接 localhost:8080(超时 30 秒)
正在连接 example.com:443(超时 5 秒)
▶ 动手试一试

说实话:调用的人还是得写上这个参数,也就是 None。Rust 更喜欢这样,因为调用处能看出这里用了默认值。如果你连写都不想写,那就改成两个名字清楚的函数,比如 connect(host, port) 内部调用 connect_with_timeout(host, port, 30)。这是 Rust 对重载的常见答案。见 Option。

修复 4:用实现了 Default 的配置结构体

Section titled “修复 4:用实现了 Default 的配置结构体”

如果一个函数有很多设置项,而大多数调用者只改其中一两个,就选这个办法。把设置放进一个结构体,实现 Default,再让调用者用 ..Default::default() 只填自己需要的部分:

struct ServerConfig {
host: String,
port: u16,
timeout_secs: u64,
verbose: bool,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: String::from("localhost"),
port: 8080,
timeout_secs: 30,
verbose: false,
}
}
}
fn start(config: ServerConfig) {
println!(
"正在监听 {}:{}(超时 {} 秒,verbose:{})",
config.host, config.port, config.timeout_secs, config.verbose
);
}
fn main() {
start(ServerConfig::default());
start(ServerConfig { port: 3000, verbose: true, ..Default::default() });
}

输出:

正在监听 localhost:8080(超时 30 秒,verbose:false)
正在监听 localhost:3000(超时 30 秒,verbose:true)
▶ 动手试一试

这样还能得到类似命名参数的效果:port: 3000 说明了这个数字是什么意思。如果每个字段的默认值都是该类型自己的默认值(0、false、空字符串),#[derive(Default)] 会帮你写好这个 impl。对于更大的 API,再往上一步就是建造者模式(builder pattern,Server::builder().port(3000).build())。见 结构体 和 设计模式。

加了一个参数之后。 你给 fn save(path: &str) 加了第二个参数,结果所有旧的 save("notes.txt") 调用都报 E0061。修复:把编译器列出的每处调用都改好;如果新值通常都一样,就用修复 3 或修复 4。

标准库的方法。 names.push("Cy", "Di") 会报 “this method takes 1 argument but 2 arguments were supplied”。push 一次只加一个元素。修复:调用两次,或者用 names.extend(["Cy", "Di"])。见 vector。

元组结构体。 定义了 struct Rgb(u8, u8, u8); 之后写 Rgb(255, 0),会报 “this struct takes 3 arguments but 2 arguments were supplied”。修复:每个字段都给值,Rgb(255, 0, 0)。

其他语言带来的习惯。 Python 的 greet("Ferris", greeting="Hi") 不是 Rust 语法。写 greet(greeting = "Hi") 并不会给参数命名;rustc 会把它当成给一个叫 greeting 的变量赋值,还会再报一个 E0425。修复:按位置传值,或者用配置结构体(修复 4)。

闭包。 如果 add 是 |a: i32, b: i32| a + b,调用 add(1) 会得到同样的信息,但错误代码是 E0057(闭包版本)。修复方法一样:两个值都传。

  • E0308:参数个数对了,但有一个类型不对,常常是因为两个参数写反了。
  • E0424:在没有 self 参数的函数里用了 self,是“方法与关联函数”问题的另一面。
  • E0599:不存在这个名字的方法,比如用点号调用 new 这样的关联函数。
  • E0425:找不到某个值,你尝试写命名参数时,它可能会和 E0061 一起出现。
  • 所有编译错误:全部错误页面的列表。相关的还有 E0057(调用闭包时参数个数不对),以及 E0428(想做重载时把一个函数定义了两次),后者没有单独的页面。

小测验

1. fn area(w: u32, h: u32) -> u32 被写成 area(5) 来调用。rustc 会报什么?

2. 方法是 fn deposit(&mut self, amount: i64)。哪个调用是对的?

3. 你想要一个 timeout 参数,但大多数调用者并不关心它。Rust 里常见的做法是什么?

得分: 0 / 3