rusttype-inferencetype-systemshindley-milner

Why does calling a method on a variable prevent Rust from inferring the type of the variable?


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:

  1. example is type ?E
  2. ?E must have a method called some_method
  3. ?E is returned
  4. The return type is Example

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?


Solution

  • Based on known facts (see below), it fails to compile because:

    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).

    Comment #1:

    This is a known limitation of the in-order type-checker. While inference flows freely, thing.f1.clone() is checked before things.push(Thing {...}) so it isn't known that thing: Thing when you try to access the f1 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.