Почему вызов метода для переменной предотвращает вывод 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 может течь в обратном направлении".
Используя эти знания в данной ситуации, мы имеем:
-
example
типа ?E
-
?E
должен иметь метод с именем some_method
-
?E
возвращается - Тип возвращаемого значения -
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
}