Rust变量状态
默认不可变状态
首先我们开始讲解变量状态,Rust变量状态表示变量所绑定的类型值是否可以修改。
Rust变量的状态有两种
- 默认不可变(immutable);
- 可变(mutable);
为什么 Rust 默认不可变变量?什么时候才会考虑使用可变变量。
首先,Rust 默认变量值不可变与确保内存安全、易于实现并发编程的目标有直接关系。
- 当变量值不可变时,意味着一旦值被绑定一个变量上,就不能改变值;
- 不可变变量可以被编译器优化提高程序的性能;
我们简单做演示:
使用 cargo new variables 命令在 projects 目录生成一个叫做 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 运行程序。应该会看到错误信息如:
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
编译器已经帮助我们找出程序中的错误!
Rust开发很多时候就是和编译器斗争的过程。
这个过程绝对值得,因为编译器可以帮助我们提前发现代码中潜在错误。
:smile: Rust语言也被人们称为面向编译器的语言。
错误信息指出: 不能对不可变变量 x 二次赋值(cannot assign twice to immutable variable x)。
代码尝试改变不可变变量的值,编译时错误是很重要的,否则很可能导致难以跟踪发现的 bug。
Rust 编译器严格执行:如果声明一个值不会变,它就真的不会变。
当阅读和编写代码时,对于不可变变量,我们无需刻意追踪值会改变,这也使得代码易于阅读推导。
可变状态
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 run
Compiling variables v0.1.0 (file:///projects/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.30 secs
Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
mut关键字允许把绑定到 x 的值从 5 改成 6。
某些时候,为了让代码更容易编写,我们会考虑在代码中使用可变变量。
:bell:但这是一种交换:得到便捷的同时意味着失去,可变变量使得代码的跟踪难度大大增加。
如何衡量取舍变量状态
除了防止出现 bug 外,到底是使用可变变量还是不可变变量,还需要我们权衡考虑以下方面:
使用大型数据结构时,采用可变变量,这样可能比借助值复制得到新实例性能更好;
对于较小的数据结构,采用不可变变量,执行值复制策略更可取,因为代码更易理解,为代码可读性牺牲一点性能是值得的。
变量与常量的区别
声明常量
- Rust 常量命名规范是使用下划线分隔的大写字母单词,插入下划线提高可读性;
- 不允许对常量使用
mut,常量值不光默认不能变,始终不能变;- 声明常量使用
const关键字而不是let,并且必须注明常量值的类型;- 常量可以在任何作用域中声明,包括全局作用域;
- 常量的值可以是常量表达式、不可以是函数调用的结果值;
下面是一个声明常量的例子:
const MAX_POINTS: u32 = 100_000;
const MAX_POINTS1: u32 = 1+3;
变量Shadowing
文件名: src/main.rs
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value of x is: {}", x);
}
变量Shadowing其实是重用变量名。
运行这个程序,如下输出:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
Running `target/debug/variables`
The value of x is: 12
变量Shadowing与可变变量的区别
变量Shadowing 与将变量标记为 mut 的区别:
- 当不小心尝试对不可变变量重新赋值时,如果没有使用
let关键字,就会导致编译时错误; - 使用
let x这种变量Shadowing的时候,变量 x 仍然是不变的; - 变量Shadowing 实际上创建了一个新变量,但复用这个变量名字、同时变量类型也可重新约定;
例如,假设程序请求用户输入空格字符来说明希望在文本之间显示多少个空格,然而我们真正需要的是将输入存储成数字(多少个空格):
let spaces = " ";
let spaces = spaces.len();
这里允许第一个 spaces 变量是字符串类型,而第二个 spaces 变量,它是一个恰巧与第一个变量同名的崭新变量,是数字类型。Shadowing使我们不必使用不同的名字,如 spaces_str 和 spaces_num;我们可以复用 spaces 这个更简单的名字。
如果使用 mut,以下代码将会得到一个编译时错误,如下所示:
let mut spaces = " ";
spaces = spaces.len();
这个错误说明:带mut关键字的变量不能改变值类型!
error[E0308]: mismatched types
--> src/main.rs:3:14
|
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected &str, found usize
|
= note: expected type `&str`
found type `usize`
现在我们已经了解了变量如何工作,让我们看看变量的类型都有哪些。