Ошибка Rust E0283: type annotations needed (sum, collect и into)
E0283 значит, что Rust знает, что должно уметь твоё значение, но это умеют многие типы, и выбирать за тебя Rust не станет. Обычно эта ошибка встречается сразу после .sum(), .collect() или .into(). Её очень легко исправить: называешь нужный тип — и готово. Остальные ошибки из этой серии ищи на странице Ошибки компилятора Rust.
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»error[E0283]: type annotations needed --> src/main.rs:3:9 |3 | let total = prices.iter().sum(); | ^^^^^ --- type must be known at this point | = note: the type must implement `Sum<&i32>` = help: the following types implement trait `Sum<A>`: `Duration` implements `Sum<&'a Duration>` `Duration` implements `Sum` `Option<T>` implements `Sum<Option<U>>` `Result<T, E>` implements `Sum<Result<U, E>>` `Saturating<u128>` implements `Sum<&'a Saturating<u128>>` `Saturating<u128>` implements `Sum` `Saturating<u16>` implements `Sum<&'a Saturating<u16>>` `Saturating<u16>` implements `Sum` and 88 othersnote: required by a bound in `std::iter::Iterator::sum`help: consider giving `total` an explicit type |3 | let total: /* Type */ = prices.iter().sum(); | ++++++++++++Компилятор говорит: нужна аннотация типа, тип должен реализовывать Sum<&i32>, и таких типов много. Вот программа, которая выдаёт эту ошибку:
fn main() { let prices = vec![250, 120, 99]; // цены в центах let total = prices.iter().sum(); println!("Итого: {total} центов");}.collect() и .into() дают ту же ошибку, только с другой пометкой. Для let words = text.split(' ').collect(); пометка будет the type must implement `FromIterator<&str>`. Для let name = "Ferris".into(); — the type must implement `From<&str>`.
Что это значит
Заголовок раздела «Что это значит»Читай сообщение сверху вниз:
^^^^^подtotal: это переменная, тип которой Rust не смог определить.---подsum: «type must be known at this point» (здесь тип уже должен быть известен).sumможет вернуть много разных типов, поэтому Rust нужен ответ именно здесь.- Пометка (note):
the type must implement `Sum<&i32>`. Это правило, которое нашёл Rust: «totalдолжен быть типом, который можно получить, складывая значения&i32». - Список в help: типы, которые умеют быть «суммой чего-то». Их много: все числовые типы,
Duration, дажеOptionиResult. Rust показывает восемь и пишет «and 88 others» (и ещё 88). Именно это имеют в виду, когда говорят multiple impls satisfy this bound: этому правилу соответствуютimplу нескольких типов.
Представь, что просишь в кафе «что-нибудь попить». Это настоящее правило (должно быть питьё), но под него подходят и чай, и сок, и вода. Официант ничего не принесёт, пока ты не выберешь. E0283 — это Rust, который стоит с подносом и ждёт твоего ответа.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»sum, collect и into обобщены по типу возвращаемого значения. Что делает функция, решает тип, который просишь ты. Трейт (Sum, FromIterator, From) — это только правило. Поэтому, когда ничто не подсказывает Rust тип, у него есть правило, но нет единственного ответа.
А ответ важен. Если собрать одни и те же слова в Vec<&str>, дубликаты останутся, а если в HashSet<&str> — исчезнут. Сумма в u8 переполнится на 255, а в u64 — нет. Если бы Rust выбирал тип за тебя, крошечная правка где-то в другом месте могла бы тихо изменить поведение программы. Rust предпочитает спросить один раз.
Как Rust сам выясняет типы (вывод типов, type inference), объясняется на странице E0282, поэтому здесь не повторяем. О трейтах, которые стоят за этими методами, читай в уроках Трейты и Обобщения.
E0282, E0283, E0284: одна проблема, три кода
Заголовок раздела «E0282, E0283, E0284: одна проблема, три кода»Все три говорят «type annotations needed» (нужна аннотация типа), и для тебя они значат одно и то же: напиши тип. Код лишь подсказывает, чем был занят Rust, когда застрял:
- E0282: у Rust нет вообще никакой подсказки о типе, или тип понадобился ему раньше, чем встретилась подсказка (например, чтобы найти метод вроде
.len()). - E0283 (эта страница): у Rust есть подсказка в виде правила-трейта (
must implement `Sum<&i32>`), но этому правилу соответствует много типов. - E0284: правило касается типа, который принадлежит неизвестному типу, например типа ошибки у
parse(cannot satisfy `<_ as FromStr>::Err == _`). Rust не может его проверить, пока не узнает основной тип.
Код может даже меняться прямо по ходу набора. let words = text.split(' ').collect();, а за ним println!("{:?}", words) — это E0283. Поменяй следующую строку на words.len(), и это станет E0282, потому что теперь Rust нужен тип, чтобы найти метод. Исправления ниже работают для всех трёх.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: укажи тип переменной
Заголовок раздела «Исправление 1: укажи тип переменной»Выбирай этот вариант в большинстве случаев. Добавь : Type после имени переменной. Это коротко, и следующий читатель сразу видит, что именно ты собираешь:
use std::collections::HashSet;
fn main() { let prices = vec![250, 120, 99]; let total: u32 = prices.iter().sum();
let tags = "rust,web,rust,cli"; let list: Vec<&str> = tags.split(',').collect(); let unique: HashSet<&str> = tags.split(',').collect();
println!("Итого: {total} центов"); println!("{:?}", list); println!("Уникальных тегов: {}", unique.len());}Вывод:
Итого: 469 центов["rust", "web", "rust", "cli"]Уникальных тегов: 3Один и тот же collect(), два разных результата: тип, который ты пишешь, и правда работает как инструкция. Содержимое можно оставить Rust через _, как в let list: Vec<_> = ....
Исправление 2: «турборыба» у sum и collect
Заголовок раздела «Исправление 2: «турборыба» у sum и collect»Выбирай этот вариант, когда нет переменной, к которой можно приписать тип: внутри println!, посреди цепочки итераторов или в аргументе функции. Укажи тип прямо в вызове через ::<Type> (так называемая «турборыба», turbofish):
fn main() { let prices = vec![250, 120, 99]; println!("Итого: {} центов", prices.iter().sum::<u32>());
let line = "tea,cake,soup"; let items = line.split(',').collect::<Vec<_>>(); let initials = line.split(',').map(|s| &s[..1]).collect::<String>();
println!("{:?}", items); println!("Первые буквы: {initials}");}Вывод:
Итого: 469 центов["tea", "cake", "soup"]Первые буквы: tcsСлова здесь остались на английском, потому что &s[..1] берёт первый байт, а кириллическая буква занимает два байта — такой срез вызвал бы панику. .product::<u64>() работает так же. Исправления 1 и 2 одинаково правильные, так что выбирай то, что лучше читается.
Исправление 3: для .into() назови целевой тип (турборыба не сработает)
Заголовок раздела «Исправление 3: для .into() назови целевой тип (турборыба не сработает)»Выбирай этот вариант для .into() и .try_into(). Хочется написать "Ferris".into::<String>(), но это другая ошибка, потому что параметр типа принадлежит трейту Into, а не методу:
error[E0107]: method takes 0 generic arguments but 1 generic argument was supplied --> src/main.rs:2:25 |2 | let name = "Ferris".into::<String>(); | ^^^^ expected 0 generic argumentsВместо этого укажи тип переменной или вызови From прямо у целевого типа — это ещё и хорошо читается:
fn main() { let name: String = "Ferris".into(); let city = String::from("Лиссабон");
let small: u8 = 200; let big: u64 = small.into(); let also_big = u64::from(small);
let level: i64 = 42; let byte: u8 = level.try_into().unwrap(); let too_big = u8::try_from(300_i64);
println!("{name} живёт в городе {city}"); println!("{big} {also_big} {byte}"); println!("300 помещается в u8? {}", too_big.is_ok());}Вывод:
Ferris живёт в городе Лиссабон200 200 42300 помещается в u8? falseString::from(...) и u64::from(...) называют целевой тип прямо в начале строки, поэтому многие предпочитают их вместо .into(), когда других подсказок нет. Смотри From и Into: преобразования, которые работают всегда.
Исправление 4: используй значение там, где его тип известен
Заголовок раздела «Исправление 4: используй значение там, где его тип известен»Выбирай этот вариант, когда значение всё равно попадает туда, где тип известен, например в параметр функции или поле структуры. Rust читает функцию целиком, так что такого использования позже достаточно:
fn print_receipt(total: u32, items: Vec<&str>) { println!("товаров: {}, сумма: {} центов", items.len(), total);}
fn main() { let prices = vec![250, 120, 99]; let total = prices.iter().sum(); // u32 благодаря print_receipt let items = "чай,торт,суп".split(',').collect(); // Vec<&str> по той же причине print_receipt(total, items);}Вывод:
товаров: 3, сумма: 469 центовНе всякое использование подходит. if prices.iter().sum() > 400 — всё равно E0283, потому что > умеет сравнивать много пар типов. Передача значения в обобщённый параметр вроде impl Display тоже не поможет: Display реализуют многие типы. Использование должно называть ровно один конкретный тип.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается».collect() прямо в println!. println!("{:?}", line.split(',').collect()); даёт E0283, и rustc сам предлагает исправление: .collect::<Vec<_>>().
.try_into().unwrap(). let small = big.try_into().unwrap(); даёт E0283 с the type must implement `TryFrom<i64>`. Исправление: let small: u8 = big.try_into().unwrap(); или u8::try_from(big).unwrap().
Default::default(), переданный в обобщённую функцию. С fn show(value: impl Debug) вызов show(Default::default()) — это E0283: Rust не может понять, чьё значение по умолчанию тебе нужно. Исправление: show(Settings::default()) или show(u32::default()). Отдельной строкой let s = Default::default(); вместо этого даёт E0790; смотри Default: разумное начальное значение.
Твоя собственная обобщённая функция. Если параметр типа встречается только в типе возвращаемого значения, как в fn make<T: Default>() -> T, то let v = make(); — это E0283. Исправление: make::<Settings>() или let v: Settings = make();.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0282: нужна аннотация типа, когда у Rust нет вообще никакой подсказки о типе (или тип нужен для вызова метода).
- E0284: нужна аннотация типа с
cannot satisfy `<_ as FromStr>::Err == _`, почти всегда из-за.parse(). - E0790:
Default::default()(или другая функция трейта) вызвана без указания типа. - E0277: ты назвал тип, но его нельзя так построить, например
let n: Vec<i32> = "1 2".split(' ').collect();. - E0107: неверное число обобщённых аргументов — это то, что даёт
.into::<String>(). - Все ошибки компилятора: полный список страниц об ошибках.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Что говорит пометка E0283 the type must implement Sum<&i32>?
2. Какая строка исправляет let name = "Ferris".into();?
3. Почему одни и те же слова, собранные в Vec<&str> и в HashSet<&str>, дают разный результат?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0283 — Rust error codes index (на английском): официальное объяснение с классическим примером: во что превратить перевёрнутые
chars()— вVec<char>или вString? - Обработка последовательности элементов с помощью итераторов — книга «Язык программирования Rust» (перевод на русский): как
sumиcollectпоглощают итератор, с примерами с аннотациями. Iterator::collect— документация стандартной библиотеки (на английском): стиль с аннотацией и стиль с турборыбой рядом.Sum— документация стандартной библиотеки (на английском): трейт, который стоит за.sum(), и полный список типов, которые его реализуют.Into— документация стандартной библиотеки (на английском): почему реализуютFrom, а вызывают.into(), и как выбирается целевой тип.