Ошибка 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 parameters6 + .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 argumenterror[E0593]: closure is expected to take 0 arguments, but it takes 1 argumenterror[E0593]: function is expected to take 1 argument, but it takes 2 argumentserror[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).
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Каждый метод, который принимает замыкание, точно говорит, как будет его вызывать. 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.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0057: closure takes N arguments but M were supplied: ты сам вызываешь замыкание с неверным числом значений.
- E0631: type mismatch in closure arguments: число верное, но у параметра не тот тип — часто
i32там, где итератор отдаёт&i32. - E0061: function takes N arguments but M were supplied: вызов обычной функции с неверным числом аргументов.
- E0308: mismatched types: то, что ты получишь с параметром-указателем
fn(...)или с|(a, b)|, когда элементы вовсе не кортежи. - E0599: no method found: иногда появляется сразу после E0593, например «the method
collectexists … but its trait bounds were not satisfied». Сначала исправь E0593, и она исчезнет. - Все ошибки компилятора: полный список страниц с ошибками.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. names.iter().enumerate().map(|i, name| ...) даёт E0593. Как исправить?
2. Сколько параметров нужно замыканию для scores.sort_by(...)?
3. thread::spawn(|id| println!("{id}")) даёт E0593. Как передать id в поток?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0593 — список кодов ошибок Rust (на английском): официальное объяснение этой ошибки.
- Замыкания: анонимные функции, которые захватывают своё окружение — книга «Язык программирования Rust» (перевод на русский): синтаксис замыканий, вывод типов параметров и трейты
Fn. - Как входные параметры — Rust на примерах (перевод на русский): как ограничение
Fnу функции решает, какое замыкание она примет. Iterator::enumerate— документация стандартной библиотеки (на английском): показывает, что каждый элемент становится одним кортежем(index, value).HashMap::retain— документация стандартной библиотеки (на английском): метод хеш-таблицы, замыкание которого принимает ключ и значение двумя аргументами.