Surprising type inference error

I have following minimal reproducible example (see also on playground):

pub struct Bar {
    value: u32,
}

pub struct Foo {
    pub bars: Vec<Bar>,
}

impl Foo {
    pub fn new(bar: Bar) -> Self {
        let mut bars = vec![];

        if let Some(_) = bars.iter().find(|&item| item.value == bar.value) {
            unreachable!();
        }

        bars.push(bar);

        Self { bars }
    }
}

When running cargo check this fails to compile with following error:

    Checking type-annotation-mre v0.1.0 (/home/akrauze/scratch/type-annotation-mre)
error[E0282]: type annotations needed for `Vec<_>`
  --> src/lib.rs:11:13
   |
11 |         let mut bars = vec![];
   |             ^^^^^^^^
12 |
13 |         if let Some(_) = bars.iter().find(|&item| item.value == bar.value) {
   |                                                   ---------- type must be known at this point
   |
help: consider giving `bars` an explicit type, where the type for type parameter `T` is specified
   |
11 |         let mut bars: Vec<_> = vec![];
   |                     ++++++++

For more information about this error, try `rustc --explain E0282`.
error: could not compile `type-annotation-mre` (lib) due to 1 previous error

I find this very odd, because:

  • it should be possible for the compiler to infer type or bars thanks to bars.push(bar) call
  • rust-analyzer shows correct types inline

I've tried running this with a couple of rust versions and editions, but the only things that changed were wording of error message.

Is this known limitation of the inference system in the compiler? I don't recall encountering anything similar in the past. If so, is it technically solvable (I suspect it is since rust-analyzer can infer types properly) and is it planned in the future?

Or is it some regression/bug in the compiler that should be reported?

1 Like

it has always been like this. a short explanation is: type inference don't "look-ahead" when you access a field or call a method on a value of unknown type.

if you don't access the .value field, then it doesn't need to know the type at the time, something like this should compile:

#[derive(PartialEq, Eq)]
struct Bar {
    value: u32,
}

// at this point, the inference just need some type `T: PartialEq<Bar>`
// which can be further resolved later
if bars.ite.find(|&item| item == &bar).is_some() {
    unreachable!();
}

see also this reply from a recent thread, although that thread was about HashMap instead of Vec.

2 Likes

Thank you. Now it does make sense. In my use-case Bar struct has more fields, but I want to compare based only one one of them. So probably the simplest solution will be to add type annotation to the argument of the closure that is passed as a predicate.

As a developer I prefer it annotated. Otherwise (although one has type-hints today) one would need to scroll (potentially a lot) to see what the type of that item was.

In my view, it's best to simply annotate it: let mut bars:Vec<Bar> = vec![ ]; then the code compiles without error.

But I understand this isn't what you (or many others) expected/wanted.

the reason field access and method call need the type to be resolved is because the type system cannot express the requirement like:

"some type T where T has a field named value"

same for method call, but for trait methods, you can workaround by using the associated function call syntax.

imagine you are the type checker, how can you synthesize the type of the closure (i.e. type P in the signature of Iterator::find())?

fn find<P>(&mut self, predicate: P) -> Option<Self::Item>
where
    Self: Sized,
    P: FnMut(&Self::Item) -> bool,

you can also see why (and how) the PartialEq example would work (pseudo code):

// `T` is unknown in `Vec<T>`, a.k.a. an inference variable
let bars: Vec<T> = vec![];

// `vec::Iter` carries the variable `T`
let iter: vec::Iter<Item = &T> = bars.iter();

// the synthensized type of the predicate closure,
// inferred from the body, without any annotation:
// argument is in the shape of `&U` because of the pattern `&item`
let predicate: impl<U> FnMut(&U) -> bool where U: PartialEq<&Bar>
         = |&item: &U| item == &bar;

// per the signature of `Iterator::find`:
// argument of the predicate, `&U`, is unified with `&Iter::Item`
// i.e.: &U == &Iter::Item == &&T
let maybe_found: Option<&T> = iter.find(predicate);

// per the signature of `Vec<T>::push()`: T == Bar
bars.push(bar);

// the solution based on inference rules:
// T == Bar
// U == &T == &Bar,
// and then, `U: PartialEq<&Bar>` checks

note, this is for illustration only. in reality, the closure is defined inline the call to Iterator::find() and some unification is done before checking the closure's body, which makes the annotation bars: Vec<Bar> work. but you can also annotate the closure instead:

if bars.iter().find(|item: &&Bar| item.value == bar.value).is_some() {
    //...
}