Почему вызов метода для переменной предотвращает вывод Rust типа переменной?

Этот код компилируется:

#[derive(Debug, Default)]
struct Example;

impl Example {
    fn some_method(&self) {}
}

fn reproduction() -> Example {
    let example = Default::default();
    // example.some_method();
    example
}

Если закомментированная строка добавлена обратно, это вызовет ошибку:

error[E0282]: type annotations needed
  --> src/lib.rs:10:5
   |
9  |     let example = Default::default();
   |         ------- consider giving 'example' a type
10 |     example.some_method();
   |     ^^^^^^^ cannot infer type
   |
   = note: type must be known at this point

Почему добавление этого вызова метода приводит к сбою вывода типа?

Я видел эти два вопроса:

Из них я знаю, что Rust использует (модифицированную) версию Hindley-Milner. Последний вопрос имеет ответ, который описывает вывод типа Rust как систему уравнений. Другой ответ прямо заявляет, что "Тип информации в Rust может течь в обратном направлении".

Используя эти знания в данной ситуации, мы имеем:

  1. example типа ?E
  2. ?E должен иметь метод с именем some_method
  3. ?E возвращается
  4. Тип возвращаемого значения - Example

Работая задом наперед, человеку легко увидеть, что ?E должно быть Example. Где разрыв между тем, что я вижу, и тем, что видит компилятор?

Ответ 1

Основываясь на известных фактах (см. Ниже), он не компилируется, потому что:

  • средство проверки типов проходит через функцию в том порядке, в котором она была написана,
  • в приведенном let example = Default::default(); example может быть все, что реализует Default,
  • доступ к полю и вызовы метода требуют известного типа,
  • "Все, что реализует Default " не является известным типом.

Я заменил some_method() на доступ к полю, и он выдает ту же ошибку.


От вывода типа зависит порядок (# 42333):

use std::path::PathBuf;

pub struct Thing {
    pub f1: PathBuf,
}

fn junk() -> Vec<Thing> {
    let mut things = Vec::new();
    for x in vec![1, 2, 3] {
        if x == 2 {
            for thing in things.drain(..) {
                thing.f1.clone();
            }
            return vec![]
        }
        things.push(Thing{f1: PathBuf::from(format!("/{}", x))});
    }   
    things  
}               

fn main() { 
    junk();
}

Это приводит к ошибке компилятора с Rust 1.33.0:

error[E0282]: type annotations needed
  --> src/main.rs:13:17
   |
9  |     let mut things = Vec::new();
   |         ---------- consider giving 'things' a type
...
13 |                 thing.f1.clone();
   |                 ^^^^^ cannot infer type
   |
   = note: type must be known at this point

Вам следует сосредоточиться на следующих комментариях от eddyb (известного члена команды разработчиков языка Rust с мая 2016 года).

Комментарий № 1:

Это известное ограничение порядковой проверки типов. В то время как логический вывод протекает свободно, thing.f1.clone() проверяется перед things.push(Thing {...}) поэтому неизвестно, что thing: Thing когда вы пытаетесь получить доступ к полю f1. Мы можем в будущем отойти от этого, но никаких ближайших планов нет.

Что важнее, комментарий № 2:

Я имею в виду, что средство проверки типов проходит функцию в том порядке, в котором она была написана. [...] Доступ к полям и вызовы методов просто не поддерживаются, если тип уже не известен.

Ответ 2

Я не знаю полного ответа и почти ничего не знаю о внутренней работе компилятора Rust, но вот некоторые выводы, к которым я пришел из своего опыта работы с Rust.

Информация о типах в Rust может "течь в обратном направлении", но бывают случаи, когда Rust должен знать (абсолютно точно) тип выражения. В этих ситуациях он должен "уже" знать тип, то есть он не будет продолжать смотреть вперед.

Из того, что я видел, эта ситуация ограничена вызовами методов. Я подозреваю, что это как-то связано с тем, что методы могут быть реализованы по признакам, что существенно усложняет ситуацию. Я сомневаюсь, что есть какие-либо черты в области применения метода с именем some_method, но я думаю, что всякий раз, когда компилятор Rust встречает вызов метода, он требует, чтобы тип уже был известен наверняка.

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

Так что это работает:

fn create_numbers(last_num: i32) -> Vec<i32> {
    let x = (0..10).collect();
    x
}

Но это не так:

fn create_numbers(last_num: i32) -> Vec<i32> {
    let x = (0..10).collect();
    // In order to call 'push', we need to *already* know the type
    // of x for "absolute certain", and the Rust compiler doesn't 
    // keep looking forward
    x.push(42);
    x
}