This code compiles:
#[derive(Debug, Default)]
struct Example;
impl Example {
fn some_method(&self) {}
}
fn reproduction() -> Example {
let example = Default::default();
// example.some_method();
example
}
If the commented line is added back, it will cause an error:
error[E0282]: type annotations needed
--> src/lib.rs:10:5
|
9 | let example = Default::default();
| ------- consider giving `example` a type
10 | example.some_method();
| ^^^^^^^ cannot infer type
|
= note: type must be known at this point
Why does adding this method call cause type inference to fail?
I've seen these two questions:
From them, I know that Rust uses a (modified) version of Hindley-Milner. The latter question has an answer that describes Rust's type inference as a system of equations. Another answer explicitly states that "Type information in Rust can flow backwards".
Using this knowledge applied to this situation, we have:
example
is type ?E
?E
must have a method called some_method
?E
is returnedExample
Working backward, it's easy for a human to see that ?E
must be Example
. Where is the gap between what I can see and what the compiler can see?
Based on known facts (see below), it fails to compile because:
let example = Default::default();
, example
can be anything which implements Default
,Default
" is not a known type.I replaced some_method()
with a field access and it produces same error.
From Type inference depends on ordering (#42333):
use std::path::PathBuf; pub struct Thing { pub f1: PathBuf, } fn junk() -> Vec<Thing> { let mut things = Vec::new(); for x in vec![1, 2, 3] { if x == 2 { for thing in things.drain(..) { thing.f1.clone(); } return vec![] } things.push(Thing{f1: PathBuf::from(format!("/{}", x))}); } things } fn main() { junk(); }
This produces a compiler error with Rust 1.33.0:
error[E0282]: type annotations needed
--> src/main.rs:13:17
|
9 | let mut things = Vec::new();
| ---------- consider giving `things` a type
...
13 | thing.f1.clone();
| ^^^^^ cannot infer type
|
= note: type must be known at this point
You should focus on the following comments from eddyb (a well-known member of the the Rust language design team since May, 2016).
This is a known limitation of the in-order type-checker. While inference flows freely,
thing.f1.clone()
is checked beforethings.push(Thing {...})
so it isn't known thatthing: Thing
when you try to access thef1
field. We may in the future move away from this, but there are no immediate plans.
What's more important is comment #2:
What I mean is that the type-checker goes through the function in the order it was written. [...] Fields accesses and methods calls are simply not supported unless the type is already known.