30

此代码编译:

#[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. 我能看到的和编译器能看到的差距在哪里?

4

2 回答 2

21

根据已知事实(见下文),它无法编译,因为:

  • 类型检查器按照编写的顺序检查函数,
  • 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 (自 2016 年 5 月起成为 Rust 语言设计团队的知名成员)的以下评论。

评论 #1

这是有序类型检查器的已知限制。虽然推理自由流动,但之前thing.f1.clone()检查过, 因此当您尝试访问该字段时things.push(Thing {...})不知道。我们将来可能会放弃这一点,但没有立即的计划。thing: Thingf1

更重要的是评论#2

我的意思是类型检查器按照编写的顺序检查函数。[...] 除非类型已知,否则根本不支持字段访问和方法调用。

于 2019-03-19T23:04:09.037 回答
8

我不知道完整的答案,而且我几乎不了解 Rust 编译器的内部工作原理,但这里有一些我从 Rust 的经验中得出的推论。

Rust 中关于类型的信息可以“倒流”,但在某些时候,Rust 需要知道(绝对确定)表达式的类型。在这些情况下,它必须“已经”知道类型,即它不会继续向前看。

从我所见,这种情况仅限于方法调用。我怀疑这与可以在特征上实现方法这一事实有关,这使事情变得非常复杂。我怀疑一个名为 的方法在作用域中是否存在任何特征some_method,但我认为每当 Rust 编译器遇到方法调用时,它都需要确定该类型。

您可以看到这种情况在实现trait的类型上的方法调用经常发生,最常见的是collect实现 trait 的类型上的方法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
}
于 2019-03-19T15:45:04.730 回答