Ошибка 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:
^^^^^^^^^^подmut scores: это переменная, тип которой Rust не смог определить.----------подVec::new(): «type must be known at this point» (здесь тип уже должен быть известен).Vec::new()создаёт пустой список, но пустой список чего? Чисел? Имён? Rust нужно это знать, чтобы выделить подходящее хранилище.- Подсказка предлагает
let mut scores: Vec<T>. ЗдесьT— просто заглушка. Замени её настоящим типом, напримерVec<u32>.
Представь посылку на почте, у которой не заполнена графа «содержимое». Работник не отправит её, пока кто-нибудь туда что-то не впишет. Можешь вписать сам или положить что-нибудь в коробку, чтобы работник увидел.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»У каждого значения в 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 не может выбрать за тебя. Гадать он не станет, ведь неверная догадка молча изменила бы поведение программы. Как одна функция может возвращать разные типы, рассказано в уроке Обобщения.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: аннотируй переменную
Заголовок раздела «Исправление 1: аннотируй переменную»Выбирай это в большинстве случаев. Допиши : Тип после имени переменной. Это коротко и заодно документирует код для следующего читателя:
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 заполнить эту часть. Подробнее о записи типов — в уроке Типы данных.
Исправление 2: используй turbofish ::<>
Заголовок раздела «Исправление 2: используй turbofish ::<>»Выбирай это, когда аннотировать нечего, например посреди цепочки вызовов итераторов. Тип ставится прямо в вызов функции через ::<Тип>. ::<> называют «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);}Вывод:
NoneSettings { volume: 0, muted: false }Громкость в запасных настройках: 0Settings::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
Что почитать
Заголовок раздела «Что почитать»- E0282 — список кодов ошибок Rust (на английском): официальное объяснение этой ошибки с дополнительными примерами.
- E0283 — список кодов ошибок Rust (на английском): вариант, который встречается с
collectиsum, когда подходит много типов. - Типы данных — книга «Язык программирования Rust» (перевод на русский): объясняет аннотации, включая классический пример с
"42".parse(). - Вывод типов — Rust на примерах (перевод на русский): короткий пример того, как Rust выводит тип
Vecиз кода ниже. Iterator::collect— документация стандартной библиотеки (на английском): показаны и стиль с аннотацией, и стиль с turbofish.