Ошибка Rust E0284: type annotations needed (тип для parse())
E0284 почти всегда значит одно: ты вызвал .parse(), а Rust не знает, во что превратить текст. Пометка cannot satisfy `<_ as FromStr>::Err == _` выглядит пугающе, но обычно исправление занимает несколько символов, например : u32 или ::<u32>. На этой странице сначала исправление, а потом разбор этой пометки, чтобы она перестала пугать. Остальные ошибки из этой серии ищи на странице Ошибки компилятора Rust.
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»error[E0284]: type annotations needed --> src/main.rs:2:9 |2 | let age = "42".parse().unwrap(); | ^^^ ----- type must be known at this point | = note: cannot satisfy `<_ as FromStr>::Err == _`help: consider giving `age` an explicit type |2 | let age: /* Type */ = "42".parse().unwrap(); | ++++++++++++Компилятор говорит: нужна аннотация типа, и предлагает явно указать тип age. Вот программа, которая выдаёт эту ошибку:
fn main() { let age = "42".parse().unwrap(); println!("В следующем году тебе будет {}", age + 1);}Ту же пометку ты увидишь с .expect(...) вместо .unwrap(), с match "42".parse() { ... } и с .parse() внутри замыкания. Когда результат .parse() не сохраняется в переменную, rustc показывает на сам parse и предлагает parse::</* Type */>().
Что это значит
Заголовок раздела «Что это значит»С отметками всё просто:
^^^подage: переменная, тип которой неизвестен.-----подparse: «type must be known at this point» (здесь тип уже должен быть известен). Именно здесь Rust застрял.- Подсказка (help): «consider giving
agean explicit type» — укажи дляageявный тип. Замени/* Type */настоящим типом, напримерu32.
Теперь пометка, по кусочкам. В сообщениях компилятора _ значит «тип, который я пока не знаю»:
| Кусочек | Простыми словами |
|---|---|
<_ as FromStr> |
«тип, в который ты разбираешь текст (я его пока не знаю)» |
::Err |
«…и тип ошибки, который к нему прилагается» |
== _ |
«должен быть равен другому типу, который я тоже не знаю» |
Так что вся пометка говорит: «Я даже не могу понять, какую ошибку может вернуть parse, потому что не знаю, во что ты разбираешь текст».
Представь, что даёшь переводчику письмо и говоришь: «Переведи». На французский? На японский? Пока ты не скажешь, он не может начать — и даже не может сказать, с какими трудностями столкнётся.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Вот сигнатура parse, немного упрощённая:
fn parse<F: FromStr>(&self) -> Result<F, F::Err>F — тип, который ты хочешь получить, а F::Err — его тип ошибки. У каждого целевого типа он свой: разбор i32 может упасть с ParseIntError, f64 — с ParseFloatError, bool — с ParseBoolError. Rust нужен F, чтобы вообще знать, что возвращает parse.
От выбора зависит и то, что произойдёт во время выполнения. "42" нормально разбирается и как u8, и как i32, и как f64. А вот "300" не разберётся как u8 (слишком большое), а "4.5" — как i32 (не целое). Rust не станет гадать: неверная догадка превратилась бы в баг, который проявляется только на определённом вводе. Раздел FromStr и .parse() урока о преобразованиях показывает, как работает FromStr и как добавить его своим типам.
Как Rust сам выясняет типы, объясняется на странице E0282. Почему для «type annotations needed» есть три разных кода (E0282, E0283, E0284), объясняется на странице E0283. Если коротко: E0284 — это вариант, в котором застрявшее правило касается типа, принадлежащего неизвестному типу (здесь F::Err). Для тебя исправление то же: назови тип.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: укажи тип переменной
Заголовок раздела «Исправление 1: укажи тип переменной»Выбирай этот вариант в большинстве случаев. Так пишут в книге о Rust, и это заодно документирует, что должно быть в тексте:
fn main() { let age: u32 = "42".parse().unwrap(); let price: f64 = "4.99".parse().unwrap(); let port: u16 = "8080".parse().expect("порт должен быть числом");
println!("В следующем году тебе будет {}", age + 1); println!("Два кофе: {:.2}", price * 2.0); println!("Слушаем порт {port}");}Вывод:
В следующем году тебе будет 43Два кофе: 9.98Слушаем порт 8080Выбирай тип, который соответствует смыслу текста: u16 для порта, u8 для процентов, f64 для цены. Слишком маленький тип превращает правильный ввод в ошибку, так что, если сомневаешься, бери тип попросторнее, например i64 или u32.
Исправление 2: «турборыба» parse::<T>()
Заголовок раздела «Исправление 2: «турборыба» parse::<T>()»Выбирай этот вариант, когда нет переменной, которой можно указать тип: внутри замыкания, посреди цепочки итераторов или в выражении вроде ... * 2:
fn main() { let line = "3 7 5"; let total = line .split(' ') .map(|s| s.parse::<u32>().unwrap()) .sum::<u32>(); let doubled = "21".parse::<i32>().unwrap() * 2;
println!("Сумма: {total}"); println!("Вдвое больше: {doubled}");}Вывод:
Сумма: 15Вдвое больше: 42Ставь турборыбу на сам parse. Внутри цепочки указать тип только у итогового результата часто недостаточно; смотри первый пункт в разделе «Где это обычно встречается» ниже.
Исправление 3: назови тип и как следует обработай ошибку
Заголовок раздела «Исправление 3: назови тип и как следует обработай ошибку»Выбирай этот вариант для настоящего ввода (файлы, аргументы командной строки, веб-формы), где .unwrap() уронил бы программу на неправильном тексте. Укажи тип у parse и сделай match по Result. Обрати внимание, как тип решает, какой ввод пройдёт:
fn main() { for input in ["42", "300", "abc"] { match input.parse::<u8>() { Ok(n) => println!("{input}: получили {n}"), Err(e) => println!("{input}: {e}"), } }
let rate: Result<f64, _> = "0.25".parse(); println!("{:?}", rate);}Вывод:
42: получили 42300: number too large to fit in target typeabc: invalid digit found in stringOk(0.25)Сообщения об ошибках («число слишком велико для целевого типа», «в строке найден недопустимый символ») печатает сама стандартная библиотека, поэтому они на английском. В Result<f64, _> ты называешь только тип значения. _ позволяет Rust самому подставить тип ошибки (ParseFloatError) — ровно ту часть, о которой спрашивала пометка. Подробнее — в уроке Result.
Исправление 4: используй значение там, где его тип известен
Заголовок раздела «Исправление 4: используй значение там, где его тип известен»Выбирай этот вариант, когда разобранное значение попадает в поле структуры или параметр функции с известным типом. Rust читает функцию целиком, так что такое использование позже отвечает на вопрос:
struct Config { port: u16, workers: u8,}
fn greet(age: u32) { println!("Добро пожаловать! Тебе {age}.");}
fn main() { let port = "8080".parse().unwrap(); // u16, из Config let workers = "4".parse().unwrap(); // u8, из Config let config = Config { port, workers }; println!("Порт {}, рабочих потоков: {}", config.port, config.workers);
let age = "42".parse().unwrap(); // u32, из greet greet(age);}Вывод:
Порт 8080, рабочих потоков: 4Добро пожаловать! Тебе 42.Не всякое использование подходит, поэтому это исправление 4. Арифметика вроде age + 1 и сравнения вроде age >= limit всё равно дают E0284, даже если limit — u32: операторы работают между многими парами типов, так что не называют один конкретный тип. А вызов метода, например age.pow(2), превращает ошибку в E0282. Если сомневаешься, используй исправление 1.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»Разбор внутри map, потом sum(). let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum(); — всё ещё E0284, хотя у total есть тип. u32 может быть суммой значений u32 или &u32, так что аннотация не говорит Rust, чем является каждый элемент. Исправление: s.parse::<u32>(). (А вот сбор в Vec<i32> работает, потому что Vec<i32> может хранить только i32.)
Сбор результатов разбора в Result. let nums: Result<Vec<_>, _> = text.split(' ').map(|x| x.parse()).collect(); — это E0284: _ в Vec<_> оставляет тип элемента открытым. Исправление: Result<Vec<i32>, _>. Этот удобный приём останавливается на первом плохом элементе и отдаёт его ошибку.
Чтение пользовательского ввода. let guess = line.trim().parse().unwrap(); в игре «Угадай число» или консольной утилите. Исправление: let guess: u32 = ..., в точности как в книге о Rust.
С ? ошибка ещё страннее. let port = text.parse()?; не даёт E0284. В редакции 2021 Rust откатывается к типу () и сообщает E0277: the trait bound `(): FromStr` is not satisfied (в редакции 2024 там ! вместо ()). Исправление то же: let port: u16 = text.parse()?;.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0282: нужна аннотация типа, когда подсказки нет вообще (или тип нужен для вызова метода вроде
age.pow(2)). - E0283: нужна аннотация типа после
.sum(),.collect()или.into(), когда подходит много типов. Там же объясняется, почему кодов три. - E0790:
FromStr::from_str("42")илиDefault::default()вызваны без указания типа. - E0277: то, что даёт
.parse()?без типа, и то, что ты получишь, если выбранный тип нельзя разобрать из текста. - E0599: метод не найден, например
.parse()у того, что не является текстом, вроде переменнойu32(такое преобразуй черезu8::try_from(n)). - Все ошибки компилятора: полный список страниц об ошибках.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Что значит cannot satisfy <_ as FromStr>::Err == _ простыми словами?
2. Какая строка исправляет let port = "8080".parse().unwrap();?
3. let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum(); всё ещё даёт E0284. Куда поставить тип?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0284 — Rust error codes index (на английском): официальное объяснение этой ошибки.
str::parse— документация стандартной библиотеки (на английском): сигнатура метода и оба стиля — с аннотацией и с турборыбой.FromStr— документация стандартной библиотеки (на английском): трейт, который стоит заparse, его типErrи как его реализовать.- Программирование игры в загадки — книга «Язык программирования Rust» (перевод на русский): где впервые появляется
let guess: u32 = guess.trim().parse(), с пошаговым объяснением. - Типы данных — книга «Язык программирования Rust» (перевод на русский): почему
"42".parse()нужна аннотация типа, с настоящей ошибкой компилятора.