为什么Rust要使用"let"关键字?

15

我对 Rust 很感兴趣,于是我开始阅读 Rust 网站上的 Rust 编程指南,并发现变量是以以下方式声明:

let x: i32 = 5;

这意味着将整数值5分配给32位整数类型的变量,从现在开始通过符号引用它。

我的主要问题是为什么要有let关键字?它似乎是多余的,好像实际上并没有做任何事情。

我假设编译器能够确定以下内容是一个变量(或常量变量)声明:

x: i32 = 5;

似乎没有理由使用let关键字,但Rust专注于安全,因此可能有一个聪明的理由。那么这个原因是什么呢?


补充说明:作为函数参数时,不需要使用let关键字。以下是一个例子:

fn add1(x: i32) -> i32
{
    x = x + 1
}

这似乎有点奇怪 - 因为缺少 let,所以“看起来”像是引用传递。但实际上并不是,它是值传递(至少我认为是这样)。这是一种语法上的不一致吗?


另外,我认为将语句改为以下形式更加合理:

i32 x = 5;

如果你愿意的话,可以在那里加上一个冒号:

i32: x = 5;

我认为这更符合逻辑,因为:

  • 在编程时,通常在想到名称之前就知道要使用什么类型的数据。

也许有些人想法不同?但这引出了另一个问题:在Rust中如何声明多个相同类型的变量?例如:

let x, y, z: i32 = {4, 5, 5} // A guess of what this might look like?

还是说在 Rust 中不允许这样做?


我不理解你的编辑意图。在一个函数签名中,上下文告诉我们正在声明参数而不是赋值任何东西。如果没有任何 & 或其他指示符,你怎么会觉得发生了任何按引用传递的事情,这超出了我的理解范围。 - user395760
@delnan 仔细阅读 - 我说它不是按引用传递的,但它看起来可能是。 - FreelanceConsultant
我理解了。但我不明白它可能看起来像什么。没有任何指示。正如你所指出的,唯一的区别是缺少“let”,这与按值/按引用有什么关系? - user395760
缺少 let 关键字让我个人感觉好像并没有创建一个新的变量。因此,也许它是其他东西。也许那个其他东西是一个引用?还有什么可能性呢? - FreelanceConsultant
3个回答

40

Rust具有本地类型推断,因此通常不需要编写类型;let x = 5;即可。而x = 5;则会有很大的不同,因为它不会声明一个变量x,而Rust非常明确地区分声明和赋值。

实际上是let PATTERN = EXPR;,而不仅仅是let IDENT = EXPR;,因此删除let关键字会导致语法上的歧义。模式可以是mut x(使变量绑定可变),可以是(a, b)(表示元组解包), &c.

你之所以只能想到i32 x = 5;是因为你习惯了像C++这样的语言。说真的,是谁想出这个主意的?在纯粹的哲学基础上,在名称后面放置类型比在前面更有道理,只用类型来声明变量也太傻了。这里有各种各样的语法模糊性。类型推断,允许您完全省略类型,是一个更好的方法。


