Почему вызов метода для переменной предотвращает вывод Rust типа переменной? - PullRequest
30 голосов
/ 19 марта 2019

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

#[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 может течь задом наперед».

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

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

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

Ответы [ 2 ]

20 голосов
/ 20 марта 2019

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

  • средство проверки типов проходит через функцию в том порядке, в котором было написано ,
  • in 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 :

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

8 голосов
/ 19 марта 2019

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

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

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

Вы можете увидеть это lot с вызовами методов для типов, которые реализуют признаки, наиболее распространенным из которых является метод 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
}
...