变量的可变性
默认情况下,变量是不可变的。这是Rust为您提供的许多小建议之一,可以利用Rust提供的安全性和易并发性来编写代码。但是,您仍然可以选择使变量可变。让我们来探讨Rust如何以及为什么鼓励您支持不变性,以及为什么有时您可能会选择退出。
当变量不可变时,一旦值绑定到名称,就不能更改该值。为了说明这一点,让我们使用cargon new variables生成一个名为变量的新项目。
然后,在新的变量目录中,打开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` due to previous error
本例显示出编译器怎样帮助开发人员发现代码中的错误。编译错误让人沮丧,但事实上错误信息仅意味着你的程序不能安全地做你想让它做的事吗。这些错误并不意味着你不是一个好的程序员。有经验的Rust程序员也会出现编译时错误。
错误信息指出,错误原因是你不能给不可变的变量x赋值两次,因为你试图给一个不可变量第二次赋值。
当我们试图改变一个被设计成不可变变量的值时,运行时错误是很重要的,因为这是导致Bug的情况。如果我们的代码的一部分基于一个值永远不会改变的假设而运行,而我们的另一部分代码改变了这个值,那么代码的第一部分就有可能无法完成它的设计任务。这种错误的原因很难在事后追踪,尤其是当第二段代码只在某些时候更改值时。Rust编译器保证当你声明一个值不会改变时,它真的不会改变,所以你不必自己跟踪它。因此,您的代码更容易推理。
但是可变性非常有用,可以使代码更方便编写。虽然变量在默认情况下是不可变的,但您可以像第2章中所做的那样,通过在变量名称前面添加mut来使其可变。添加mut还通过指示代码的其他部分将更改此变量的值来向代码的未来读者传达意图。
例如,让我们改一下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关键字声明常量,并且必须显示指定类型。我们将在下一节“数据类型”中介绍类型和类型注释,所以现在不要担心细节。只需知道您必须始终注释类型。
常量可以在任何范围内声明,包括全局范围,这使得它们对于代码的许多部分需要了解的值非常有用。
最后一个区别是常量只能设置为常量表达式,而不是只能在运行时计算的值的结果。
以下是常量声明的示例:
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
常量名字叫THREE_HOURS_IN_SECONDS 并且值补设置为60*60*3计算3小时的秒数。Rust为常量的命名习惯是:用下划线将大写单词连接。编译器能够在编译时评估一组有限的操作,这使我们可以选择以更易于理解和验证的方式写出该值,而不是将该常量设置为值10800。有关声明常量时可以使用哪些操作的更多信息,请参阅Rust Reference的常量评估部分。
常量在声明的范围内,程序运行的整个时间内都有效。常量在应用程序中多个部分都可以使用特性非常有用,如游戏中的用户最大分数和光的速度。
将整个程序中使用的硬编码值命名为常量,有助于未来代码维护者理解该值的含义。如果将来需要更新硬编码值,那么代码中只有一个地方需要更改,这也会有所帮助。
变量遮蔽
正如你在第二章猜数字游戏中看到的,可以用相同的名字声明一个变量。Rust程序员认为第一个变量被第二个变量遮蔽了。意思是当你使用这个名变量时,只有第二个变会被编译器将要看到。效果是第二个变量遮蔽了第一个变量,在作用范围结束或者自身被遮蔽之前,这个变量可以任意使用。我们可以使用相同的变量名遮蔽一个变量并且重复使用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}");
}
此程序首先将5与x关连,然后创建一个新变量,然后在原变量上加1,所以x 的值为6。然后在内部作用域创建了第三个x变量遮蔽了之前的x,并且将x*2创建新的变量。当内部作用域结束时,内部遮蔽结束,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
遮蔽不同于让变可变,如果我们偶然试图用let关键字重新赋值时,会得到编译时错误。通过使用let,我们可以转换变量值的类型,但是在转换完成之后,此变量值不能再可改变。
mut和遮蔽另外的不同是,当我们再次用let关键字时,我们创建了一个新变量,我们可以重用变量名并改变其类型。例如我们的程序请求显示在文本之间输入的空格数,我们可以将原字符变量重新赋值为空格个数。
let spaces = " ";
let spaces = spaces.len();
第一个spaces变量是一个字符串类型,而第二个spaces变量是一个数值类型。
因此,遮蔽使我们不必想出不同的名称,例如spaces_tr和spaces_num;取而代之的是重用spaces这个较简单的名字。然而如果我们试图使用mut关键字,我们将得到错误,代码如下:
let mut spaces = " "; spaces = spaces.len();
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` due to previous error
现在我们知道变量如何工作,接下来让我们了解更多的数据类型