Как работает вывод типа в этом примере Docopt? - PullRequest
0 голосов
/ 26 февраля 2019

Посмотрите на этот код, используя библиотеку docopt:

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());
}

Если вы посмотрите на выражение справа от знака равенства, вы увидите, что оно не упоминает Argsструктурировать где угодно.Как компилятор определяет тип возвращаемого значения этого выражения?Может ли информация типа передаваться в противоположном направлении (от цели инициализации до выражения инициализатора) в Rust?

1 Ответ

0 голосов
/ 26 февраля 2019

«Как это работает?» может быть слишком большим вопросом для переполнения стека, но (наряду с другими языками, такими как 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();

Так как насчет вашего исходного примера?

  1. Docopt::new возвращает Result<Docopt, Error>, что будет Result::Err<Error>, если предоставленные опции не могут быть проанализированы в качестве аргументов.На данный момент неизвестно, являются ли аргументы действительными, просто они правильно сформированы.
  2. Далее, 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().
  3. Но мы знаем, что 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.
  4. 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 из предыдущего шага.
  5. Затем мы присваиваем переменной типа Args, поэтому T с предыдущего шагаArgs, что означает, что D на шаге 3 (и U на шаге 2) также Args.
  6. Компилятор теперь может сделать вывод, что когда вы написали deserialize, вы имели в видуметод <Args as Deserialize>::deserialize, который был получен автоматически с атрибутом #[derive(Deserialize)].
...