Skip to content

Rust Error E0283: Type Annotations Needed for sum, collect and into

E0283 means Rust knows what your value must be able to do, but many types can do it, and Rust won’t pick one for you. You’ll usually meet it right after .sum(), .collect() or .into(). It’s one of the friendliest errors to fix: you name the type you want, and you’re done. For the other errors in this series, see Rust compiler errors.

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();
| ++++++++++++

This is the program that produces it:

fn main() {
let prices = vec![250, 120, 99]; // prices in cents
let total = prices.iter().sum();
println!("Total: {total} cents");
}

.collect() and .into() give the same error with a different note. For let words = text.split(' ').collect(); the note is the type must implement `FromIterator<&str>`. For let name = "Ferris".into(); it is the type must implement `From<&str>`.

Read the message from top to bottom:

  1. ^^^^^ under total: this is the variable whose type Rust couldn’t decide.
  2. --- under sum: “type must be known at this point”. sum can return many different types, so Rust needs the answer here.
  3. The note: the type must implement `Sum<&i32>`. This is the rule Rust found: “total must be a type that can be made by adding up &i32 values”.
  4. The help list: the types that know how to be “a sum of something”. There are many of them: every number type, Duration, even Option and Result. Rust shows eight and says “and 88 others”. That’s what people mean by multiple impls satisfy this bound: more than one type has an impl that follows the rule.

Imagine asking a café for “something to drink”. That’s a real rule (it must be drinkable), but tea, juice and water all qualify. The waiter can’t bring anything until you choose. E0283 is Rust holding the tray and waiting for your answer.

sum, collect and into are generic in their return type. The type that you ask for decides what the function does. The trait (Sum, FromIterator, From) is only the rule. So when nothing tells Rust the type, it has a rule but no single answer.

And the answer matters. Collecting the same words into a Vec<&str> keeps duplicates, but collecting them into a HashSet<&str> removes them. Summing into a u8 overflows at 255, while a u64 doesn’t. If Rust picked a type for you, a tiny edit somewhere else could quietly change how your program behaves. Rust prefers to ask once.

The way Rust works out types on its own (type inference) is explained on the E0282 page, so it isn’t repeated here. For the traits behind these methods, see Traits and Generics.

E0282, E0283, E0284: one problem, three codes

Section titled “E0282, E0283, E0284: one problem, three codes”

All three say “type annotations needed”, and for you they mean the same thing: write the type. The code only tells you what Rust was doing when it got stuck:

  • E0282: Rust has no clue at all about the type, or it needed the type before reaching the clue (for example, to look up a method like .len()).
  • E0283 (this page): Rust has a clue in the form of a trait rule (must implement `Sum<&i32>`), but many types follow that rule.
  • E0284: the rule is about a type that belongs to the unknown type, like the error type of parse (cannot satisfy `<_ as FromStr>::Err == _`). Rust can’t check it until it knows the main type.

The code can even change while you type. let words = text.split(' ').collect(); followed by println!("{:?}", words) is E0283. Change the next line to words.len() and it becomes E0282, because now Rust needs the type to find the method. The fixes below work for all three.

Choose this most of the time. Add : Type after the variable name. It’s short, and it tells the next reader exactly what you’re building:

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: {total} cents");
println!("{:?}", list);
println!("Unique tags: {}", unique.len());
}

Output:

Total: 469 cents
["rust", "web", "rust", "cli"]
Unique tags: 3
▶ Try it Yourself

Same collect(), two different results: the type you write really is the instruction. You can leave the inside to Rust with _, as in let list: Vec<_> = ....

Fix 2: Use the turbofish on sum and collect

Section titled “Fix 2: Use the turbofish on sum and collect”

Choose this when there’s no variable to annotate: inside a println!, in the middle of an iterator chain, or as a function argument. Put the type on the call with ::<Type> (the “turbofish”):

fn main() {
let prices = vec![250, 120, 99];
println!("Total: {} cents", 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: {initials}");
}

Output:

Total: 469 cents
["tea", "cake", "soup"]
Initials: tcs
▶ Try it Yourself

.product::<u64>() works the same way. Fix 1 and Fix 2 are equally correct, so pick the one that reads better.

Fix 3: For .into(), name the target type (the turbofish won’t work)

Section titled “Fix 3: For .into(), name the target type (the turbofish won’t work)”

Choose this for .into() and .try_into(). It’s tempting to write "Ferris".into::<String>(), but that’s a different error, because the type parameter belongs to the Into trait, not to the method:

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

Instead, annotate the variable, or call From on the target type directly, which reads nicely too:

fn main() {
let name: String = "Ferris".into();
let city = String::from("Lisbon");
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} lives in {city}");
println!("{big} {also_big} {byte}");
println!("300 fits in a u8? {}", too_big.is_ok());
}

Output:

Ferris lives in Lisbon
200 200 42
300 fits in a u8? false
▶ Try it Yourself

String::from(...) and u64::from(...) say the target type right at the start of the line, so many people prefer them over .into() when there’s no other clue. See Conversions.

Fix 4: Use the value where its type is fixed

Section titled “Fix 4: Use the value where its type is fixed”

Choose this when the value goes somewhere with a known type anyway, like a function parameter or a struct field. Rust reads the whole function, so that later use is enough:

fn print_receipt(total: u32, items: Vec<&str>) {
println!("{} items, {} cents", items.len(), total);
}
fn main() {
let prices = vec![250, 120, 99];
let total = prices.iter().sum(); // u32, thanks to print_receipt
let items = "tea,cake,soup".split(',').collect(); // Vec<&str>, same reason
print_receipt(total, items);
}

Output:

3 items, 469 cents
▶ Try it Yourself

Not every use counts. if prices.iter().sum() > 400 is still E0283, because > can compare many pairs of types. Passing the value to a generic parameter like impl Display doesn’t help either, since many types are Display. The use must name one exact type.

.collect() straight into println!. println!("{:?}", line.split(',').collect()); gives E0283, and rustc suggests the fix itself: .collect::<Vec<_>>().

.try_into().unwrap(). let small = big.try_into().unwrap(); gives E0283 with the type must implement `TryFrom<i64>`. Fix: let small: u8 = big.try_into().unwrap(); or u8::try_from(big).unwrap().

Default::default() passed to a generic function. With fn show(value: impl Debug), the call show(Default::default()) is E0283: Rust can’t tell whose default you want. Fix: show(Settings::default()) or show(u32::default()). On its own line, let s = Default::default(); gives E0790 instead; see Default.

Your own generic function. If a type parameter only appears in the return type, like fn make<T: Default>() -> T, then let v = make(); is E0283. Fix: make::<Settings>() or let v: Settings = make();.

  • E0282: type annotations needed, when Rust has no clue at all about the type (or needs it for a method call).
  • E0284: type annotations needed with cannot satisfy `<_ as FromStr>::Err == _`, nearly always from .parse().
  • E0790: Default::default() (or another trait function) called without saying which type.
  • E0277: you named a type, but it can’t be built that way, like let n: Vec<i32> = "1 2".split(' ').collect();.
  • E0107: wrong number of generic arguments, which is what .into::<String>() gives you.
  • All compiler errors: the full list of error pages.

Quick check

1. What does E0283's note the type must implement Sum<&i32> tell you?

2. Which line fixes let name = "Ferris".into();?

3. Why does collecting the same words give different results into Vec<&str> and HashSet<&str>?

Score: 0 / 3