为什么Rust无法进行更复杂的类型推断?

时间:2018-08-01 20:31:34

标签: rust type-inference

Rust编程语言Chapter 3中,以下代码用作Rust无法管理的一种类型推断的示例:

fn main() {
    let condition = true;

    let number = if condition { 5 } else { "six" };

    println!("The value of number is: {}", number);
}

解释如下:

  

Rust需要在编译时知道number变量的类型,以便它可以在编译时验证其类型在我们使用number的任何地方都是有效的。如果number的类型仅在运行时确定,Rust将无法做到;如果编译器必须跟踪任何变量的多种假设类型,则编译器将更加复杂,并且对代码的保证更少。

我不确定我理解其基本原理,因为该示例确实看起来像一个简单的编译器可以推断类型的事物。

究竟是什么使这种类型推断如此困难?在这种情况下,可以在编译时明确推断出 condition 的值(这是对的),因此数字的类型也可以(i32?)。

例如,如果您试图在多个编译单元之间推断类型,那么我会发现事情可能会变得变得更加复杂,但是有关此特定示例的内容会增加很多编译器复杂吗?

2 个答案:

答案 0 :(得分:9)

我能想到的主要原因有三个:

1。远距离行动效果

让我们假设这种语言是这样工作的。由于我们正在扩展类型推断,因此我们还可以使语言更加智能,并使其推断返回类型。这使我可以编写如下内容:

pub fn get_flux_capacitor() {
  let is_prod = true;

  if is_prod { FluxCapacitor::new() } else { MovieProp::new() }
}

在项目的其他地方,我可以通过调用该函数来获得FluxCapacitor。但是,有一天,我将is_prod更改为false。现在,我不会在我的函数返回错误的类型时出错,而是在每个调用站点上看到错误。一个函数内部的微小更改会导致完全不变的文件中出现错误!真是奇怪。

(如果我们不想添加推断的返回类型,请想象它是一个非常长的函数。)

2。编译器内部暴露

如果不是那么简单会发生什么?当然,这应该与上面的示例相同:

pub fn get_flux_capacitor() {
  let is_prod = (1 + 1) == 2;

  ...
}

但是这延伸了多远?编译器的常量传播主要是实现细节。您不希望程序中的类型取决于此版本的编译器的智能程度。

3。你到底是什么意思?

当人们看着这段代码时,似乎缺少了一些东西。您为什么要在true上分支?为什么不只写FluxCapacitor::new()?可能缺少检查env=DEV环境变量的逻辑。也许实际上应该使用特征对象,以便您可以利用运行时多态性。

在这种情况下,您要求计算机执行似乎不太正确的操作,Rust经常选择举起手来要求您修复代码。

答案 1 :(得分:2)

您是对的,在这种非常特殊的情况下(静态为condition=true),可以使编译器能够检测到else分支不可达,因此number必须是5。

这只是一个人为的示例,但是...在更一般的情况下,condition的值只能在运行时动态地知道。 正如其他人所说,就是在这种情况下,推理变得难以实现。

关于该主题,我还没有提到两件事。

  1. Rust语言设计倾向于在做事时犯错 尽可能明确地
  2. 铁锈类型推断仅是本地

在第1点上,Rust处理“此类型可以是多种类型之一”用例的显式方式是enums。 您可以定义如下内容:

#[derive(Debug)]
enum Whatsit {
    Num(i32),
    Text(&'static str),
}

然后执行let number = if condition { Num(5) } else { Text("six") };

在第2点上,让我们看看枚举(单词间)是该语言中的首选方法。在本书的示例中,我们仅尝试打印number的值。 在更实际的情况下,我们有时会将number用于打印以外的其他用途。

这意味着将其传递给另一个函数或将其包含在其他类型中。或者(甚至允许使用println!)在其上实现DebugDisplay特征。本地推断意味着(如果您无法在Rust中命名number的类型),则您将无法执行任何这些操作。 假设您想创建一个对number做某事的函数; 用您将写的枚举:

fn do_something(number: Whatsit)

但没有它...

fn do_something(number: /* what type is this? */)

简而言之,您说对了,原则上编译器可以为number合成类型是正确的。例如,编译该代码时,编译器可能会创建一个类似于上面的Whatsit的匿名枚举。 但是您-程序员-不知道该类型的名称,无法引用它,甚至不知道您可以使用它做什么(我可以将两个“数字”相乘吗?),这将大大限制它的有用性。

遵循类似的方法,例如为语言添加了闭包。编译器会知道闭包具有什么特定类型,但是程序员则不会。如果您有兴趣,我可以尝试查找有关该方法在语言设计中引入的困难的讨论。