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

Ошибка Rust E0282: type annotations needed (нужна аннотация типа)

E0282 значит, что Rust пытался сам определить тип и не нашёл достаточно подсказок. С логикой твоего кода всё в порядке. Компилятору просто нужно, чтобы ты назвал один тип, обычно парой лишних символов. Сначала на этой странице показано исправление, а потом объяснено, как Rust угадывает типы (это называется вывод типов, type inference), чтобы ты знал, когда ему помочь. Другие ошибки из этой серии собраны на странице Ошибки компилятора Rust.

error[E0282]: type annotations needed for `Vec<_>`
--> src/main.rs:2:9
|
2 | let mut scores = Vec::new(); // a Vec of... what?
| ^^^^^^^^^^ ---------- type must be known at this point
|
help: consider giving `scores` an explicit type, where the type for type parameter `T` is specified
|
2 | let mut scores: Vec<T> = Vec::new(); // a Vec of... what?
| ++++++++

Компилятор пишет: для Vec<_> нужна аннотация типа, и предлагает явно указать тип scores. Вот программа, которая выдаёт эту ошибку:

fn main() {
let mut scores = Vec::new(); // a Vec of... what?
println!("Scores recorded: {}", scores.len());
}

Комментарий спрашивает: «Vec из… чего?».

Слова «type annotations needed» встречаются ещё с тремя кодами. Они значат одно и то же, и исправления с этой страницы работают для всех:

  • E0283: .collect() или .sum() без целевого типа. rustc добавляет примечание вроде the type must implement `FromIterator<&str>` или `Sum<&i32>`, потому что результатом может быть много разных типов.
  • E0284: "42".parse().unwrap() без целевого типа. Примечание говорит cannot satisfy `<_ as FromStr>::Err == _` — пугающий способ спросить «разобрать во что?».
  • E0790: let settings = Default::default();. Заголовок звучит как cannot call associated function on trait without specifying the corresponding `impl` type: Rust не знает, чьё значение по умолчанию тебе нужно.

Какой код ты получишь, зависит от конкретной строки и может поменяться, если добавить ещё кода. Например, let age = "42".parse().unwrap(); даёт E0284, но если на следующей строке вызвать age.pow(2), это станет E0282. Не переживай из-за номера; ищи переменную, на которую указывает rustc.

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

  1. ^^^^^^^^^^ под mut scores: это переменная, тип которой Rust не смог определить.
  2. ---------- под Vec::new(): «type must be known at this point» (здесь тип уже должен быть известен). Vec::new() создаёт пустой список, но пустой список чего? Чисел? Имён? Rust нужно это знать, чтобы выделить подходящее хранилище.
  3. Подсказка предлагает let mut scores: Vec<T>. Здесь T — просто заглушка. Замени её настоящим типом, например Vec<u32>.

Представь посылку на почте, у которой не заполнена графа «содержимое». Работник не отправит её, пока кто-нибудь туда что-то не впишет. Можешь вписать сам или положить что-нибудь в коробку, чтобы работник увидел.

У каждого значения в Rust один точный тип, и он определяется во время компиляции. Чаще всего ты его не пишешь, потому что Rust читает твой код и выводит тип сам. let count = 5; — это i32. let name = String::from("Ferris"); — это String. Это и есть вывод типов, и в уроке Переменные его больше.

Вывод типов использует подсказки из всей функции, а не из одной строки. Если позже ты вызываешь scores.push(90), это подсказка: в Vec лежат числа. Если передаёшь scores в функцию, которая принимает &[u32], это подсказка ещё лучше. E0282 появляется, когда подсказок нет вообще или когда Rust нужен тип раньше, чем он дойдёт до подсказки (например, чтобы найти метод).

Некоторые функции обобщены по возвращаемому типу: parse, collect, sum, Default::default, Vec::new, None. Они могут выдавать много разных типов, поэтому Rust не может выбрать за тебя. Гадать он не станет, ведь неверная догадка молча изменила бы поведение программы. Как одна функция может возвращать разные типы, рассказано в уроке Обобщения.

Выбирай это в большинстве случаев. Допиши : Тип после имени переменной. Это коротко и заодно документирует код для следующего читателя:

fn main() {
let scores: Vec<u32> = Vec::new();
let age: u32 = "42".parse().unwrap();
let words: Vec<&str> = "красный зелёный синий".split(' ').collect();
let winner: Option<String> = None;
println!("Записано результатов: {}", scores.len());
println!("В следующем году тебе будет {}", age + 1);
println!("{:?}", words);
println!("{:?}", winner);
}

Вывод:

Записано результатов: 0
В следующем году тебе будет 43
["красный", "зелёный", "синий"]
None
▶ Попробуй сам

Писать тип целиком не всегда обязательно. let words: Vec<_> = ...collect(); значит «это Vec, а что внутри — определи сам». _ просит Rust заполнить эту часть. Подробнее о записи типов — в уроке Типы данных.

Выбирай это, когда аннотировать нечего, например посреди цепочки вызовов итераторов. Тип ставится прямо в вызов функции через ::<Тип>. ::<> называют «turbofish» (турборыба), потому что ::<> немного похож на рыбку:

