变量与可变性
变量默认是不可改变的(immutable)。这是 Rust 提供给你的众多优势之一,让你得以充分利用 Rust 提供的安全性和简单并发性来编写代码。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 为何及如何鼓励你利用不可变性,以及何时你会选择禁用它。
当变量不可变时,一旦值被绑定一个名称上,你就不能改变这个值。
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
错误信息指出错误的原因是 不能对不可变变量 x 二次赋值(cannot assign twice to immutable variable
x )
,因为你尝试对不可变变量 x 赋第二个值。
在尝试改变预设为不可变的值时,产生编译时错误是很重要的,因为这种情况可能导致 bug。如果一部分代码假设一个值永远也不会改变,而另一部分代码改变了这个值,第一部分代码就有可能以不可预料的方式运行。不得不承认这种 bug 的起因难以跟踪,尤其是第二部分代码只是 有时 会改变值。
Rust 编译器保证,如果声明一个值不会变,它就真的不会变,所以你不必自己跟踪它。这意味着你的代码更易于推导。
不过可变性也是非常有用的,可以用来更方便地编写代码。尽管变量默认是不可变的,你仍然可以在变量名前添加 mut 来使其可变。mut 也向读者表明了其他代码将会改变这个变量值的意图。
将上面的代码更改为
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。是否让变量可变的最终决定权仍然在你,取决于在某个特定情况下,你是否认为变量可变会让代码更加清晰明了。
常量
类似于不可变变量,常量 (constants) 是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
首先,不允许对常量使用 mut。常量不光默认不可变,它总是不可变。声明常量使用 const 关键字而不是 let,并且 必须 注明值的类型。
常量可以在任何作用域中声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。
最后一个区别是,常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值。
下面是一个声明常量的例子
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
常量的名称是 THREE_HOURS_IN_SECONDS,它的值被设置为 60(一分钟内的秒数)乘以 60(一小时内的分钟数)再乘以 3(我们在这个程序中要计算的小时数)的结果。Rust 对常量的命名约定是在单词之间使用全大写加下划线。编译器能够在编译时计算一组有限的操作,这使我们可以选择以更容易理解和验证的方式写出此值,而不是将此常量设置为值 10,800。
在声明它的作用域之中,常量在整个程序生命周期中都有效,此属性使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速。
将遍布于应用程序中的硬编码值声明为常量,能帮助后来的代码维护人员了解值的意图。如果将来需要修改硬编码值,也只需修改汇聚于一处的硬编码值。
遮蔽
第二个变量遮蔽了第一个变量,此时任何使用该变量名的行为中都会视为是在使用第二个变量,直到第二个变量自己也被遮蔽或第二个变量的作用域结束。可以用相同变量名称来遮蔽一个变量,以及重复使用 let
关键字来多次遮蔽,如下所示:
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
,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不可变的。