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

Ошибка 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() работает так же: он отдаёт тебе не сами элементы, а номерки к ним (ссылки). Замыкание, которое настаивает на самом пальто, номерок не примет.

Каждый метод итератора точно решает, какой тип он передаёт твоему замыканию, и этот тип зависит от того, как ты получил итератор:

  • v.iter() одалживает каждый элемент: &T.
  • v.into_iter() (для Vec, которым ты владеешь) отдаёт каждый элемент: T.
  • filter и find одалживают элемент ещё раз, так что поверх .iter() получается &&T.
  • sort_by одалживает тебе два элемента: &T и &T.

Если ты не пишешь типы параметров, Rust берёт их из метода, и они всегда подходят. Если пишешь тип сам, Rust верит тебе на слово и проверяет его. Он не станет тихо копировать значение или превращать ссылку во владеющее значение, ведь это изменило бы поведение кода (а для String копия стоит памяти и времени). Урок Итераторы показывает три способа получить итератор, а урок Заимствование объясняет ссылки.

Выбирай это в первую очередь. Параметрам замыканий почти никогда не нужен тип: 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|.

Проверь себя

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