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?
"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:
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:The type can even be inferred just based on how a type is later used:
Another nice example is picking the correct string parser, based on the expected type:
So what about your original example?
Docopt::new
returns aResult<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.and_then
has the following signature: The variableself
has typeResult<T, E>
whereT
isDocopt
andE
isError
, deduced from step 1.U
is still unknown, even after you supply the closure|d| d.deserialize()
.T
isDocopts
, sodeserialize
isDocopts::deserialize
, which has the signature: The variableself
has typeDocopts
.D
is still unknown, but we know it is the same type asU
from step 2.Result::unwrap_or_else
has the signature: The variableself
has typeResult<T, Error>
. But we know thatT
is the same asU
andD
from the previous step.Args
, soT
from the previous step isArgs
, which means that theD
in step 3 (andU
from step 2) is alsoArgs
.deserialize
you meant the method<Args as Deserialize>::deserialize
, which was derived automatically with the#[derive(Deserialize)]
attribute.