fn main() {
let input = "4 15 7";
let numbers = input
.split(' ')
.map(|s| s.parse::<u32>().unwrap())
.collect::<Vec<_>>();
let total = numbers.iter().sum::<u32>();
println!("{:?}", numbers);
println!("Итого: {total}");
}

Вывод:

[4, 15, 7]
Итого: 26
▶ Попробуй сам

Три самых частых вызова с turbofish — parse::<i32>(), collect::<Vec<_>>() и sum::<i32>(). Исправления 1 и 2 одинаково правильны; let total: u32 = numbers.iter().sum(); делает ту же работу. Выбирай то, что читается лучше.

Исправление 3: используй значение, чтобы Rust мог вывести тип

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

Выбирай это, когда ошибка появилась, потому что код ещё не дописан. Часто ты создаёшь пустой Vec, а код, который его заполняет, появится позже. Как только ты используешь значение, у Rust появляется подсказка, и ошибка исчезает — без всякой аннотации:

fn print_team(names: &[String]) {
for name in names {
println!("- {name}");
}
}
fn main() {
let mut team = Vec::new(); // тип пока неизвестен...
team.push(String::from("Ferris")); // теперь Rust знает: Vec<String>
team.push(String::from("Corro"));
print_team(&team);
}

Вывод:

- Ferris
- Corro
▶ Попробуй сам

Порядок немного важен. Подсказка должна появиться раньше, чем Rust понадобится тип для вызова метода. Замыкание вроде let shout = |s| s.to_uppercase(); всё равно не скомпилируется, даже если на следующей строке вызвать shout("hi"), потому что Rust должен знать, что такое s, чтобы найти .to_uppercase(). Там напиши тип: |s: &str| s.to_uppercase(). Подробнее в уроке Замыкания.

Исправление 4: назови тип для None и Default::default()

Заголовок раздела «Исправление 4: назови тип для None и Default::default()»

Выбирай это для значений, которые вообще не несут подсказок. None не говорит, что он мог бы содержать, а Default::default() не говорит, чьё значение по умолчанию тебе нужно. Впиши тип прямо в выражение:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
muted: bool,
}
fn main() {
let fastest_lap = None::<f64>; // вместо: None
let settings = Settings::default(); // вместо: Default::default()
let backup: Settings = Default::default(); // аннотация тоже работает
println!("{:?}", fastest_lap);
println!("{:?}", settings);
println!("Громкость в запасных настройках: {}", backup.volume);
}

Вывод:

None
Settings { volume: 0, muted: false }
Громкость в запасных настройках: 0
▶ Попробуй сам

Settings::default() — самый понятный вариант: читается как «Settings по умолчанию». None::<f64> удобен, но let fastest_lap: Option<f64> = None; (исправление 1) встречается чаще. Подробнее в уроке Option.

Разбор текста в число. let guess = line.trim().parse().unwrap(); даёт E0284 (или E0282, если следом вызвать метод у guess). parse может выдать i32, u8, f64 и многое другое. Исправление: let guess: i32 = ... или .parse::<i32>(). Сравнения guess с переменной типа i32 позже здесь недостаточно (а вот передачи в функцию, которая принимает i32, хватило бы), поэтому проще всего назвать тип прямо в вызове. Подробно этот случай разобран на странице E0284.

.collect(). Он может собрать Vec, String, HashSet, HashMap… поэтому ему всегда нужно знать цель. Исправление: let words: Vec<_> = ...collect(); или .collect::<Vec<_>>(). Подробнее в уроке Итераторы.

.sum() и .product(). nums.iter().sum() даёт E0283 с the type must implement `Sum<&i32>`. Сумма i32 может быть i32, но и другие числовые типы тоже могут быть суммой. Исправление: let total: i32 = nums.iter().sum(); или .sum::<i32>().

Пустая коллекция в конструкторе структуры. let cache = HashMap::new(); в функции new(), которую ты ещё не дописал. Исправление: один раз напиши полный тип (HashMap<String, u32>) или верни её в структуре, у которой тип поля уже объявлен, — это подсказка, которой Rust воспользуется.

  • E0308: mismatched types. Бывает, когда твоя аннотация и значение не совпадают, например let age: u32 = "42";.
  • E0277: trait bound not satisfied. Тип ты указал, но так его не построить, например let n: Vec<i32> = "1 2".split(' ').collect(); (это Vec<&str>; сначала разбери каждый элемент).
  • E0599: no method found, например когда ты аннотировал let n: u32, а потом вызываешь у него метод String вроде .trim().
  • E0004: non-exhaustive patterns — ещё одна ошибка, где rustc пишет исправление за тебя.
  • Все ошибки компилятора: полный список страниц с ошибками.

Проверь себя

1. Какая строка исправит E0282 для let ids = Vec::new();, если в Vec должны лежать значения u64?

2. Почему let n = "7".parse().unwrap(); нужен тип, хотя в тексте явно число?

3. Что значит _ в .collect::<Vec<_>>()?

Результат: 0 / 3