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() {
//...
}