变量和可变性

“使用变量存储值”部分所述,默认情况下,变量是不可变的。这是 Rust 为了让你以利用 Rust 提供的安全性和易于并发的方式编写代码而给予的许多提示之一。然而,你仍然可以选择将变量设为可变的。让我们探讨 Rust 为什么鼓励你倾向于不可变性,以及为什么有时你可能想要选择退出。

当变量是不可变的,一旦一个值被绑定到一个名称,你就不能改变这个值。为了说明这一点,请在你的projects目录中使用cargo new variables生成一个名为variables的新项目。

然后,在您的新 variables 目录中,打开 src/main.rs 并将其代码替换为以下代码,这些代码暂时还无法编译:

文件名: src/main.rs

fn main() {
    let x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

使用 cargo run 保存并运行程序。您应该会收到一个关于不可变性的错误消息,如下所示:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:4:5
  |
2 |     let x = 5;
  |         - first assignment to `x`
3 |     println!("The value of x is: {x}");
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     let mut x = 5;
  |         +++

For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to 1 previous error

这个例子展示了编译器如何帮助你找到程序中的错误。 编译器错误可能会令人沮丧,但事实上它们只意味着你的程序还没有安全地完成你想要它做的事情;它们并意味着你不是一个好程序员!有经验的 Rustaceans 仍然会遇到编译器错误。

您收到错误消息 cannot assign twice to immutable variable `x` 是因为您尝试给不可变的 x 变量赋第二个值。

在我们尝试更改被指定为不可变的值时,获得编译时错误是很重要的,因为这种情况可能会导致错误。如果我们的代码的一部分基于某个值永远不会改变的假设运行,而代码的另一部分更改了该值,那么代码的第一部分可能无法按设计执行。这种错误的原因在事后可能很难追踪,特别是当代码的第二部分只在某些时候更改该值时。Rust编译器保证,当你声明一个值不会改变时,它确实不会改变,因此你不必自己跟踪它。因此,你的代码更容易理解。

但是可变性非常有用,可以使代码更方便编写。 虽然变量默认是不可变的,但您可以通过在变量名前添加mut来使其可变,就像您在第2章中所做的那样。添加mut还可以向未来的代码读者传达意图,表明代码的其他部分将更改此变量的值。

例如,让我们将 src/main.rs 更改为以下内容:

文件名: src/main.rs

fn main() {
    let mut x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

当我们现在运行程序时,我们得到的是这个:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/variables`
The value of x is: 5
The value of x is: 6

当我们使用 mut 时,允许我们将绑定到 x 的值从 5 更改为 6。最终,是否使用可变性取决于您,并且取决于您认为在特定情况下最清晰的方式。

常量

像不可变变量一样,常量是绑定到名称的值,并且不允许更改,但常量和变量之间存在一些差异。

首先,你不允许在常量中使用mut。常量不仅默认是不可变的——它们始终是不可变的。你使用const关键字而不是let关键字来声明常量,而且值的类型必须被注解。我们将在下一节“数据类型”中讨论类型和类型注解,所以现在不用担心细节。只需知道你必须始终注解类型。

常量可以在任何作用域中声明,包括全局作用域,这使得它们对于许多代码部分需要了解的值非常有用。

最后一个不同点是常量只能设置为常量表达式,不能设置为只能在运行时计算的值的结果。

这是一个常量声明的例子:

#![allow(unused)]
fn main() {
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
}

常量的名称是THREE_HOURS_IN_SECONDS,其值设置为60(一分钟的秒数)乘以60(一小时的分钟数)再乘以3(我们在这个程序中想要计算的小时数)。Rust中常量的命名约定是使用全大写并在单词之间使用下划线。编译器能够在编译时评估有限的操作,这使我们能够选择以更容易理解和验证的方式写出这个值,而不是将这个常量设置为10,800。有关声明常量时可以使用哪些操作的更多信息,请参阅Rust参考手册中关于常量评估的部分

常量在其声明的作用域内对程序运行的整个时间都有效。这一特性使得常量对于应用程序领域中多个程序部分可能需要了解的值非常有用,例如游戏中任何玩家允许获得的最高分数,或光速。

将程序中使用的硬编码值命名为常量,有助于向未来的代码维护者传达该值的含义。同时,如果将来需要更新硬编码值,这样做也可以确保你只需要在一个地方进行更改。

遮蔽

正如你在第2章的猜数字游戏教程中所见,你可以声明一个与先前变量同名的新变量。Rustaceans 说,第一个变量被第二个变量遮蔽了,这意味着当你使用变量名时,编译器将看到的是第二个变量。实际上,第二个变量遮蔽了第一个变量,将变量名的任何使用指向自己,直到它自己被遮蔽或作用域结束。我们可以通过使用相同的变量名并重复使用let关键字来遮蔽一个变量,如下所示:

文件名: src/main.rs

fn main() {
    let x = 5;

    let x = x + 1;

    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}");
    }

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

这个程序首先将 x 绑定到值 5。然后它通过重复 let x = 创建一个新变量 x,将原始值加 1,因此 x 的值变为 6。然后,在用大括号创建的内部作用域中,第三个 let 语句也遮蔽了 x 并创建了一个新变量,将前一个值乘以 2,使 x 的值为 12。当该作用域结束时,内部遮蔽结束,x 变回 6。当我们运行这个程序时,它将输出以下内容:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6

阴影与将变量标记为mut不同,因为如果我们不小心尝试在没有使用let关键字的情况下重新赋值给这个变量,我们会得到一个编译时错误。通过使用let,我们可以在值上执行一些转换,但在这些转换完成后,变量将变为不可变。

mut 和遮蔽之间的另一个区别是,由于我们在再次使用 let 关键字时实际上是创建了一个新变量,因此我们可以更改值的类型但重用相同的名称。例如,假设我们的程序要求用户通过输入空格字符来显示他们希望在某些文本之间有多少个空格,然后我们希望将该输入存储为数字:

fn main() {
    let spaces = "   ";
    let spaces = spaces.len();
}

第一个 spaces 变量是字符串类型,第二个 spaces 变量 是数字类型。因此,遮蔽使我们不必想出不同的名称,例如 spaces_strspaces_num;相反,我们可以重用更简单的 spaces 名称。但是,如果我们尝试为此使用 mut,如这里所示,我们将得到一个编译时错误:

fn main() {
    let mut spaces = "   ";
    spaces = spaces.len();
}

错误说我们不允许改变变量的类型:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
 --> src/main.rs:3:14
  |
2 |     let mut spaces = "   ";
  |                      ----- expected due to this value
3 |     spaces = spaces.len();
  |              ^^^^^^^^^^^^ expected `&str`, found `usize`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` (bin "variables") due to 1 previous error

现在我们已经探讨了变量的工作原理,让我们来看看它们可以拥有的更多数据类型。