How does Vec::new() know what is the requested element type?

Viewed 416

I can create vector of any type like this:

let mut vec: Vec<u32> = Vec::new();

How does new() know what is requested vector element type when (unlike C++) it is not parametrized with type name?

On the other hand: Can I specify the type explicitely? This doesn't seem to compile:

let mut vec = Vec<u32>::new();
3 Answers

The Rust compiler uses a (slightly modified) Hindley-Milner type inference algorithm. Basically, all type information you provide in the source code is used as constraints on the unbound generic type parameters, and a constraint solver then tries to figure out all unknowns. In the case of the assignment

let vec: Vec<u32> = Vec::new();

there is one unbound generic type parameter on the right-hand side – the T parameter of Vec<T>. The compiler remembers that unbound type parameter as yet to be determined. By looking at the prototype of Vec::new(), the compiler can see that it returns Vec<T>, so the expression on the right-hand side has type Vec<T>, with T still being unbound. Based on the type annotation on the left-hand side, the compiler knows that this type is supposed to be Vec<u32>, so it can infer that T must be u32.

This is a relatively simple case. The constraint solver is able to handle much more complex situations as well.

You can explicitly specify the type on the right-hand side using the "turbofish" operator ::<>:

let vec = Vec::<u32>::new();

On the other hand: Can I specify the type explicitely? This doesn't seem to compile:

let mut vec = Vec<u32>::new();

The use of <> to specify generic arguments is ambiguous in an expression context where < could be the "less than" operator.

Thus, whenever you want to specify generic parameters explicitly in an expression context, you need to use the so-called "turbofish" operator to disambiguate:

let mut vec = Vec::<u32>::new();

This way the distinction is purely syntactic.


How does new() know what is requested vector element type when (unlike C++) it is not parametrized with type name?

The term you are looking for is type inference: the ability of the compiler to infer the type from the context, without it being explicitly specified.

Different languages have different type inference algorithms:

  • C++ has one-way type inference: the type of an expression can be inferred without specifying it, allowing the use of auto to declare a variable.
  • Java has one-way type inference in the other direction for generics: HashMap<String, String> map = new HashMap<>();.
  • Rust has two-way type inference, as it uses a variant of Hindley-Milner, making it so that you rarely have to specify the type within a function body.

For illustrative purpose, imagine the following (Playground link):

fn display(i: u64) {
    println!("{}", i);
}

fn main() {
    let mut vec = Vec::new();

    vec.push(1);
    
    display(vec[0]);
}

The compiler will reason in the following way:

  1. vec has type Vec<?0>.
  2. Vec<?0>::push takes an {integer} as argument, hence ?0 is an {integer}.
  3. <Vec<?0> as Index>::Output is ?0.
  4. display takes a u64 as argument, thus ?0 is u64.

This combination of links (threading ?0 through) allows it to gather 2 constraints:

  • ?0 is an {integer}.
  • ?0 is u64.

Since both constraints are satisfied by ?0 being a u64, then it is inferred to be a u64.

If however you change display to take a &str, then you'll get an error message as inference will fail:

error[E0308]: mismatched types
  --> src/main.rs:10:17
   |
10 |         display(vec[0]);
   |                 ^^^^^^ expected `&str`, found integer

Because satisfying ?0 = &str and ?0 is {integer} at the same time is impossible.

When implementing something for a generic type the compiler is smart enough to elide the type. Even more if you specify the type directly in the variable.

Elided type to Vec<u32>

let mut vec = Vec::new();
vec.push(0u32);

variable declaration:

let mut vec: Vec<u32> = Vec::new();

type especification:

let mut vec = Vec::<u32>::new();

Playground

Related