1
这里提出了一些有趣的观点 - 你能举个这样的歧义的例子吗?回应第三段,不,很多年前当我停止使用Basic并开始用C编程时,我喜欢“dim”关键字消失了,因为我没有看到它存在的理由。我不明白你关于哲学基础的观点 - 我认为首选类型或标识符是个人偏好问题?你在将类型放在后面方面的哲学论证是什么? - FreelanceConsultant
1
那么,从视觉角度来看,将一个值分配给类型比 i32 x = 5; 更不愚蠢吗? - seb
2
"谁想出了那个主意?" [是Dennis Ritchie] (https://www.bell-labs.com/usr/dmr/www/chist.html)。 "类比推理导致声明语法的名称镜像表达式语法,其中名称通常出现。" 回顾来看,这是一个不幸的举动(因为它删除了语言的所有上下文无关性希望),但从哲学的角度来看,这可能在当时看起来像一个伟大的主意(“如何概括类型组合语法?”)。 - Wtrmute
@Wtrmute:我发现有点难以理解。它所指的表达式语法是“(int) foo”吗?如果是,那似乎不是很好的镜像,所以我想知道它是否还有其他含义。 - Chris Morgan
@ChrisMorgan:Ritchie的文章(我提供了链接)比SO评论更好地解释了这一点,但它涉及到用于从变量类型中获取原始类型的表达式。Ritchie给出了一些例子,其中包括int *api [10],(* pai) [10]; - 在每种情况下,表达式* api [0](* pai)[0]恰好产生原始类型int - Wtrmute

13

首先,解析歧义。虽然解析这样的代码是可能的(但仅因为a < b > c已经因为其他原因而非法),但它会使所有工具复杂化,并使解析器性能(和错误恢复)变差。

其次,类型推断。无论类型写在哪里,都可以省略,因此需要let或另一个标记来区分赋值和声明。(你可以技术上使用let x = ...;Type x = ...;,但为什么呢?它不是一种类型,而是一种类型的缺失。)

第三,普遍性。let适用于任何(无法反驳的)模式,包括那些同时作为完全合法表达式的模式:StructLiteral { field: x, other_field: y }Some(thing)。如果不是解析器(它当前拒绝Some(a) = b),则这是更多歧义的来源,那就是人类。

第四,虽然可能习惯了类型名称,但长期以来一直有名称:类型的传统,并且它正在获得主流认可,而不是Rust所做的(例如在Scala中)。

第五,我对此提出异议:

编程时,通常在想到名称之前就知道要使用什么数据类型。

也许我知道数据的一般形状(整数、字符串、迭代器、集合等),但确切的类型通常是实现细节,其次才考虑。这个论点在类型推断面前也会崩溃:如果你大多数时间都在思考类型,那么省略类型有什么用处呢?如果另一方面,你只是在编写代码时谈论你首先想到的内容,那么这种情况就被驳回了:代码被阅读的次数比编写的次数多得多,引入概念的顺序不需要(并且通常不应该)反映它们构思的顺序。


我不完全理解你在最后一段想表达什么。当我编写程序时,我知道我的数据是整数、指针、浮点值、字符、std::vector等等...我可能会说,除非你知道数据存储的格式,否则不可能编写代码。这就是编程的全部意义——操作数据,因此如果你“不知道”你的数据长什么样子,那么你最好在开始打字之前弄清楚! - FreelanceConsultant
4
@user3728501 我知道我的数据是什么样子的。幸运的是,我没有被机器完全限制住,可以更抽象地思考:我可以知道某个东西是整数,而不必预先确定它是 u32 还是 u64。我可以知道我想查找所有用户 ID 的用户,而不在乎 items.iter().map(User::lookup) 的结果类型是 iter::Map<slice::Iter<u32>, fn(u32) -> User>。即使在某个时刻需要知道这些信息,我也不需要始终都知道并将其推给每个读者面前。 - user395760
2
@user3728501 那你也反对类型推断吗? - user395760
1
当然,写作是必须的。但我们正在谈论(您认为)写入能力的轻微损失,以换取可读性的轻微提高。它将被阅读的频率比写入更多,这使得这种权衡是好的。关于最后一条评论:但我们确实添加了冒号,它甚至有许多其他好处(如在此答案中解释)。那么你的观点是什么?再次考虑到Rust中迄今为止最常见的变量声明是let name = value;,没有提到类型。 - user395760
1
@user3728501,这已经超出了评论链(以及Stack Overflow的范围)的讨论,所以我建议您去谷歌上找一些有关“代码被阅读的次数比编写的次数多”的文章。我没有时间也没有空间在这里重复所有的论点。 - user395760
显示剩余8条评论

9

这显著简化了语法。请记住,左侧有一个完整的模式,它实际上不是let var,而是let pattern

如何在Rust中声明几个相同类型的变量?

这是一个完全不同的问题,但没有直接的方法来做到这一点,至少不是你想的那种方式。您可以

let (a, b, c) = (1, 2, 3);

但它们不必是相同类型的,这是一个解构模式的实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接