Перейти к содержимому

Ошибка Rust E0593: closure is expected to take a single 2-tuple as argument

E0593 значит, что ты передал замыкание (или функцию) куда-то ещё — в map, sort_by или thread::spawn, — а число его параметров не совпадает с тем, как его будут вызывать. Самый частый случай: итератор отдаёт замыканию один кортеж, а ты написал два параметра. Чаще всего исправление — один символ, и 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)) // ❌ two parameters
| ^^^ --------- 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)) // ❌ two parameters
6 + .map(|(i, name)| format!("{}. {name}", i + 1)) // ❌ two parameters
|

Компилятор пишет: ожидается замыкание, которое принимает один кортеж из двух элементов, а твоё принимает два отдельных аргумента. Подсказка предлагает принять кортеж. Вот программа, которая выдаёт эту ошибку:

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

Комментарий отмечает: два параметра. У E0593 бывают и другие первые строки. Все они значат «не то число параметров»:

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; и наоборот — ожидались два отдельных аргумента, а замыкание принимает один кортеж.

Посмотри на две отметки под строкой 6:

  • ^^^ указывает на map. Это тот, кто вызывает твоё замыкание, и он решает, сколько значений передать.
  • --------- указывает на твой список параметров |i, name|. Это то, что ты пообещал принять.

Они не совпадают. enumerate() превращает каждый элемент в один кортеж (index, item), и map передаёт этот кортеж замыканию как один аргумент. А твоё замыкание просит два отдельных.

Представь почтовую щель, которая принимает по одному конверту. Внутрь конверта можно вложить два письма (кортеж), но два отдельных письма щель не примет. |(i, name)| говорит: «дай мне конверт, я открою его прямо здесь».

Слово «closure» или «function» в сообщении говорит, что именно ты передал: замыкание, написанное на месте, или именованную fn, как в .map(add).

Каждый метод, который принимает замыкание, точно говорит, как будет его вызывать. map хочет что-то вроде FnMut(Self::Item) -> B: один аргумент — элемент. Для enumerate() элемент — это (usize, &&str). Это одно значение, которое просто оказалось кортежем. sort_by хочет FnMut(&T, &T) -> Ordering: два аргумента. thread::spawn хочет FnOnce() -> T: вообще без аргументов.

Rust никогда не раскладывает кортеж на отдельные аргументы за тебя и никогда не упаковывает отдельные аргументы в кортеж. Так правило остаётся простым: что написано в сигнатуре метода, то твоё замыкание и получит. Научившись читать такие сигнатуры, ты сможешь предсказать, что отдаёт любой метод итератора.

О трейтах Fn, которые за этим стоят, читай в уроке Fn, FnMut и FnOnce, а о кортежах — в уроке Кортежи.

Исправление 1: разбери кортеж прямо в списке параметров

Заголовок раздела «Исправление 1: разбери кортеж прямо в списке параметров»

Выбирай это, когда в сообщении есть «a single 2-tuple». Это исправление понадобится чаще всего. Добавь скобки: |(i, name)| принимает один кортеж и даёт имена двум его частям. Это работает для enumerate, zip и перебора хеш-таблицы:

