Strange type inference error


#1

I stumbled upon a strange compile error today, that I have minimized to the following example:

#[derive(Copy, Clone, Debug)]
struct Element {
    next: Option<usize>,
}

fn main() {
    let array = [Element { next: None }; 16];
    let number: usize = 42;
    let mut option = None;

    match option {
        Some(n) => {
            // This alone compiles
            let bar = array[n];
            println!("Number is {:?}", bar);

            // The following doesn't compile:

            // error[E0282]: type annotations needed
            //  --> src/main.rs:19:23
            //    |
            // 19 |             let foo = array[n].next;
            //    |                       ^^^^^^^^^^^^^ cannot infer type for `_`

            let foo = array[n].next;
            println!("Number is {:?}", foo);
        }
        None => option = Some(number),
    }

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

What’s strange is that when I add a purposefully wrong type annotation like this:

let bar: bool = array[n];

the compiler correctly recognizes that bool is the wrong type and even tells me the correct type:

error[E0271]: type mismatch resolving `<usize as std::slice::SliceIndex<[Element]>>::Output == bool`
  --> src/main.rs:14:29
   |
14 |             let bar: bool = array[n];
   |                             ^^^^^^^^ expected struct `Element`, found bool
   |
   = note: expected type `Element`
              found type `bool`

However, as soon as I try to access a field, type inference fails. (Basically it knows that array[n] is of type Element, but later cannot infer the type of array[n].next.)

(If I move the None arm of the match up to the beginning, type inference succeeds…)

Is this expected behaviour?


#2

It doesn’t know what option is:

let mut option = None;

The error surfaces in the wrong place, however.


#3

I understood that much (took me some time, however, because I found the error confusing at first). Changing that line to
let mut option: Option<usize> = None;
made it compile.

However, what confuses me is that the line with bar compiles, whereas the one with foo doesn’t. The only difference is the additional field access…


#4

My guess is it’s an artifact of how typeck + inference work (field access triggers typeck and it realizes it doesn’t have enough info here overall), and possibly a (known) bug/limitation. It’s definitely strange because it clearly allowed indexing the first time by inferring what n is.

File a github issue - it’s likely this is known, but you’ll get a more thorough explanation perhaps.


#5

Thanks, I opened an issue on the Rust repo.