Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • How does Rust's type inference work across multiple statements?
  • How does Rust infer resultant types from From::<>::from()?

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?

like image 208
Shepmaster Avatar asked Mar 19 '19 14:03

Shepmaster


People also ask

Does rust use type inference?

Rust performs type inference in fairly advanced situations.

How does rust type inference work?

Type inference means that if you don't tell the compiler the type, but it can decide by itself, it will decide. The compiler always needs to know the type of the variables, but you don't always need to tell it. Actually, usually you don't need to tell it.

How to make a variable mutable in rust?

The mut keyword can be added after let keyword or const keyword to make the variable mutable. Rust compiler can detect the data type of the variable, but you can also explicitly define the data type of the variable. Immutability or an immutable object is an object whose state or valve cannot be modified after it is created or assigned.

Is it safe to use one letter variables in rust-graphics?

The variable can change type, which the design of Rust-Graphics heavily depends on. Each method returns a new type and you can only call .draw (gl) when the context provides enough information to do the drawing. I've always found one letter variables a little dangerous for anything other than loops. Is it really so hard to type cnxt or similar?

What is a type in rust?

Every variable, item, and value in a Rust program has a type. The type of a value defines the interpretation of the memory holding it and the operations that may be performed on the value.

How to prevent rusting?

How to prevent rusting begins right from the way you handle objects made of iron-containing metal. Proper storage away from moisture cannot be overemphasized. Regular maintenance is also necessary. Other ways of preventing rust formation include painting, applying oil, powder coating, among others.


2 Answers

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

  • the type checker goes through the function in the order it was written,
  • in let example = Default::default();, example can be anything which implements Default,
  • field accesses & method calls require a known type,
  • "anything implementing 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).

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.

like image 122
zrzka Avatar answered Oct 21 '22 17:10

zrzka


I don't know the full answer and I have next to no knowledge of the internal workings of the Rust compiler, but here are some deductions I've come to from my experience with Rust.

Information about types in Rust can "flow backwards", but there are certain times when Rust needs to know (for absolute certain) the type of an expression. In these situations, it must "already" know the type, i.e. it will not continue to look forward.

From what I've seen, this situation is limited to method calls. I suspect it has something to do with the fact that methods can be implemented on traits, which substantially complicates things. I doubt that there are any traits in scope with a method named some_method, but I think that whenever the Rust compiler encounters a method call it requires the type to already be known for certain.

You can see this happen a lot with method calls on types which implement traits, the most common being the collect method on a type that implements the Iter trait. You will be able to call collect, but won't be able to call any methods on the result unless you specify the type.

So this works:

fn create_numbers(last_num: i32) -> Vec<i32> {     let x = (0..10).collect();     x } 

But this does not:

fn create_numbers(last_num: i32) -> Vec<i32> {     let x = (0..10).collect();     // In order to call `push`, we need to *already* know the type     // of x for "absolute certain", and the Rust compiler doesn't      // keep looking forward     x.push(42);     x } 
like image 44
Harrison Mc Avatar answered Oct 21 '22 16:10

Harrison Mc