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

Ошибка 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 others
note: 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>`.

Читай сообщение сверху вниз:

  1. ^^^^^ под total: это переменная, тип которой Rust не смог определить.
  2. --- под sum: «type must be known at this point» (здесь тип уже должен быть известен). sum может вернуть много разных типов, поэтому Rust нужен ответ именно здесь.
  3. Пометка (note): the type must implement `Sum<&i32>`. Это правило, которое нашёл Rust: «total должен быть типом, который можно получить, складывая значения &i32».
  4. Список в help: типы, которые умеют быть «суммой чего-то». Их много: все числовые типы, Duration, даже Option и Result. Rust показывает восемь и пишет «and 88 others» (и ещё 88). Именно это имеют в виду, когда говорят multiple impls satisfy this bound: этому правилу соответствуют impl у нескольких типов.

Представь, что просишь в кафе «что-нибудь попить». Это настоящее правило (должно быть питьё), но под него подходят и чай, и сок, и вода. Официант ничего не принесёт, пока ты не выберешь. E0283 — это Rust, который стоит с подносом и ждёт твоего ответа.

sum, collect и into обобщены по типу возвращаемого значения. Что делает функция, решает тип, который просишь ты. Трейт (Sum, FromIterator, From) — это только правило. Поэтому, когда ничто не подсказывает Rust тип, у него есть правило, но нет единственного ответа.

А ответ важен. Если собрать одни и те же слова в Vec<&str>, дубликаты останутся, а если в HashSet<&str> — исчезнут. Сумма в u8 переполнится на 255, а в u64 — нет. Если бы Rust выбирал тип за тебя, крошечная правка где-то в другом месте могла бы тихо изменить поведение программы. Rust предпочитает спросить один раз.

Как Rust сам выясняет типы (вывод типов, type inference), объясняется на странице E0282, поэтому здесь не повторяем. О трейтах, которые стоят за этими методами, читай в уроках Трейты и Обобщения.

Все три говорят «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 нужен тип, чтобы найти метод. Исправления ниже работают для всех трёх.

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

Выбирай этот вариант, когда нет переменной, к которой можно приписать тип: внутри 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 42
300 помещается в u8? false
▶ Попробуй сам

String::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