Ошибка Rust E0631: type mismatch in closure arguments (не тот тип параметра)
E0631 значит, что у твоего замыкания правильное число параметров, но хотя бы у одного не тот тип. В девяти случаях из десяти разница — один символ &: ты написал |x: i32|, а итератор одалживает тебе &i32. Часто быстрее всего просто удалить аннотацию типа и дать Rust вывести его самому. Сначала на этой странице показано это исправление, а потом объяснено, откуда берётся &, чтобы в следующий раз ты мог это предсказать. Другие ошибки из этой серии собраны на странице Ошибки компилятора Rust.
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»error[E0631]: type mismatch in closure arguments --> src/main.rs:3:43 |3 | let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ items are &i32 | ^^^ -------- found signature defined here | | | expected due to this | = note: expected closure signature `fn(&{integer}) -> _` found closure signature `fn(i32) -> _`note: required by a bound in `map`help: consider adjusting the signature so it borrows its argument |3 | let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); // ❌ items are &i32 | +Компилятор пишет: несовпадение типов в аргументах замыкания, — и предлагает изменить сигнатуру так, чтобы замыкание заимствовало аргумент. Вот программа, которая выдаёт эту ошибку:
fn main() { let prices = vec![120, 45, 300]; let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ items are &i32 println!("{:?}", doubled);}Комментарий напоминает: элементы здесь — &i32. Сразу после неё rustc печатает вторую, более длинную ошибку: the method `collect` exists for struct `Map<...>`, but its trait bounds were not satisfied (E0599). Это лишь побочный эффект. Исправь E0631, и она исчезнет.
Что это значит
Заголовок раздела «Что это значит»Две строки note — сердце сообщения. Читай их как «метод хотел вот это, а ты написал вот то»:
- expected
fn(&{integer}) -> _:mapбудет вызывать твоё замыкание со ссылкой на целое число.{integer}значит «какой-то целочисленный тип, который Rust ещё не уточнил», а_— «любой возвращаемый тип». - found
fn(i32) -> _: твоё замыкание говорит, что принимаетi32по значению.
Поставь их рядом, и единственная разница — &. Строка help: показывает исправление в один символ.
Представь гардероб. Ты сдаёшь пальто и получаешь номерок. .iter() работает так же: он отдаёт тебе не сами элементы, а номерки к ним (ссылки). Замыкание, которое настаивает на самом пальто, номерок не примет.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Каждый метод итератора точно решает, какой тип он передаёт твоему замыканию, и этот тип зависит от того, как ты получил итератор:
v.iter()одалживает каждый элемент:&T.v.into_iter()(дляVec, которым ты владеешь) отдаёт каждый элемент:T.filterиfindодалживают элемент ещё раз, так что поверх.iter()получается&&T.sort_byодалживает тебе два элемента:&Tи&T.
Если ты не пишешь типы параметров, Rust берёт их из метода, и они всегда подходят. Если пишешь тип сам, Rust верит тебе на слово и проверяет его. Он не станет тихо копировать значение или превращать ссылку во владеющее значение, ведь это изменило бы поведение кода (а для String копия стоит памяти и времени). Урок Итераторы показывает три способа получить итератор, а урок Заимствование объясняет ссылки.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: убери аннотацию типа
Заголовок раздела «Исправление 1: убери аннотацию типа»Выбирай это в первую очередь. Параметрам замыканий почти никогда не нужен тип: Rust точно знает, что передают map, filter и sort_by. Удали : i32, и код скомпилируется:
fn main() { let prices = vec![120, 45, 300]; let ages = vec![12, 34, 17, 50]; let mut scores = vec![42, 7, 99, 15];
// без аннотаций типов: Rust берёт то, что на самом деле отдаёт итератор let doubled: Vec<i32> = prices.iter().map(|p| p * 2).collect(); let adults: Vec<&i32> = ages.iter().filter(|age| **age >= 18).collect(); scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", doubled); println!("{:?}", adults); println!("{:?}", scores);}Вывод:
[240, 90, 600][34, 50][99, 42, 15, 7]p * 2 работает с &i32, потому что числовые операторы Rust принимают и ссылки. В filter age — это &&i32, поэтому **age проходит по обеим ссылкам до самого числа. Сравнения вроде b.cmp(a) работают со ссылками напрямую.
Исправление 2: напиши тип, который метод передаёт на самом деле
Заголовок раздела «Исправление 2: напиши тип, который метод передаёт на самом деле»Выбирай это, когда хочешь оставить тип на виду, например чтобы длинную цепочку было легче читать. Следуй подсказке help: от rustc и добавь &. & можно поставить и в образец: |&p| разбирает ссылку и даёт тебе обычную копию числа (это работает для Copy-типов вроде целых чисел):
fn main() { let prices = vec![120, 45, 300]; let ages = vec![12, 34, 17, 50]; let mut scores = vec![42, 7, 99, 15];
// пишем настоящие типы: & для .iter(), && внутри filter let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); let adults: Vec<&i32> = ages.iter().filter(|age: &&i32| **age >= 18).collect(); scores.sort_by(|a: &i32, b: &i32| b.cmp(a));
// или копируем число наружу образцом с & let tax: Vec<i32> = prices.iter().map(|&p| p / 10).collect();
println!("{:?}", doubled); println!("{:?}", adults); println!("{:?}", scores); println!("{:?}", tax);}Вывод:
[240, 90, 600][34, 50][99, 42, 15, 7][12, 4, 30]Обрати внимание на разницу: |p: &i32| значит «p и есть ссылка», а |&p| значит «я получаю ссылку, дай мне то, что за ней».
Исправление 3: измени то, что отдаёт итератор
Заголовок раздела «Исправление 3: измени то, что отдаёт итератор»Выбирай это, когда тебе действительно нужны значения, а не ссылки. Получить их можно двумя способами:
.iter().copied()превращает каждый&i32вi32. Используй его для маленькихCopy-типов вроде чисел,charиbool. (.cloned()делает то же для типов вродеString, клонируя их.).into_iter()уVecотдаёт сами элементы. После этогоVecизрасходован, так что выбирай этот способ, когда вектор больше не нужен.
fn main() { let prices = vec![120, 45, 300];
// .copied() превращает элементы &i32 в элементы i32 let doubled: Vec<i32> = prices.iter().copied().map(|p: i32| p * 2).collect(); println!("{:?}", doubled);
// .into_iter() у Vec отдаёт сами элементы (и расходует Vec) let names = vec![String::from("ferris"), String::from("corro")]; let loud: Vec<String> = names.into_iter().map(|name: String| name.to_uppercase()).collect(); println!("{:?}", loud);}Вывод:
[240, 90, 600]["FERRIS", "CORRO"]Если names понадобится после цикла, оставь .iter() и принимай &String. Перемещение из Vec, который ты только заимствовал, — это уже другая ошибка, E0507.
Исправление 4: пусть именованная функция принимает то, что отдаёт итератор
Заголовок раздела «Исправление 4: пусть именованная функция принимает то, что отдаёт итератор»Выбирай это, когда передаёшь функцию по имени, например .map(shout). Тогда в сообщении вместо «closure» будет «function»:
error[E0631]: type mismatch in function arguments --> src/main.rs:7:51 |1 | fn shout(word: &String) -> String { | --------------------------------- found signature defined here...7 | let loud: Vec<String> = words.into_iter().map(shout).collect(); | --- ^^^^^ expected due to this | | | required by a bound introduced by this call | = note: expected function signature `fn(&str) -> _` found function signature `fn(&String) -> _`Компилятор ожидал функцию с сигнатурой fn(&str), а нашёл fn(&String). Элементы — &str, а shout хочет &String. Хорошая привычка: пусть функции, которые только читают текст, принимают &str. Тогда они примут строковые литералы, &String и не только. Когда итератор добавляет лишний & (как filter), оберни вызов в короткое замыкание. В обычном вызове функции Rust сам убирает лишние & (это называется deref coercion, автоматическое разыменование), но не может сделать этого, когда функцию передают по имени:
// принимает &str: работает со строковыми литералами, &String и не толькоfn shout(word: &str) -> String { word.to_uppercase()}
fn is_long(word: &str) -> bool { word.len() > 4}
fn main() { let words = vec!["crab", "rustacean", "ferris"];
// элементы — &str, поэтому shout можно передать по имени let loud: Vec<String> = words.iter().copied().map(shout).collect(); println!("{:?}", loud);
// filter даёт &&str: короткое замыкание позволяет Rust преобразовать это за тебя let long: Vec<&str> = words.iter().copied().filter(|w| is_long(w)).collect(); println!("{:?}", long);}Вывод:
["CRAB", "RUSTACEAN", "FERRIS"]["rustacean", "ferris"]Слова здесь остались на английском, потому что .len() считает байты, а каждая русская буква занимает в UTF-8 два байта. Подробнее о &str и String — в уроке Строки.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»Сортировка с типизированными параметрами. scores.sort_by(|a: i32, b: i32| b.cmp(&a)) даёт E0631 с expected closure signature `for<'a, 'b> fn(&'a {integer}, &'b {integer}) -> _`. Часть for<'a, 'b> говорит лишь «для любого заимствования». Исправление: |a, b| b.cmp(a). Подробнее в уроке Сортировка.
Записи хеш-таблицы с владеющими типами. stock.iter().map(|(name, count): (String, i32)| ...) для HashMap<String, i32> даёт expected closure signature `fn((&String, &_)) -> _`. .iter() у хеш-таблицы одалживает и ключ, и значение. Исправление: убери аннотацию или напиши (&String, &i32).
Твоя собственная обобщённая функция. Если fn apply<F: Fn(i32) -> i32>(f: F, x: i32) получает замыкание |x: f64| x / 2.0, rustc сообщает expected closure signature `fn(i32) -> _` и found closure signature `fn(f64) -> _` и указывает на ограничение. Исправление: пусть замыкание принимает i32 (а внутри преобразуй: x as f64 / 2.0), или поменяй ограничение, если ты имел в виду f64. Подробнее в уроке Fn, FnMut и FnOnce.
for_each со String. words.iter().for_each(|w: String| ...) для Vec<&str> ожидает fn(&&str). &str — это не String. Исправление: |w| или |w: &&str|.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0593: closure takes the wrong number of arguments: родственная ошибка, но про число параметров (например,
|a, b|, когда передаётся один кортеж). - E0057: closure called with the wrong number of arguments: ты сам вызываешь замыкание и передаёшь слишком мало или слишком много значений.
- E0308: mismatched types: общая ошибка «не тот тип», например когда не того типа возвращаемое значение замыкания или для параметра-указателя
fn(...). - E0599: no method found: ошибка «trait bounds were not satisfied» у
collectилиsum, которая идёт следом за E0631. Она исчезает вместе с E0631. - E0507: cannot move out of a borrowed value: когда пытаешься забрать владение элементами, которые только заимствовал через
.iter(). - Все ошибки компилятора: полный список страниц с ошибками.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. let v = vec![1, 2, 3]; Какой тип получает замыкание в v.iter().map(|x| ...)?
2. Примечание говорит expected closure signature fn(&{integer}) -> _, found closure signature fn(i32) -> _. Как быстрее всего исправить?
3. Что делает |&p| p * 2 внутри prices.iter().map(...)?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0631 — список кодов ошибок Rust (на английском): официальное объяснение этой ошибки.
- Обработка последовательности элементов с помощью итераторов — книга «Язык программирования Rust» (перевод на русский):
iter,into_iterиiter_mutи что отдаёт каждый из них. - Замыкания: анонимные функции, которые захватывают своё окружение — книга «Язык программирования Rust» (перевод на русский): как выводятся типы параметров замыкания и когда стоит писать их самому.
Iterator::copied— документация стандартной библиотеки (на английском): превращает итератор по&Tв итератор поT.slice::sort_by— документация стандартной библиотеки (на английском): сигнатура показывает, что замыкание сравнения получает две ссылки.