Type inference

Type inference is useful in statically typed languages as it makes the code easier to write, maintain, and refactor. Rust's type system can figure out types for fields, methods, local variables, and most generic type arguments when you don't specify them. Under the hood, a component of the compiler called the type checker uses the Hindley Milner type inference algorithm to decide what the types of local variables should be. It is a set of rules about establishing types of expressions based on their usage. As such, it can infer types based on the environment and the way a type is used. One such example is the following:

let mut v = vec![];
v.push(2); // can figure type of `v` now to be of Vec<i32>

With only the first line initializing the vector, Rust's type checker is unsure of what the type for v should be. It's only when it reaches the next line, v.push(2), that it knows that v is of the type, Vec<i32>. Now the type of v is frozen to Vec<i32>.

If we added another line, v.push(2.4f32);, then the compiler will complain of type mismatch as it already had inferred it from the previous line to be of Vec<i32>. But sometimes, the type checker cannot figure out types of variables in complex situations. But with some help from the programmer, the type checker is able to infer types. For example, for the next snippet, we read a file foo.txt, containing some text and read it as bytes:

// type_inference_iterator.rs

use std::fs::File;
use std::io::Read;

fn main() {
let file = File::open("foo.txt").unwrap();
let bytes = file.bytes().collect();
}

Compiling this gives us this error:

The collect method on iterators is basically an aggregator method. We'll look at iterators later in this chapter. The resulting type it collects into can be any collection type. It can either be LinkedList, VecDeque, or Vec. Rust does not know what the programmer intends and, due to such ambiguity, it needs our help here. We made the following change for the second line in main:

 let bytes: Vec<Result<u8, _>> = file.bytes().collect();

Calling bytes() returns Result<u8, std::io::Error>. After adding some type hint as to what to collect into (here, Vec), the program compiles fine. Note the _ on the Result error variant. It was enough for Rust to hint that we need a Vec of Result of u8. The rest, it is able to figure out—the error type in Result needs to be of std::io::Error type. It was able to figure that out because there is no such ambiguity here. It gets the information from the bytes() method signature. Quite smart!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset