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”。每次都是同一个错误。
这个错误是什么意思
Section titled “这个错误是什么意思”这条信息分三部分,每一部分都有用:
- 第一行在数数:函数要 2 个参数,你给了 1 个。
- 你调用处下面的标注点出了问题:“argument #2 of type
&stris missing”(缺少第 2 个参数,类型是&str)。 note:跳到函数的定义,并在你漏掉的参数greeting: &str下面画了线。
help: 这一行显示参数该放在哪里。/* &str */ 只是一个告诉你类型的占位符;它是注释,原样粘贴进去是编译不过的。在那里放一个真正的值,比如 "Hello"。
这就像一台咖啡机,既要放杯子又要投币。只投币不会给你半杯咖啡,它根本就不会启动。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”在 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! 这样确实能接收任意数量值的东西是宏,! 就是这个意思。见 宏。)
参数的用法在 函数 一课里有讲解。
修复 1:签名要什么就传什么
Section titled “修复 1:签名要什么就传什么”几乎每次都该选这个办法。打开 note: 指向的位置,看看参数列表,按顺序每个参数传一个值:
fn greet(name: &str, greeting: &str) { println!("{greeting},{name}!");}
fn main() { greet("Ferris", "你好"); greet("Corro", "早上好");}输出:
你好,Ferris!早上好,Corro!如果你刚改了函数,结果每处调用都坏了,那是编译器在帮你:把它列出的每处调用都改好,或者想一想,这个新参数是不是应该是可选的(修复 3)。
修复 2:用点号调用方法
Section titled “修复 2:用点号调用方法”如果函数有 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
- E0061 — Rust error codes index(英文):这个错误的官方说明。
- 函数 —《Rust 程序设计语言》:参数、签名里的类型,以及函数调用是怎么工作的。
- 方法语法 —《Rust 程序设计语言》:为什么
self由点号填上,以及new这样的关联函数。 Defaulttrait — 标准库文档(英文):deriveDefault和使用..Default::default()。- Builder pattern — Rust Design Patterns(英文):替代长参数列表和可选参数的常用办法。