use std::collections::BTreeMap;
fn main() {
let names = vec!["Ferris", "Corro", "Rusty"];
// enumerate даёт один кортеж (index, name) на элемент
let lines: Vec<String> = names
.iter()
.enumerate()
.map(|(i, name)| format!("{}. {name}", i + 1))
.collect();
println!("{:?}", lines);
// перебор хеш-таблицы тоже даёт один кортеж (key, value) на элемент
let mut stock = BTreeMap::new();
stock.insert("яблоки", 3);
stock.insert("груши", 0);
stock.insert("сливы", 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: дай методу столько параметров, сколько он передаёт

Заголовок раздела «Исправление 2: дай методу столько параметров, сколько он передаёт»

Выбирай это, когда сообщение говорит «expected to take N arguments» (ожидается N аргументов). Проверь, что передаёт метод, и напиши ровно столько параметров:

  • sort_by сравнивает два элемента: |a, b|.
  • for_each, map, filter передают один элемент: |item| или |_|, если он не нужен.
  • thread::spawn не передаёт ничего: ||, обычно move ||.
use std::thread;
fn main() {
// sort_by сравнивает по два элемента: два параметра
let mut scores = vec![42, 7, 99, 15];
scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", scores);
// for_each передаёт один элемент: один параметр (или _, если он не нужен)
let tasks = vec!["написать", "проверить", "выпустить"];
tasks.iter().for_each(|task| println!("сделать: {task}"));
tasks.iter().for_each(|_| print!("*"));
println!();
// thread::spawn вызывает замыкание без аргументов: ноль параметров
let id = 1;
let handle = thread::spawn(move || format!("поток {id} готов"));
println!("{}", handle.join().unwrap());
}

Вывод:

[99, 42, 15, 7]
сделать: написать
сделать: проверить
сделать: выпустить
***
поток 1 готов
▶ Попробуй сам

Чтобы передать данные в поток, не добавляй параметр. Захвати их через move, как id выше. Подробнее в уроках Многопоточность и Сортировка.

Исправление 3: оберни именованную функцию или сделай так, чтобы она принимала кортеж

Заголовок раздела «Исправление 3: оберни именованную функцию или сделай так, чтобы она принимала кортеж»

Выбирай это, когда сообщение говорит «function is expected to take…». Ты передал функцию по имени, например .map(add), а её параметры не подходят. Здесь элементы — пары, а add принимает два числа:

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

Компилятор ждёт функцию, которая принимает один кортеж, а add принимает два отдельных аргумента. Можно обернуть функцию в маленькое замыкание, которое разбирает кортеж и вызывает её, или написать версию, которая сразу принимает кортеж:

fn add(a: i32, b: i32) -> i32 {
a + b
}
// версия, которая принимает пару одним кортежем
fn add_pair((a, b): (i32, i32)) -> i32 {
a + b
}
fn main() {
let pairs = vec![(1, 2), (3, 4), (10, 20)];
// обёртка: на входе один кортеж, на выходе два аргумента
let sums: Vec<i32> = pairs.iter().map(|&(a, b)| add(a, b)).collect();
println!("{:?}", sums);
// или передай функцию, которая уже принимает один кортеж
let sums2: Vec<i32> = pairs.into_iter().map(add_pair).collect();
println!("{:?}", sums2);
}

Вывод:

[3, 7, 30]
[3, 7, 30]
▶ Попробуй сам

& в |&(a, b)| стоит потому, что .iter() одалживает каждую пару как &(i32, i32), а образец копирует два числа наружу. Обёртка обычно лучше: add остаётся нормальной функцией с двумя аргументами, которую может вызывать остальной код.

Если твоя собственная обобщённая функция ждёт Fn(i32) -> i32, а ты передаёшь add с двумя аргументами, получишь «function is expected to take 1 argument, but it takes 2 arguments». Исправление — обёртка, которая подставляет лишнее значение; она показана в исправлении 3 на странице E0057.

HashMap::retain (наоборот). Большинство методов хеш-таблицы отдают кортеж, но retain передаёт ключ и значение двумя отдельными аргументами. Поэтому stock.retain(|(name, count)| *count > 0) даёт «closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument». Исправление: убери скобки, stock.retain(|_name, count| *count > 0). Vec::retain, как обычно, принимает один элемент.

Забытый параметр. tasks.iter().for_each(|| println!("task")) даёт «closure is expected to take 1 argument, but it takes 0 arguments», и rustc предлагает |_|. Исправление: |_| или |task|, если хочешь им пользоваться.

Потоки. thread::spawn(|id| ...) даёт «closure is expected to take 0 arguments, but it takes 1 argument». Исправление: thread::spawn(move || ...) и захвати id снаружи.

Параметр — указатель на функцию. Если твоя функция принимает f: fn(i32) -> i32 (обычный указатель на функцию, а не обобщённый F: Fn), передача add с двумя аргументами даст вместо этого 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