变量和可变性

  1. fn main() {
  2. let x = 5;
  3. println!("The value of x is:{x}");
  4. x = 6;
  5. println!("The vlaue of x is:{x}");
  6. }

而在 Rust 中,我们这样写: let x = 5; ,同时给这个过程起了另一个名字:变量绑定

为何不用赋值而用绑定呢(其实你也可以称之为赋值,但是绑定的含义更清晰准确)?这里就涉及 Rust 最核心的原则——所有权,简单来讲,任何内存对象都是有主人的,而且一般情况下完全属于它的主人,绑定就是把这个对象绑定给一个变量,让这个变量成为它的主人,像极了我们的现实世界,不是吗?

运行会出现以下报错

:::tips 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

:::

不能对不可变变量 x 进行二次赋值(cannot assign twice to immutable variable)

在尝试改变预设为不可变的值时,产生编译时错误是很重要的,因为这种情况可能导致 bug。如果一部分代码假设一个值永远也不会改变,而另一部分代码改变了这个值,第一部分代码就有可能以不可预料的方式运行。不得不承认这种 bug 的起因难以跟踪,尤其是第二部分代码只是 有时 会改变值。

Rust 编译器保证,如果声明一个值不会变,它就真的不会变,所以你不必自己跟踪它。这意味着你的代码更易于推导。

变量名前使用 mut来使其可变。

  1. fn main() {
  2. let mut x = 5;
  3. println!("The value of x is:{x}");
  4. x = 6;
  5. println!("The vlaue of x is:{x}");
  6. }

运行这个程序

:::tips /Users/yog/.cargo/bin/cargo run —color=always —package variables —bin variables

  1. Finished dev [unoptimized + debuginfo] target(s) in 0.00s
  2. Running `target/debug/variables`

The value of x is:5

The vlaue of x is:6

:::

通过 mut ,允许把绑定到 x 的值从 5 改成 6。是否让变量可变的最终决定权仍然在你,

常量

类似于不可变变量,常量(constants)是绑定一个名称不允许改变的值,不过常量与变量还是有一些区别。

首先,不允许对常量使用mut。常量不光默认不可变,它总是不可变。声明常量使用 const关键字而不是let,并且必须注明值的类型。

常量可以在任何作用域中声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。

最后一个区别是,常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值。

下面是一个声明常量的例子:

const THREE_HOURS_IN_SECONDS: u32 = 60*60*3;

Rust 对常量的命名约定是在单词之间使用全大写加下划线。编译器能够在编译时计算一组有限的操作,这使我们可以选择以更容易理解和验证的方式写出此值,而不是将此常量设置为10800。

在声明它的作用域之中,常量在整个程序生命周期中都有效,此属性使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分数或者光速。

将遍布于应用程序中的硬编码值声明为常量,能帮助后来的代码维护人员了解值的意图。如果将来需要修改硬编码值,也只需要修改汇聚于一处的硬编码值。

:::tips

在编写软件或应用程序时,开发者通常会在代码中直接使用具体的数值或字符串等数据,这种做法被称为“硬编码”。例如,如果你在多个地方使用了数字 10 来表示允许的最大登录尝试次数,这个 10 就是一个硬编码值。

:::

隐藏

我们可以定义一个与之前变量同名的新变量。Rustacean 们称之为第一个变量被第二个隐藏(Shadowing)了,这意味着当您使用变量名称时,编译器将看到第二个变量。实际上第二个变量“遮蔽”了第一个变量,此时任何使用该变量名的行为中都会视为在使用第二个变量,知道第二个变量自己也被隐藏或第二个变量的作用域结束。可以使用相同的变量名称来隐藏一个变量,以及重复使用 let关键字来多次隐藏,如下所示:

  1. fn main() {
  2. let x= 5;
  3. let x = x + 1;
  4. {
  5. let x = x * 2;
  6. println!("The value of x in the inner scope is:{x}");
  7. }
  8. println!("The value of x is:{x}");
  9. }

这个程序首先将x绑定到值5上。接着通过 let x = 创建来一个新变量 x ,获取初始值并加1,这样x的值就变成6了。

然后,在使用花括号创建的内部作用域内,第三个let语句也隐藏了x并创建了一个新的变量,将之前的值乘以2,x得到的值是12。

当该作用域结束时,内部 shadowing 的作用域也结束了,x又返回到6。运行这个程序它会有以下输出:

:::tips /Users/yog/.cargo/bin/cargo run —color=always —package variables —bin variables

  1. Finished dev [unoptimized + debuginfo] target(s) in 0.00s
  2. Running `target/debug/variables`

The value of x in the inner scope is:12

The value of x is:6

进程已结束,退出代码为 0

:::

隐藏于将变量标记为mut是有区别的。当不小心尝试对变量重现赋值时,如果没有使用let关键字,就会导致编译时错误。通过使用let,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不可变的。

mut于隐藏的另一个区别是,当再次使用let时,实际上创建了一个新变量,我们可以改变值的类型,并且复用这个名字。例如,假设程序请求用户输入空格字符来说明希望在文本之间显示多少个空格,接下来我们想将输入储存成数字(多少个空格):

  1. let spaces = " ";
  2. let spaces = spaces.len();

第一个 spaces 变量是字符串类型,第二个 spaces 变量是数字类型。隐藏使我们不必使用不同的名字,如 spaces_str 和 spaces_num;相反,我们可以复用 spaces 这个更简单的名字。然而,如果尝试使用 mut,将会得到一个编译时错误,如下所示:

  1. let mut spaces = " ";
  2. spaces = spaces.len();

这个错误说明,我们不能改变变量的类型:

:::tips $ 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

:::

使用下划线开头忽略未使用的变量

如果你创建了一个变量却不在任何地方使用它,Rust 通常会给你一个警告,因为这可能会是个 BUG。但是有时创建一个不会被使用的变量是有用的,比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头

  1. fn main() {
  2. let _x = 5;
  3. let y = 10;
  4. }

运行代码:

:::tips warning: unused variable: y

—> src/main.rs:3:9

|

3 | let y = 10;

| ^ help: 如果 y 故意不被使用,请添加一个下划线前缀: _y

|

= note: #[warn(unused_variables)] on by default

:::

可以看到,两个变量都是只有声明,没有使用,但是编译器却独独给出了 y 未被使用的警告,充分说明了 _ 变量名前缀在这里发挥的作用。

值得注意的是,这里编译器还很善意的给出了提示( Rust 的编译器非常强大,这里的提示只是小意思 ): 将 y 修改 _y 即可。这里就不再给出代码,留给大家手动尝试并观察下运行结果。

解构

  1. let (mut x, y) = (1, 2);

在 Rust 语言中,let (x, y) = (1, 2); 是一个使用模式匹配(pattern matching)来同时声明和初始化两个变量 x 和 y 的语句。这种写法被称为“解构赋值”(destructuring assignment),允许你在单个语句中对多个变量进行赋值。
这行代码做了以下几件事情:

  1. 声明变量:声明了两个变量 xy
  2. 赋值:将整数 1 赋值给变量 x,将整数 2 赋值给变量 y
  3. 可变性(Mutability):关键字 mut 表示变量是可变的。在这个例子中,mut x 表明 x 可以在之后的代码中被重新赋值,即它的值可以被修改。相比之下,y 没有 mut 前缀,这意味着 y 是不可变的,它的值一旦赋值后就不能被改变。

练习

Rust By Practice,支持代码在线编辑和运行,并提供详细的习题解答