«Как это работает?» может быть слишком большим вопросом для переполнения стека, но (наряду с другими языками, такими как Scala и Haskell) система типов Rust основана на Hindley-Milnerсистема типов , хотя со многими модификациями и расширениями.
Сильно упрощая, идея состоит в том, чтобы рассматривать каждый неизвестный тип как переменную и определять отношения между типами как серию ограничений, которые затем могут бытьрешается алгоритмом.В некотором смысле это похоже на уравнения одновременности, которые вы, возможно, решили в алгебре в школе.
Вывод типа - это особенность Rust (и других языков в расширенной семье Хиндли-Милнер), который широко используется в идиоматическом коде для:
- уменьшения шума аннотаций типов
- повышения удобства сопровождения за счет несложного кодирования типов в нескольких местах (DRY)
Вывод типа Rust является мощным и, как вы говорите, может проходить в обоих направлениях.Чтобы использовать Vec<T>
в качестве более простого и знакомого примера, допустим любой из них:
let vec = Vec::new(1_i32);
let vec = Vec::<i32>::new();
let vec: Vec<i32> = Vec::new();
Тип может быть выведен только на основании того, как тип используется позже:
let mut vec = Vec::new();
// later...
vec.push(1_i32);
Другой хороший пример - выбор правильного синтаксического анализатора строк на основе ожидаемого типа:
let num: f32 = "100".parse().unwrap();
let num: i128 = "100".parse().unwrap();
let address: SocketAddr = "127.0.0.1:8080".parse().unwrap();
Так как насчет вашего исходного примера?
Docopt::new
возвращает Result<Docopt, Error>
, что будет Result::Err<Error>
, если предоставленные опции не могут быть проанализированы в качестве аргументов.На данный момент неизвестно, являются ли аргументы действительными, просто они правильно сформированы. - Далее,
and_then
имеет следующую подпись: pub fn and_then<U, F>(self, op: F) -> Result<U, E>
where
F: FnOnce(T) -> Result<U, E>,
Переменная self
имеет тип Result<T, E>
, где T
равно Docopt
, а E
равно Error
, выведено из шага 1. U
все еще неизвестно, даже после того, как вы поставили затвор |d| d.deserialize()
. - Но мы знаем, что
T
равно Docopts
, поэтому deserialize
равно Docopts::deserialize
, которое имеет подпись: fn deserialize<'a, 'de: 'a, D>(&'a self) -> Result<D, Error>
where
D: Deserialize<'de>
Переменная self
имеет тип Docopts
.D
до сих пор неизвестно, но мы знаем, что это тот же тип, что и U
из шага 2. Result::unwrap_or_else
имеет подпись: fn unwrap_or_else<F>(self, op: F) -> T
where
F: FnOnce(E) -> T
Переменная self
имеет тип Result<T, Error>
.Но мы знаем, что T
совпадает с U
и D
из предыдущего шага. - Затем мы присваиваем переменной типа
Args
, поэтому T
с предыдущего шагаArgs
, что означает, что D
на шаге 3 (и U
на шаге 2) также Args
. - Компилятор теперь может сделать вывод, что когда вы написали
deserialize
, вы имели в видуметод <Args as Deserialize>::deserialize
, который был получен автоматически с атрибутом #[derive(Deserialize)]
.