为什么在变量上调用方法会阻止Rust推断变量的类型?

She*_*ter 30 type-systems type-inference hindley-milner rust

此代码编译:

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

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

fn reproduction() -> Example {
    let example = Default::default();
    // example.some_method();
    example
}
Run Code Online (Sandbox Code Playgroud)

如果添加了注释行,则会导致错误:

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

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

fn reproduction() -> Example {
    let example = Default::default();
    // example.some_method();
    example
}
Run Code Online (Sandbox Code Playgroud)

为什么添加此方法调用会导致类型推断失败?

我看过以下两个问题:

从他们那里,我知道Rust使用的是Hindley-Milner 的(修改的)版本。后一个问题的答案将Rust的类型推论描述为一个方程系统。另一个答案明确指出“ Rust中的类型信息可以倒流”。

使用适用于这种情况的知识,我们可以:

  1. example 是类型 ?E
  2. ?E 必须有一个称为 some_method
  3. ?E 返回
  4. 返回类型为 Example

向后工作,很容易使人看到?E必须如此Example。我所看到的与编译器所看到的之间的差距在哪里?

zrz*_*zka 20

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

  • 类型检查器按其编写顺序遍历函数,
  • 在中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();
}
Run Code Online (Sandbox Code Playgroud)

这会在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
Run Code Online (Sandbox Code Playgroud)

您应该关注eddyb自2016年5月以来成为Rust语言设计团队的知名成员)的以下评论。

评论1

这是顺序类型检查器的已知限制。尽管推理thing.f1.clone()是自由流动的,但请先 进行检查things.push(Thing {...})以便thing: Thing在尝试访问该f1字段时不知道。我们将来可能会放弃这一点,但没有立即的计划。

更重要的是评论2

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

  • 我认为我们不需要证明eddyb是杰出成员。在您提出第一个评论时,只要在他的名字旁边写下一个便笺就足够了。我认为可以通过删除“证明”和“临时”部分来改善此答案;您可以针对自己在进行中的工作等问题发表自己的答案,等等...当1/3实际没有答案时,无需吓people答案更长的人。// // //在另一个说明上:很棒的挖掘! (2认同)
  • 我个人觉得您的回答很令人满意;确保它不是“证明”,但是我不认为Rust语言愿意在规范中包含类型推断(因为它会导致停滞),因此我怀疑研究代码并引用已知限制是我们所能做到的最好得到。 (2认同)

Har*_* Mc 8

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

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

据我所知,这种情况仅限于方法调用。我怀疑这与可以在特征上实现方法的事实有关,这会使事情变得非常复杂。我怀疑名为的方法在作用域中是否有任何特征some_method,但是我认为无论何时Rust编译器遇到方法调用时,都要求该类型已经确定。

你可以看到这样的情况一个很多与方法调用上实现特质类型,其中最常见的是collect方法上的一种类型,实现了Iter特质。您将能够调用collect,但是除非指定类型,否则将无法在结果上调用任何方法。

所以这工作:

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

但这不是:

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
}
Run Code Online (Sandbox Code Playgroud)