Как работает вывод типа в этом примере Docopt?

Взгляните на этот код, используя библиотеку 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?


person saga    schedule 26.02.2019    source источник


Ответы (1)


"Как это работает?" может быть слишком сложным вопросом для Stack Overflow, но (наряду с другими языками, такими как Scala и Haskell) система типов Rust основана на система типов Хиндли-Милнера, хотя и со многими модификациями и расширениями.

Сильно упрощая, идея состоит в том, чтобы рассматривать каждый неизвестный тип как переменную и определять отношения между типами как ряд ограничений, которые затем могут быть решены с помощью алгоритма. В некотором смысле это похоже на одновременные уравнения, которые вы, возможно, решали на уроках алгебры в школе.


Вывод типов — это функция 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)].
person Peter Hall    schedule 26.02.2019