变量与可变性

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

当变量不可变时,一旦值绑定到名称,就不能更改该值。 为了说明这一点,使用 cargo new variables 在你的 projects 目录中生成一个名为 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`
  |         help: consider making this binding mutable: `mut x`
3 |     println!("The value of x is: {x}");
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

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

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

你收到错误消息 cannot assign twice to immutable variable `x` 是因为你试图为不可变变量 x 分配第二个值。

重要的是,当我们尝试更改指定为不可变的值时,我们会收到编译时错误,因为这种情况会导致错误。 如果我们代码的一部分假设一个值永远不会改变,而我们代码的另一部分改变了该值,则代码的第一部分可能无法执行其设计的功能。 这种错误的原因可能很难在事后追踪,尤其是当第二段代码_有时_只改变值时。 Rust 编译器保证,当你声明一个值不会改变时,它就真的不会改变,所以你不必自己跟踪它。 因此,你的代码更容易推理。

但是可变性非常有用,并且可以使代码更容易编写。 尽管默认情况下变量是不可变的,但你可以通过在变量名之前添加 mut 来使其可变,就像你在 第二章中所做的那样。 添加 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 [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 参考手册中关于常量求值的章节

在程序运行的整个过程中,常量在其声明的作用域内都是有效的。这个特性使得常量对于应用程序域中程序的多个部分可能需要知道的值非常有用,例如游戏玩家允许获得的最大点数,或者光速。

将程序中使用的硬编码值命名为常量,有助于向代码的未来维护者传达该值的含义。它还有助于在代码中只有一个地方需要更改,以防将来需要更新硬编码值。

隐藏

正如你在第二章的猜谜游戏教程中所见,你可以用与先前变量相同的名称声明一个新变量。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 [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`
  |
help: try removing the method call
  |
3 -     spaces = spaces.len();
3 +     spaces = spaces;
  |

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

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