问题
Take a look at this code using the docopt library:
const USAGE: &'static str = "...something...";
#[derive(Deserialize)]
struct Args {
flag: bool,
}
type Result<T> = result::Result<T, Box<error::Error + Send + Sync>>;
fn main() {
let mut args: Args = Docopt::new(USAGE)
.and_then(|d| d.deserialize())
.unwrap_or_else(|e| e.exit());
}
If you look at the expression to the right of equals sign, you'll see that it doesn't mention the Args struct anywhere. How does the compiler deduce the return type of this expression? Can the type information flow in opposite direction (from initialization target to initializer expression) in Rust?
回答1:
"How does it work?" might be too big of a question for Stack Overflow but (along with other languages like Scala and Haskell) Rust's type system is based on the Hindley-Milner type system, albeit with many modifications and extensions.
Simplifying greatly, the idea is to treat each unknown type as a variable, and define the relationships between types as a series of constraints, which can then be solved by an algorithm. In some ways it's similar to simultaneous equations you may have solved in algebra at school.
Type inference is a feature of Rust (and other languages in the extended Hindley-Milner family) that is exploited pervasively in idiomatic code to:
- reduce the noise of type annotations
- improve maintainability by not hard-coding types in multiple places (DRY)
Rust's type inference is powerful and, as you say, can flow both ways. To use Vec<T> as a simpler and more familiar example, any of these are valid:
let vec = Vec::new(1_i32);
let vec = Vec::<i32>::new();
let vec: Vec<i32> = Vec::new();
The type can even be inferred just based on how a type is later used:
let mut vec = Vec::new();
// later...
vec.push(1_i32);
Another nice example is picking the correct string parser, based on the expected type:
let num: f32 = "100".parse().unwrap();
let num: i128 = "100".parse().unwrap();
let address: SocketAddr = "127.0.0.1:8080".parse().unwrap();
So what about your original example?
- Docopt::new returns a
Result<Docopt, Error>, which will beResult::Err<Error>if the supplied options can't be parsed as arguments. At this point, there is no knowledge of if the arguments are valid, just that they are correctly formed. - Next, and_then has the following signature:
The variablepub fn and_then<U, F>(self, op: F) -> Result<U, E> where F: FnOnce(T) -> Result<U, E>,selfhas typeResult<T, E>whereTisDocoptandEisError, deduced from step 1.Uis still unknown, even after you supply the closure|d| d.deserialize(). - But we know that
TisDocopts, sodeserializeis Docopts::deserialize, which has the signature:
The variablefn deserialize<'a, 'de: 'a, D>(&'a self) -> Result<D, Error> where D: Deserialize<'de>selfhas typeDocopts.Dis still unknown, but we know it is the same type asUfrom step 2. - Result::unwrap_or_else has the signature:
The variablefn unwrap_or_else<F>(self, op: F) -> T where F: FnOnce(E) -> Tselfhas typeResult<T, Error>. But we know thatTis the same asUandDfrom the previous step. - We then assign to a variable of type
Args, soTfrom the previous step isArgs, which means that theDin step 3 (andUfrom step 2) is alsoArgs. - The compiler can now deduce that when you wrote
deserializeyou meant the method<Args as Deserialize>::deserialize, which was derived automatically with the#[derive(Deserialize)]attribute.
来源:https://stackoverflow.com/questions/54884862/how-does-the-type-deduction-work-in-this-docopt-example