How to fix Rust E0283 ambiguous type

E0283 means Rust's type inference saw multiple matching candidates and refused to guess. Fix it with a binding annotation or turbofish syntax on the call.

The compiler asking for help

Rust is famous for figuring out what type you meant. You write let x = 5; and it works out x: i32 from context. You write let v = Vec::new(); and as soon as you push an i32 into it, the compiler walks that fact backwards and decides v is Vec<i32>. This works almost all the time. Almost.

Sometimes the inference engine genuinely doesn't have enough clues to pick a type. You'll see something like this:

error[E0283]: type annotations needed
 --> src/main.rs:3:9
  |
3 |     let n = "42".parse().unwrap();
  |         ^   --------- type must be known at this point
  |
  = note: cannot satisfy `_: FromStr`
help: consider giving `n` an explicit type
  |
3 |     let n: /* Type */ = "42".parse().unwrap();
  |          +++++++++++++

That's E0283. The error message even calls it "type annotations needed." The compiler is saying: I see you're calling parse, which can produce many different types (i32, i64, f64, bool, anything implementing FromStr). I have no information that lets me pick one. You have to tell me.

This isn't a sign of a broken compiler or broken code. It's the compiler refusing to guess. Rust would rather you be explicit than silently produce a bool when you meant i32.

Why E0283 happens

The error comes up in any situation where multiple types could satisfy a generic constraint at the same point in your code. Two common scenarios.

Scenario 1: a generic method with no usage. parse() returns Result<F, F::Err> for any F: FromStr. If you call it and don't immediately use the result in a way that fixes the type, the compiler is stuck.

fn main() {
    // No type for the binding, no use that constrains it.
    let n = "42".parse().unwrap();
    println!("{:?}", n);   // Debug works for any type, doesn't help inference.
}

Scenario 2: a function with multiple matching impls. Default::default() exists for hundreds of types. Without context, the compiler can't know which one you mean.

fn main() {
    let x = Default::default();
    println!("{:?}", x);
}

In both cases, the fix is to give the compiler one piece of information: the desired type.

Two ways to provide the type

A type annotation on the binding

fn main() {
    // The `i32` here is what the compiler needs. parse() picks the i32 impl.
    let n: i32 = "42".parse().unwrap();
    println!("{}", n + 1);
}

This is the most common fix. Stick a colon and a type after the variable name. Read top-to-bottom, it tells whoever's reading "this is an integer," which is also useful documentation.

Turbofish syntax on the call

When you don't have a binding (you're chaining calls or passing the result directly), the binding-annotation trick won't work. Instead, you can pass the type to the generic function directly with the turbofish operator ::<T>.

fn main() {
    // Turbofish: tell parse() that we want an i32 result.
    let n = "42".parse::<i32>().unwrap();

    // Same idea inside an expression.
    println!("{}", "100".parse::<u64>().unwrap() * 2);
}

The ::<T> is officially called the turbofish because it looks like a fish swimming through punctuation. The official Rust docs use the term unironically, so we will too. Practically, it's "explicit generic argument list" but with a fun name.

Use turbofish when:

  • You don't want to introduce a let binding.
  • The function returns the generic, and the result flows straight into the next call.

Use a binding annotation when:

  • You're storing the result in a variable anyway.
  • You want the type visible at a glance for the reader.

Both are correct. Pick the one that reads better in context.

The collect() classic

The single most common place E0283 shows up is collect. iterator.collect() can produce a Vec, a HashMap, a String, a HashSet, anything implementing FromIterator. The compiler needs to know which.

fn main() {
    // Bad: no type, ambiguous.
    // let nums = (0..5).collect();   // E0283

    // Good: turbofish on collect.
    let nums = (0..5).collect::<Vec<i32>>();

    // Equally good: annotate the binding instead.
    let evens: Vec<i32> = (0..10).filter(|n| n % 2 == 0).collect();

    // Mixed: annotate the binding partially, leave the element type to inference.
    let chars: Vec<_> = "hello".chars().collect();

    println!("{:?} {:?} {:?}", nums, evens, chars);
}

The last form, Vec<_>, is a useful compromise: you tell the compiler you want a Vec, but you let it figure out the element type from the iterator. That's often the cleanest writing.

Default and other "create from nothing" calls

Default::default() is the other big one. It's a trait method that returns whatever Self is, and Self has no source.

use std::collections::HashMap;

fn main() {
    // Without context, ambiguous.
    // let map = Default::default();

    // Annotate the binding.
    let map: HashMap<String, i32> = Default::default();

    // Or use the type's own associated function (often clearer).
    let map2 = HashMap::<String, i32>::new();

    println!("{} {}", map.len(), map2.len());
}

When you're calling Type::default() rather than Default::default(), the type is already known and the error doesn't appear. Worth knowing as a stylistic preference: many codebases write HashMap::new() over Default::default() exactly so the type is obvious without extra annotations.

A more realistic example: parsing many things

In real code, you'll often parse a sequence of inputs into a vector of typed values. This is a place where one annotation cleans up everything.

fn main() {
    let raw = "10 20 30 40";

    // The annotation `Vec<i32>` flows backwards through every iterator step.
    // parse() picks i32 because the surrounding Vec wants i32 elements.
    let nums: Vec<i32> = raw
        .split_whitespace()
        .map(|s| s.parse().unwrap())
        .collect();

    println!("{:?}", nums);
}

The single : Vec<i32> annotation tells collect it produces a Vec<i32>, which tells map its closure must return i32, which tells parse to produce i32. One annotation, four inference points fixed. That's the inference engine doing its job; we just had to give it one fact.

Common pitfalls

You wrote let x = something.parse().unwrap() and saw E0283. Add : SomeType after x or use turbofish on parse.

You used Default::default() and got E0283. Annotate the binding or use the type's own constructor (HashMap::new(), Vec::new(), etc.).

You wrote vec.iter().sum() and saw E0283 because sum is generic over the result type. Use vec.iter().sum::<i32>() or annotate the binding.

You hit E0283 in a context where adding a type isn't natural (e.g. a closure parameter). Try a different shape: bind to a local, annotate that, then use it.

You confused E0283 with E0282 ("type annotations needed"). E0282 means inference saw nothing at all. E0283 means inference saw multiple matching candidates and couldn't pick. The fix is the same: provide a type. The error code just tells you which side of the decision tree the compiler got stuck on.

When inference fails, lean into types

The pattern across all these fixes: when the compiler asks for a type, give it the smallest hint it needs and no more. Often that's Vec<_> or : i32. The aim is readable code where the interesting types are visible and the obvious types are inferred.

Some teams adopt a style of always annotating top-level let bindings, just to make code skim-able. That's a taste choice; either is fine. The annotation is free at runtime; you're communicating intent to the next person who reads the file.

Where to go next

Other inference and type-related compiler errors share the same shape: read what the compiler is asking for, give it the minimum, move on.

How to fix Rust E0277 Iterator not implemented

How to fix Rust E0382 value used after being moved

How to fix Rust E0507 cannot move out of dereference of shared reference