Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

变量与可变性

Variables and Mutability

正如在“使用变量存储值”部分所提到的,默认情况下,变量是不可变的。这是 Rust 为你提供的众多暗示之一,旨在让你编写代码时能利用 Rust 提供的安全性和便捷的并发性。不过,你仍然可以选择让变量变得可变。让我们探讨一下 Rust 为何以及如何鼓励你优先使用不可变性,以及为什么有时你可能想要弃用它。

As mentioned in the “Storing Values with Variables” section, by default, variables are immutable. This is one of many nudges Rust gives you to write your code in a way that takes advantage of the safety and easy concurrency that Rust offers. However, you still have the option to make your variables mutable. Let’s explore how and why Rust encourages you to favor immutability and why sometimes you might want to opt out.

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

When a variable is immutable, once a value is bound to a name, you can’t change that value. To illustrate this, generate a new project called variables in your projects directory by using cargo new variables.

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

Then, in your new variables directory, open src/main.rs and replace its code with the following code, which won’t compile just yet:

文件名:src/main.rs Filename: 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 运行程序。你应该会收到一条关于不可变性错误的错误消息,如下输出所示:

Save and run the program using cargo run. You should receive an error message regarding an immutability error, as shown in this output:

$ 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 仍然会遇到编译器错误。

This example shows how the compiler helps you find errors in your programs. Compiler errors can be frustrating, but really they only mean your program isn’t safely doing what you want it to do yet; they do not mean that you’re not a good programmer! Experienced Rustaceans still get compiler errors.

你收到了错误消息 cannot assign twice to immutable variable `x`(不能对不可变变量 x 进行二次赋值),因为你尝试为不可变的 x 变量分配第二个值。

You received the error message cannot assign twice to immutable variable `x` because you tried to assign a second value to the immutable x variable.

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

It’s important that we get compile-time errors when we attempt to change a value that’s designated as immutable, because this very situation can lead to bugs. If one part of our code operates on the assumption that a value will never change and another part of our code changes that value, it’s possible that the first part of the code won’t do what it was designed to do. The cause of this kind of bug can be difficult to track down after the fact, especially when the second piece of code changes the value only sometimes. The Rust compiler guarantees that when you state that a value won’t change, it really won’t change, so you don’t have to keep track of it yourself. Your code is thus easier to reason through.

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

But mutability can be very useful and can make code more convenient to write. Although variables are immutable by default, you can make them mutable by adding mut in front of the variable name as you did in Chapter 2. Adding mut also conveys intent to future readers of the code by indicating that other parts of the code will be changing this variable’s value.

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

For example, let’s change src/main.rs to the following:

文件名:src/main.rs Filename: 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}");
}

当我们现在运行程序时,我们得到如下结果:

When we run the program now, we get this:

$ 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。最终,决定是否使用可变性取决于你,取决于你认为在特定情况下什么最清晰。

We’re allowed to change the value bound to x from 5 to 6 when mut is used. Ultimately, deciding whether to use mutability or not is up to you and depends on what you think is clearest in that particular situation.

声明常量

Declaring Constants

与不可变变量类似,常量也是绑定到名称且不允许更改的值,但常量和变量之间有一些区别。

Like immutable variables, constants are values that are bound to a name and are not allowed to change, but there are a few differences between constants and variables.

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

First, you aren’t allowed to use mut with constants. Constants aren’t just immutable by default—they’re always immutable. You declare constants using the const keyword instead of the let keyword, and the type of the value must be annotated. We’ll cover types and type annotations in the next section, “Data Types”, so don’t worry about the details right now. Just know that you must always annotate the type.

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

Constants can be declared in any scope, including the global scope, which makes them useful for values that many parts of code need to know about.

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

The last difference is that constants may be set only to a constant expression, not the result of a value that could only be computed at runtime.

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

Here’s an example of a constant declaration:

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

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

The constant’s name is THREE_HOURS_IN_SECONDS, and its value is set to the result of multiplying 60 (the number of seconds in a minute) by 60 (the number of minutes in an hour) by 3 (the number of hours we want to count in this program). Rust’s naming convention for constants is to use all uppercase with underscores between words. The compiler is able to evaluate a limited set of operations at compile time, which lets us choose to write out this value in a way that’s easier to understand and verify, rather than setting this constant to the value 10,800. See the Rust Reference’s section on constant evaluation for more information on what operations can be used when declaring constants.

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

Constants are valid for the entire time a program runs, within the scope in which they were declared. This property makes constants useful for values in your application domain that multiple parts of the program might need to know about, such as the maximum number of points any player of a game is allowed to earn, or the speed of light.

将整个程序中使用的硬编码值命名为常量,有助于向代码未来的维护者传达该值的含义。如果以后需要更新硬编码值,你的代码中也只需在一处进行更改。

Naming hardcoded values used throughout your program as constants is useful in conveying the meaning of that value to future maintainers of the code. It also helps to have only one place in your code that you would need to change if the hardcoded value needed to be updated in the future.

重影(Shadowing)

Shadowing

正如你在第 2 章的猜数字教程中所看到的,你可以声明一个与先前变量同名的新变量。Rustaceans 说第一个变量被第二个变量*重影(shadowed)*了,这意味着当你使用变量名称时,编译器将看到第二个变量。实际上,第二个变量遮蔽了第一个变量,将所有对该变量名的使用都据为己有,直到它自己被重影或作用域结束。我们可以通过使用相同的变量名并重复使用 let 关键字来重影一个变量,如下所示:

As you saw in the guessing game tutorial in Chapter 2, you can declare a new variable with the same name as a previous variable. Rustaceans say that the first variable is shadowed by the second, which means that the second variable is what the compiler will see when you use the name of the variable. In effect, the second variable overshadows the first, taking any uses of the variable name to itself until either it itself is shadowed or the scope ends. We can shadow a variable by using the same variable’s name and repeating the use of the let keyword as follows:

文件名:src/main.rs Filename: 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。当我们运行此程序时,它将输出以下内容:

This program first binds x to a value of 5. Then, it creates a new variable x by repeating let x =, taking the original value and adding 1 so that the value of x is 6. Then, within an inner scope created with the curly brackets, the third let statement also shadows x and creates a new variable, multiplying the previous value by 2 to give x a value of 12. When that scope is over, the inner shadowing ends and x returns to being 6. When we run this program, it will output the following:

$ 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,我们可以对一个值执行几次转换,但在这些转换完成后,变量仍然是不可变的。

Shadowing is different from marking a variable as mut because we’ll get a compile-time error if we accidentally try to reassign to this variable without using the let keyword. By using let, we can perform a few transformations on a value but have the variable be immutable after those transformations have completed.

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

The other difference between mut and shadowing is that because we’re effectively creating a new variable when we use the let keyword again, we can change the type of the value but reuse the same name. For example, say our program asks a user to show how many spaces they want between some text by inputting space characters, and then we want to store that input as a number:

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

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

The first spaces variable is a string type, and the second spaces variable is a number type. Shadowing thus spares us from having to come up with different names, such as spaces_str and spaces_num; instead, we can reuse the simpler spaces name. However, if we try to use mut for this, as shown here, we’ll get a compile-time error:

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

错误提示我们不允许更改变量的类型:

The error says we’re not allowed to mutate a variable’s type:

$ 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

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

Now that we’ve explored how variables work, let’s look at more data types they can have.