コンテンツにスキップ

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 は引数を2つほしがっている
| ^^^^^---------- 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 は引数を2つほしがっている
| ++++++++++++

「この関数は引数を2つ取りますが、1つしか渡されていません。&str 型の2番目の引数がありません」という意味です。

このエラーが出るプログラムはこちらです。

fn greet(name: &str, greeting: &str) {
println!("{greeting}, {name}!");
}
fn main() {
greet("Ferris"); // ❌ greet は引数を2つほしがっている
}

引数が多すぎる場合も同じコードになり、“unexpected argument”(予期しない引数)というラベルと、“remove the extra argument”(余分な引数を消して)という help が付きます。

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}`

ドットで呼ぶメソッドの場合、1行目は “this method takes…”(このメソッドは〜を取ります)になります。Rgb(255, 0) のようなタプル構造体の場合は “this struct takes 3 arguments”(この構造体は引数を3つ取ります)です。どれも同じエラーです。

メッセージは3つの部分に分かれていて、どれも役に立ちます。

  1. 1行目は数を数えています。関数は引数を2つほしがっているのに、あなたは1つしか渡していません。
  2. 呼び出しの下のラベルは、問題を名指ししています。“argument #2 of type &str is missing”(&str 型の2番目の引数がない)です。
  3. note: は関数の定義に飛んで、渡し忘れた引数 greeting: &str に下線を引いています。

help: の行は、引数をどこに置けばいいかを示しています。/* &str */ は型を教えてくれているだけのプレースホルダーです。これはコメントなので、そのまま貼り付けてもコンパイルできません。"Hello" のような本物の値を入れましょう。

カップとコインの両方が必要なコーヒーマシンのようなものです。コインだけ入れても、半分のコーヒーが出てくるわけではありません。そもそも動かないのです。

Rust では、関数のシグネチャは厳密な契約です。この引数を、この順番で、この型で。すべての呼び出しは、それにぴったり合っていなければなりません。デフォルト引数(greeting = "Hello")も、オーバーロード(引数の違う greet という名前の関数を2つ作ること)も、「いくつでも」引数を受け取れる関数もありません。デフォルト値を書こうとすると error: parameter defaults are not supported(引数のデフォルト値はサポートされていない)になり、2つ目の greet を作ると E0428 “the name greet is defined multiple times”(greet という名前が複数回定義されている)になります。

厳しく聞こえますが、ちゃんと見返りがあります。greet(name, greeting) を読めば、どの関数が実行され、何を受け取るのかがはっきりわかります。関数に引数を追加すると、更新が必要な呼び出しをコンパイラがすべて挙げてくれるので、直し忘れがありません。(println! のように値をいくつでも受け取れるものはマクロです。! はそういう意味です。マクロのページを見てください。)

引数のしくみは関数のページで説明しています。

直し方1:シグネチャが求めるとおりに渡す

Section titled “直し方1:シグネチャが求めるとおりに渡す”

ほとんどの場合はこれを選びましょう。note: が示す場所を開き、引数のリストを読んで、引数1つにつき値を1つ、順番どおりに渡します。

fn greet(name: &str, greeting: &str) {
println!("{greeting}, {name}!");
}
fn main() {
greet("Ferris", "Hello");
greet("Corro", "Good morning");
}

実行結果:

Hello, Ferris!
Good morning, Corro!
▶ 実際に試してみよう

関数を変えたばかりで、呼び出しが全部壊れてしまった? それはコンパイラが手伝ってくれているのです。挙げられた呼び出しを1つずつ直すか、新しい引数は本当は省略できるべきではないか考えてみましょう(直し方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);
| +++++++++++++++++++

「&mut Account 型の1番目の引数がない」と言われています。

数え方に注目してください。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) のようにメソッドそのものをどこかに渡したいときに便利です(例)。new のように self がまったくない関数は、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 ではむしろそのほうが好まれます。デフォルト値が使われていることが、呼び出しを見ればわかるからです。それすら書きたくないなら、わかりやすい名前の関数を2つ作りましょう。たとえば connect(host, port) が connect_with_timeout(host, port, 30) を呼ぶ、という形です。これが、オーバーロードの代わりとして Rust でよく使われる方法です。Option のページも参考にしてください。

直し方4:Default を実装した設定用の構造体を使う

Section titled “直し方4:Default を実装した設定用の構造体を使う”

関数に設定項目がたくさんあって、ほとんどの呼び出し側はそのうち1つか2つしか変えない、というときはこれを選びましょう。設定を構造体にまとめて 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 なら、次のステップはビルダーパターン(Server::builder().port(3000).build())です。構造体とデザインパターンのページも参考にしてください。

引数を追加したあと。 fn save(path: &str) に2つ目の引数を加えると、これまでの save("notes.txt") という呼び出しがすべて E0061 で壊れます。直し方:コンパイラが挙げた呼び出しをそれぞれ直すか、新しい値がたいてい同じなら直し方3か直し方4を使います。

標準ライブラリのメソッド。 names.push("Cy", "Di") は “this method takes 1 argument but 2 arguments were supplied”(このメソッドは引数を1つ取るのに2つ渡された)になります。push が追加するのは1つだけです。直し方:2回呼ぶか、names.extend(["Cy", "Di"]) を使います。ベクタのページも参考にしてください。

タプル構造体。 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:引数の数は合っているけれど、1つの型が違うときに出ます。2つの引数を入れ違えたのが原因のこともよくあります。
  • E0424:self 引数のない関数で self を使ったときに出ます。メソッドと関連関数の、もう一方の側の問題です。
  • E0599:その名前のメソッドがないときに出ます。たとえば、new のような関連関数をドットで呼んだときです。
  • E0425:値が見つからないときに出ます。名前付き引数を使おうとしたとき、E0061 と一緒に出ることがあります。
  • コンパイルエラー解説の一覧:エラーのページをすべて並べています。E0057(クロージャを間違った数の引数で呼んだ)と、専用のページはありませんが E0428(オーバーロードしようとして関数を2回定義した)も関連しています。

理解度チェック

1. fn area(w: u32, h: u32) -> u32 を area(5) と呼びました。rustc は何と報告するでしょう?

2. メソッドが fn deposit(&mut self, amount: i64) です。正しい呼び出しはどれでしょう?

3. ほとんどの呼び出し側が気にしない timeout 引数がほしいです。Rust でよく使われる方法はどれでしょう?

スコア: 0 / 3