コンテンツにスキップ

Rustのエラー E0057: this function takes N arguments but M were supplied(クロージャ)

E0057 は、クロージャを間違った数の引数で呼んだ、という意味です。少なすぎても多すぎても出ます。E0061 のクロージャ版で、メッセージもほとんど同じです。直し方はたいてい簡単で、クロージャの |...| を見て、引数1つにつき値を1つ渡すだけです。このページでは、イテレータでよく出会う近い親戚 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 は数を2つほしがっている
| ^^^--- 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 は数を2つほしがっている
| +++++++++++

「この関数は引数を2つ取りますが、1つしか渡されていません。i32 型の2番目の引数がありません」という意味です。note: はクロージャが定義された場所を、help: は引数を足す場所を示しています。

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

fn main() {
let add = |a: i32, b: i32| a + b;
let total = add(1); // ❌ add は数を2つほしがっている
println!("{total}");
}

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

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

メッセージは、関数のときと同じように読めます。1行目は数を数えていて(クロージャは2つ取るのに、あなたは1つ渡した)、呼び出しの下のラベルは足りない引数や余分な引数を名指しし、help: はそれをどこに置けばいいかを示しています。/* i32 */ は型を教えてくれているだけのプレースホルダーのコメントです。そこには本物の値を入れましょう。

E0057 と E0061 の見分け方は? 1行目は同じです。違いは 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| は、いつでもちょうど2つの i32 を受け取ります。デフォルト引数もありませんし、「少なめに渡してもいいよ」ということもありません。型を省略した場合でも、rustc はクロージャの本体と最初の使われ方から型を推論し、それ以降はその型に固定されます。

内部では、クロージャは Fn(i32, i32) -> i32 のような、決まった引数の型のリストを持つ Fn 系トレイトのどれかを実装しています。コンパイラはすべての呼び出しをそのリストと照らし合わせるので、引数の数が違う呼び出しが実行中のプログラムにたどり着くことはありません。クロージャとその引数のしくみは、クロージャとクロージャのトレイトのページで説明しています。

直し方1:呼び出しをクロージャの引数に合わせる

Section titled “直し方1:呼び出しをクロージャの引数に合わせる”

ほとんどの場合はこれを選びましょう。note: が示す場所に行き、パイプ |...| の間にある引数を数えて、それぞれに値を1つずつ渡します。引数より値のほうが多いなら、クロージャを複数回呼びましょう。

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"); // 名前1つにつき1回呼ぶ
}

実行結果:

3
こんにちは、Ferrisさん!
こんにちは、Corroさん!
▶ 実際に試してみよう

クロージャが本当は別の数の値を受け取るべきなら、クロージャのほうを変えましょう。それでもかまいません。すべての呼び出しがクロージャと合っていれば大丈夫です。

直し方2:タプルに注意。タプル1つは引数1つ

Section titled “直し方2:タプルに注意。タプル1つは引数1つ”

タプルが関わっているときはこれを選びましょう。|(name, score)| と |name, score| は似ていますが、まったく別のクロージャです。前者はタプルという1つの引数を受け取って、それを分解します。後者は2つの引数を受け取ります。

タプルを受け取るクロージャに値を2つ渡すと 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、マップの繰り返し処理は、クロージャに要素1つにつきタプル1つを渡します。.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}"))

「クロージャは2要素のタプルを1つ受け取るはずなのに、別々の引数を2つ受け取っています」という意味です。

どちらも直し方は同じです。タプルがあるところに丸かっこを付けましょう。

fn main() {
let names = vec!["Ferris", "Corro"];
let scores = vec![90, 75];
// zip は要素ごとに (name, score) のタプルを1つ渡すので、引数リストで分解する
let lines: Vec<String> = names
.iter()
.zip(scores.iter())
.map(|(name, score)| format!("{name}: {score}"))
.collect();
println!("{:?}", lines);
// タプルを受け取るクロージャは、タプル1つで呼ぶ
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 を1つ受け取るものを求めていますが、add は2つ受け取ります。これも 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つ取る関数が期待されているのに、この関数は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つ受け取り、add を2つの引数で呼ぶ
println!("{}", apply(|x| add(x, bonus), 5));
// タプルを1つ受け取り、その2つの部分で 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))のは、その引数がすでに合っているときだけ使えます。クロージャのトレイトのページも参考にしてください。

ジェネリック関数の中。 fn call_twice<F: Fn(i32)>(f: F) の中で f(2, 3) と呼ぶと、Fn(i32) を指す note: callable defined here 付きで E0057 になります。トレイト境界がそのまま引数のリストなのです。直し方:f(2) と呼ぶか、本当に値が2つ必要なら境界を Fn(i32, i32) に変えます(その場合は、呼び出し側が渡すクロージャもすべて2つ受け取る必要があります)。

Box に入れたクロージャ。 let f: Box<dyn Fn(i32, i32) -> i32> = Box::new(|a, b| a + b); のあとで f(1) と呼ぶと、note: implementation defined here 付きで E0057 になります。直し方:両方の値を渡して 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 は要素を2つずつ比べるからです。直し方: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