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| -> hyper::Error {
::std::convert::From::<std::io::Error>::from(x)
})
The type definition of From::from()
seems to be fn from(T) -> Self;
How is it that what seems to be a std::io::Error -> Self
seems to return a hyper::Error
value, when none of the generics and arguments I give it are of the type hyper::Error
?
It seems that some sort of implicit type conversion is happening even when I specify all the types explicitly?
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 typeY
. 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:
And start from
main
:v => A
,1 => B
,A = Vec<C>
(fromv = Vec::new()
),C = B
(fromv.push(1)
),A = &[u32]
OR<A as Deref>::Output = &[u32]
OR...
(fromprint_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.
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 behyper::Error
, therefore the result ofFrom::from
must behyper::Error
.If you wanted to, you could use ...
... which would be the even more fully qualified version. But with the closure return type there, it's unnecessary.