变量和可变性


“Storing Values with Variables“ 部分,默认情况下,变量是不可变的。这是 Rust 为您提供的众多推动之一,让您以利用 Rust 提供的安全性和轻松并发的方式编写代码。但是,您仍然可以选择将变量设置为可变。让我们探讨一下 Rust 如何以及为什么鼓励你支持不变性,以及为什么有时你可能想要选择退出。


当变量是不可变的时,一旦值绑定到名称,就无法更改该值。为了说明这一点,请使用 cargo new variablesprojects 目录中生成一个名为 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


此示例说明编译器如何帮助您查找程序中的错误。编译器错误可能令人沮丧,但实际上它们只意味着您的程序还没有安全地执行您希望它做的事情;它们并不意味着您不是一个好的程序员!有经验的 Rustacean 仍然会遇到编译器错误。


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


当我们尝试更改指定为不可变的值时,我们得到编译时错误是很重要的,因为这种情况可能会导致 bug。如果代码的一部分假设某个值永远不会改变,而另一部分代码改变了该值,那么代码的第一部分可能无法执行其设计目的。这种 bug 的原因在事后可能很难追踪,尤其是当第二段代码有时仅更改值时。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 与常量一起使用。常量不仅仅是 immutable - 它们始终是不可变的。您可以使用 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 参考 中关于 constant 的部分 evaluation 了解有关声明常量时可以使用哪些作的更多信息。


常量在程序运行的整个时间内有效,在声明它们的范围内。此属性使常量对于应用程序域中程序的多个部分可能需要了解的值非常有用,例如,允许游戏的任何玩家获得的最大分数或光速。


将整个程序中使用的硬编码值命名为常量有助于将该值的含义传达给代码的未来维护者。如果将来需要更新硬编码值,则代码中只有一个位置需要更改也会有所帮助。


阴影


正如您在章节中的猜谜游戏教程中看到的那样 2 中,您可以声明与前一个变量同名的新变量。Rustacean 说第一个变量被第二个变量遮蔽,这意味着当你使用变量的名称时,编译器将看到第二个变量。实际上,第二个变量会掩盖第一个变量,将变量 name 的任何使用都归自己所有,直到它本身被隐藏或范围结束。我们可以使用相同的变量名称并重复使用 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。然后它会创建一个新变量 x 通过重复 let 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 和 shadowing 之间的另一个区别是,因为我们在再次使用 let 关键字时实际上是在创建一个新变量,所以我们可以更改值的类型,但重复使用相同的名称。例如,假设我们的程序要求用户通过输入空格字符来显示他们希望某些文本之间有多少个空格,然后我们想将该输入存储为数字:

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


第一个 spaces 变量是字符串类型,第二个 spaces 变量是数字类型。因此,隐藏使我们不必想出不同的名称,例如 spaces_strspaces_num;相反,我们可以重用 Simpler 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


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