Rust 变量与可变性
文章目录
- 变量与可变性
- 常量
- 遮蔽(Shadowing)
变量与可变性
Rust中变量默认是不可变的,这是 Rust 鼓励你编写更安全、易于并发代码的众多方式之一。不过,你仍然可以选择让变量可变。让我们来探讨 Rust 为什么鼓励你优先使用不可变性,以及为什么有时你可能需要选择可变性。
当变量是不可变的时,一旦一个值被绑定到名字上,你就无法更改该值。为说明这一点,请在你的项目目录下用 cargo new 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 runCompiling 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;| +++
有关此错误的更多信息,请尝试 rustc --explain E0384
。
error: could not compile variables
(bin “variables”) due to 1 previous error
这个例子展示了编译器如何帮助你发现程序中的错误。
你收到“不能对不可变变量 x
进行二次赋值”的错误,是因为你试图给不可变变量 x
赋第二个值。
当我们试图更改被指定为不可变的值时,在编译时报错是很有价值的,因为不应改变值的变量发生了改变很容易导致 bug。如果代码的一部分要求某个值永远不变,而另一部分代码却改变了这个值,那么第一部分代码可能就无法按预期工作。尤其是当第二部分代码只在某些情况下才改变值时,这种 bug 很难追踪。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 runCompiling variables v0.1.0 (file:///projects/variables)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30sRunning `target/debug/variables`
The value of x is: 5
The value of x is: 6
使用 mut
后,我们可以将 x 的值从 5 改为 6。最终,是否使用可变性取决于我们实际的需求和应用场景。
常量
与不可变变量类似,常量也是绑定到名字上的值,且不允许更改,但常量和变量之间有一些区别。
首先,常量不能使用 mut
。常量不仅默认不可变——它们始终不可变。我们需要用 const
关键字声明常量,而不是 let
,并且必须标注值的类型。
常量可以在任何作用域声明,包括全局作用域,从而可供多个模块或代码片使用。
最后一个区别是,常量只能被赋值为常量表达式,而非在运行时计算的值。
下面是一个常量声明的例子:
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
常量名为 THREE_HOURS_IN_SECONDS,其值为 60(每分钟的秒数)乘以 60(每小时的分钟数)再乘以 3(我们想要计算的小时数)。Rust 的常量命名约定是全大写并用下划线分隔单词。编译器能在编译时完成有限的运算,这让我们可以用更易理解的方式写出这个值,而不是直接写 10,800。更多关于常量声明时可用操作的信息,请参阅 Rust Reference 的常量求值部分。
在声明的作用域内,常量于整个程序运行期间都有效。此属性使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速。
将程序中用到的硬编码值声明为常量,能帮助后来的代码维护人员了解值的意图。如果将来需要更改硬编码值,也只需改动一处即可。
遮蔽(Shadowing)
正如你在第 2 章的猜数字游戏教程中看到的,你可以用相同的名字声明新变量。Rustacean 称第一个变量被第二个变量“遮蔽”,即编译器在使用变量名时会看到第二个变量。实际上,第二个变量覆盖了第一个变量,直到它自己被遮蔽或作用域结束。我们可以通过重复使用 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,此时 x 的值为 6。接着,在用花括号创建的内部作用域中,第三个 let
语句再次遮蔽 x,创建了一个新变量,其值为前一个值乘以 2,即 12。当该作用域结束后,内部遮蔽结束,x 恢复为 6。运行该程序,输出如下:
$ cargo runCompiling variables v0.1.0 (file:///projects/variables)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31sRunning `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6
遮蔽与将变量标记为 mut
不同,因为如果你试图在没有使用 let
关键字的情况下重新赋值,会得到编译时错误。通过使用 let
,我们可以对值进行多次转换,但在这些转换完成后变量仍然是不可变的。
mut
和遮蔽的另一个区别是,使用 let
关键字实际上创建了一个新变量,因此我们可以更改值的类型,但仍然复用相同的名字。例如,假设我们的程序让用户输入文本之间要有多少个空格,用户输入空格字符后,我们想把这个输入存储为数字:
let spaces = " ";
let spaces = spaces.len();
第一个 spaces 变量是字符串类型,第二个 spaces 变量是数字类型。遮蔽让我们无需起不同的名字(如 spaces_str 和 spaces_num),而可以复用更简单的 spaces 名字。但如果我们尝试用 mut
,如下所示,会得到编译时错误:
此代码无法编译!
let mut spaces = " ";
spaces = spaces.len();
错误提示我们不能更改变量的类型:
$ cargo runCompiling 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`
有关此错误的更多信息,请尝试 rustc --explain E0308
。
error: could not compile variables
(bin “variables”) due to 1 previous error
整体代码示例如下:
fn main() {// 变量可变性 let mut x = 5;println!("The value of x is: {x}");x = 6;println!("The value of x is: {x}");// 常量const MAX_POINTS: u32 = 100_000;println!("The maximum points is: {MAX_POINTS}");// 变量遮蔽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 in the outer scope is: {x}");
}
运行结果如下
The value of x is: 5
The value of x is: 6
The maximum points is: 100000
The value of x in the inner scope is: 12
The value of x in the outer scope is: 6
现在我们已经了解了变量的工作方式,接下来让我们看看它们可以拥有的数据类型。