In this snippet from Hyper\'s example, there\'s a bit of code that I\'ve annotated with types that compiles successfully:
.map_err(|x: std::io::Error| -> hype
Type information in Rust can flow backwards.
The return type of the closure is specified to be hyper::Error
. Therefore, the result of the block must be hyper::Error
, therefore the result of From::from
must be hyper::Error
.
If you wanted to, you could use ...
<hyper::Error as ::std::convert::From>::<std::io::Error>::from(x)
... which would be the even more fully qualified version. But with the closure return type there, it's unnecessary.
Type inference has varying degrees.
For example, in C++ each literal is typed, and only a fully formed type can be instantiated, therefore the type of any expression can be computed (and is). Before C++11, this led to the compiler giving an error message: You are attempting to assign a value of type X
to a variable of type Y
. In C++11, auto
was introduced to let the compiler figure out the type of the variable based on the value that was assigned to it.
In Java, this works slightly differently: the type of a variable has to be fully spelled out, but in exchange when constructing a type the generic bits can be left out since they are deduced from the variable the value is assigned to.
Those two examples are interesting because type information does not flow the same way in both of them, which hints that there is no reason for the flow to go one way or another; there are however technical constraints aplenty.
Rust, instead, uses a variation of the Hindley Milner type unification algorithm.
I personally see Hindley Milner as a system of equation:
For example, imagine the following:
fn print_slice(s: &[u32]) {
println!("{:?}", s);
}
fn main() {
let mut v = Vec::new();
v.push(1);
print_slice(&v);
}
And start from main
:
v => A
, 1 => B
,A = Vec<C>
(from v = Vec::new()
), C = B
(from v.push(1)
), A = &[u32]
OR <A as Deref>::Output = &[u32]
OR ...
(from print_slice(&v)
,A = Vec<B>
, &[B] = &[u32]
,B = u32
, A = Vec<u32>
.There are some difficulties woven into the mix because of subtyping (which the original HM doesn't have), however it's essentially just that.
In this process, there is no consideration for going backward or forwarded, it's just equation solving either way.
This process is known as Type Unification and if it fails you get a hopefully helpful compiler error.