コンテンツにスキップ

Rustのエラー E0593: closure is expected to take a single 2-tuple as argument(引数の数)

E0593 は、map、sort_by、thread::spawn などにクロージャ(または関数)を渡したときに、その引数の数が、呼ばれるときの数と合っていない、という意味のエラーです。いちばんよくあるのは、イテレータがクロージャにタプルを1つ渡してくるのに、引数を2つ書いてしまったケースです。ほとんどの場合はカッコを足すだけで直りますし、rustc がたいてい直し方まで書いてくれます。クロージャを呼ぶ側で値の数を間違えたときは、E0057 のほうになります。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments
--> src/main.rs:6:10
|
6 | .map(|i, name| format!("{}. {name}", i + 1)) // ❌ 引数が2つ
| ^^^ --------- 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
|
6 - .map(|i, name| format!("{}. {name}", i + 1)) // ❌ 引数が2つ
6 + .map(|(i, name)| format!("{}. {name}", i + 1)) // ❌ 引数が2つ
|

「クロージャは2要素のタプルを1つ受け取るはずなのに、別々の引数を2つ受け取っている」という意味です。help は「別々の引数ではなく、タプルを受け取るようにクロージャを変えてはどうか」と提案し、- の行を + の行に書き換える案を見せています。

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

fn main() {
let names = vec!["Ferris", "Corro", "Rusty"];
let lines: Vec<String> = names
.iter()
.enumerate()
.map(|i, name| format!("{}. {name}", i + 1)) // ❌ 引数が2つ
.collect();
println!("{:?}", lines);
}

E0593 には、1行目が少し違うものもあります。どれも「引数の数が合わない」という意味です。

error[E0593]: closure is expected to take 2 arguments, but it takes 1 argument
error[E0593]: closure is expected to take 0 arguments, but it takes 1 argument
error[E0593]: function is expected to take 1 argument, but it takes 2 arguments
error[E0593]: closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument

上から順に「クロージャは引数を2つ取るはずなのに1つ」「0個のはずなのに1つ」「関数は1つのはずなのに2つ」「別々の引数を2つ取るはずなのに、2要素のタプルを1つ」という意味です。

6行目の下にある2つの印を見てください。

  • ^^^ は map を指しています。クロージャを呼ぶのは map で、いくつ値を渡すかを決めるのも map です。
  • --------- は、あなたが書いた引数のリスト |i, name| を指しています。これは、あなたが「これだけ受け取ります」と約束した数です。

この2つが食い違っています。enumerate() は各要素を (index, item) という1つのタプルに変え、map はそのタプルを1つの引数としてクロージャに渡します。ところが、あなたのクロージャは別々の引数を2つほしがっています。

封筒を1通ずつしか入れられない郵便受けを想像してください。封筒の中に手紙を2枚入れる(タプル)のはかまいませんが、ばらばらの手紙を2枚そのまま入れることはできません。|(i, name)| は「封筒ごと受け取って、ここで開けます」という意味になります。

メッセージの「closure」か「function」という言葉で、何を渡したのかがわかります。その場で書いたクロージャなのか、.map(add) のように名前で渡した fn なのか、です。

クロージャを受け取るメソッドは、どれも「どんなふうに呼ぶか」をはっきり決めています。map がほしがるのは FnMut(Self::Item) -> B のようなもので、引数は1つ、つまり要素です。enumerate() のあとの要素は (usize, &&str) です。これは、たまたまタプルになっている1つの値です。sort_by がほしがるのは FnMut(&T, &T) -> Ordering で、引数は2つです。thread::spawn がほしがるのは FnOnce() -> T で、引数はありません。

Rust は、タプルを勝手にばらして別々の引数にすることも、別々の引数を勝手にタプルにまとめることもしません。おかげでルールがシンプルになります。メソッドのシグネチャに書いてあるものが、そのままクロージャに渡されるのです。このシグネチャが読めるようになれば、どのイテレータのメソッドが何を渡してくるか、予想できるようになります。

この裏にある Fn 系トレイトについてはクロージャのトレイト、タプルについてはタプルを分解するを見てください。

直し方1:引数のリストの中でタプルを分解する

Section titled “直し方1:引数のリストの中でタプルを分解する”

メッセージに「a single 2-tuple」(2要素のタプル1つ)とあるときは、これを選びます。いちばんよく使う直し方です。カッコを足して |(i, name)| と書くと、タプルを1つ受け取って、その2つの部分に名前を付けられます。enumerate、zip、マップのループのどれでも使えます。

use std::collections::BTreeMap;
fn main() {
let names = vec!["Ferris", "Corro", "Rusty"];
// enumerate は要素ごとに (番号, 名前) のタプルを1つ渡す
let lines: Vec<String> = names
.iter()
.enumerate()
.map(|(i, name)| format!("{}. {name}", i + 1))
.collect();
println!("{:?}", lines);
// マップのループも、要素ごとに (キー, 値) のタプルを1つ渡す
let mut stock = BTreeMap::new();
stock.insert("apples", 3);
stock.insert("pears", 0);
stock.insert("plums", 5);
let total: i32 = stock.iter().map(|(_name, count)| *count).sum();
println!("果物は全部で{total}個");
}

実行結果:

["1. Ferris", "2. Corro", "3. Rusty"]
果物は全部で8個
▶ 実際に試してみよう

_name の先頭の _ は、「これは使わないとわかっています」と Rust に伝える印です。_ だけを書いてもかまいません。zip で同じことをする例は、E0057 のページにあります。

直し方2:メソッドが渡す数だけ引数を書く

Section titled “直し方2:メソッドが渡す数だけ引数を書く”

メッセージに「expected to take N arguments」(引数を N 個取るはず)とあるときは、これを選びます。メソッドが何を渡すかを確かめて、ちょうどその数だけ引数を書きましょう。

  • sort_by は要素を2つ比べます:|a, b|
  • for_each、map、filter は要素を1つ渡します:|item|、使わないなら |_|
  • thread::spawn は何も渡しません:||、ふつうは move ||
use std::thread;
fn main() {
// sort_by は2つずつ比べる:引数は2つ
let mut scores = vec![42, 7, 99, 15];
scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", scores);
// for_each は要素を1つ渡す:引数は1つ(使わないなら _)
let tasks = vec!["書く", "テストする", "リリースする"];
tasks.iter().for_each(|task| println!("やること: {task}"));
tasks.iter().for_each(|_| print!("*"));
println!();
// thread::spawn は何も渡さずに呼ぶ:引数は0個
let id = 1;
let handle = thread::spawn(move || format!("ワーカー{id}、完了"));
println!("{}", handle.join().unwrap());
}

実行結果:

[99, 42, 15, 7]
やること: 書く
やること: テストする
やること: リリースする
***
ワーカー1、完了
▶ 実際に試してみよう

スレッドにデータを渡したいときは、引数を足すのではなく、上の id のように move でキャプチャします。並行処理とソートも見てください。

直し方3:名前付きの関数をクロージャで包むか、タプルを受け取るようにする

Section titled “直し方3:名前付きの関数をクロージャで包むか、タプルを受け取るようにする”

メッセージに「function is expected to take…」とあるときは、これを選びます。.map(add) のように関数を名前で渡していて、その引数が合っていない状態です。次の例では、要素はペアなのに、add は数を2つ受け取ります。

error[E0593]: function is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments
--> src/main.rs:7:48
|
1 | fn add(a: i32, b: i32) -> i32 {
| ----------------------------- takes 2 distinct arguments
...
7 | let sums: Vec<i32> = pairs.into_iter().map(add).collect();
| --- ^^^ expected function that takes a single 2-tuple as argument
| |
| required by a bound introduced by this call

「関数は2要素のタプルを1つ受け取るはずなのに、別々の引数を2つ受け取っている」という意味です。required by a bound introduced by this call は「この呼び出し(map)が持ち込んだ境界によって必要とされている」という意味です。

関数を小さなクロージャで包んで、タプルを分解してから呼ぶか、タプルをそのまま受け取るバージョンを書きます。

fn add(a: i32, b: i32) -> i32 {
a + b
}
// ペアを1つのタプルとして受け取るバージョン
fn add_pair((a, b): (i32, i32)) -> i32 {
a + b
}
fn main() {
let pairs = vec![(1, 2), (3, 4), (10, 20)];
// 包む:タプルを1つ受け取って、引数2つで呼ぶ
let sums: Vec<i32> = pairs.iter().map(|&(a, b)| add(a, b)).collect();
println!("{:?}", sums);
// または、最初からタプルを1つ受け取る関数を渡す
let sums2: Vec<i32> = pairs.into_iter().map(add_pair).collect();
println!("{:?}", sums2);
}

実行結果:

[3, 7, 30]
[3, 7, 30]
▶ 実際に試してみよう

|&(a, b)| に & があるのは、.iter() が各ペアを &(i32, i32) として貸してくるからです。このパターンで、2つの数をコピーして取り出しています。ふつうは包むほうがおすすめです。add は普通の引数2つの関数のままなので、ほかのコードからもそのまま呼べます。

自分で書いたジェネリック関数が Fn(i32) -> i32 をほしがっているのに、引数2つの add を渡すと、「function is expected to take 1 argument, but it takes 2 arguments」(関数は引数を1つ取るはずなのに2つ取る)になります。直し方は、足りない値を埋めるラッパーです。E0057 の直し方3で紹介しています。

HashMap::retain(逆のパターン)。 マップのメソッドはたいていタプルを渡してきますが、retain だけはキーと値を別々の2つの引数で渡します。そのため、stock.retain(|(name, count)| *count > 0) と書くと「closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument」(別々の引数を2つ取るはずなのに、2要素のタプルを1つ取る)になります。直し方:カッコを外して stock.retain(|_name, count| *count > 0) と書きます。Vec::retain はいつもどおり要素を1つ受け取ります。

引数を書き忘れる。 tasks.iter().for_each(|| println!("task")) は「closure is expected to take 1 argument, but it takes 0 arguments」(引数を1つ取るはずなのに0個)になり、rustc は |_| を提案します。直し方:|_|、または値を使いたいなら |task| と書きます。

スレッド。 thread::spawn(|id| ...) は「closure is expected to take 0 arguments, but it takes 1 argument」(引数を0個取るはずなのに1つ)になります。直し方:thread::spawn(move || ...) と書き、id は外側からキャプチャします。

関数ポインタの引数。 関数が f: fn(i32) -> i32(ジェネリックな F: Fn ではなく、ただの関数ポインタ)を受け取る場合、引数2つの add を渡すと、E0593 ではなく E0308 になり、「incorrect number of function parameters」(関数の引数の数が正しくない)というラベルが付きます。直し方は同じで、|x| add(x, 10) のようなラッパーのクロージャを使います。何もキャプチャしないクロージャは、fn ポインタとして渡せます。

理解度チェック

1. names.iter().enumerate().map(|i, name| ...) で E0593 が出ました。どう直しますか?

2. scores.sort_by(...) に渡すクロージャには、引数がいくつ必要ですか?

3. thread::spawn(|id| println!("{id}")) で E0593 が出ました。id をスレッドに渡すにはどうしますか?

スコア: 0 / 3