How does Rust infer resultant types from From::<

2019-08-27 16:50发布

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?

标签: rust
2条回答
成全新的幸福
2楼-- · 2019-08-27 17:38

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:

  1. Give each potential type a name: A, B, C, ...
  2. Create equations tying together those types based on the structure of the program.

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:

  1. Assign names to types: v => A, 1 => B,
  2. Put forth some equations: 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),
  3. First round of solving: A = Vec<B>, &[B] = &[u32],
  4. Second round of solving: 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.

查看更多
The star\"
3楼-- · 2019-08-27 17:48

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.

查看更多
登录 后发表回答