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

Ошибка 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 */>().

С отметками всё просто:

  1. ^^^ под age: переменная, тип которой неизвестен.
  2. ----- под parse: «type must be known at this point» (здесь тип уже должен быть известен). Именно здесь Rust застрял.
  3. Подсказка (help): «consider giving age an explicit type» — укажи для age явный тип. Замени /* Type */ настоящим типом, например u32.

Теперь пометка, по кусочкам. В сообщениях компилятора _ значит «тип, который я пока не знаю»:

Кусочек Простыми словами
<_ as FromStr> «тип, в который ты разбираешь текст (я его пока не знаю)»
::Err «…и тип ошибки, который к нему прилагается»
== _ «должен быть равен другому типу, который я тоже не знаю»

Так что вся пометка говорит: «Я даже не могу понять, какую ошибку может вернуть parse, потому что не знаю, во что ты разбираешь текст».

Представь, что даёшь переводчику письмо и говоришь: «Переведи». На французский? На японский? Пока ты не скажешь, он не может начать — и даже не может сказать, с какими трудностями столкнётся.

Вот сигнатура 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). Для тебя исправление то же: назови тип.

Выбирай этот вариант в большинстве случаев. Так пишут в книге о 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:

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: получили 42
300: number too large to fit in target type
abc: invalid digit found in string
Ok(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