携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情
从这一篇开始,我们会结构化地学习 Rust 相关的语法知识。今天我们的主题是变量,常量以及 可变性(mutability)。
上一节我们提到过,Rust 的变量默认都是不可变的,即 immutable,这样在编译期进行检查能够很大程度上减轻开发者的心智负担,毕竟一个 immutable 的变量一定是并发安全的(类似 Golang 中的 string)。当然,我们也可以通过加上 mut 标识来让一个变量变成 mutable,这样就可以更改了。
今天我们一起来看看这样设计的原因以及是如何助力我们开发。
变量不可变
对于一个 immutable 的变量,一旦发生赋值,你就无法再改变这个值。我们来实验一下:
- 首先在我们的 rust-learn 目录下,用
cargo new variable来创建一个新工程。
- 更新 main.rs 为以下内容:
fn main() {
let x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
我们先声明了一个变量 5,这里没加 mut 所以它是默认 immutable 的。随后尝试赋值 6,看看能不能正常运行。
- 进入 variable 目录,触发
cargo build编译。
$ cargo build
=======================================================
Compiling variable v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/variable)
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 `variable` due to previous error
果然,这里报错信息也很明确 cannot assign twice to immutable variable 不能两次赋值。如果确实需要,consider making this binding mutable 考虑让它变成 mutable 的。
Rust 编译器能够保证,当你声明一个值是 immutable 时,它真的就会是 immutable,不会改变,你不用自己去分析各种链路代码,这样心智负担会小很多。
这里还是要说 Rust 这样的强编译器检查还是很有帮助的。要求严格,把问题扼杀在编译期,这样你的运行时环境会稳很多。虽然写代码时可能会痛苦一些,因为你要遵循规范,要多思考一些,但是这样也能保证 least surprise。而且,Rust 编译器不仅仅告诉你为什么报错,还会给你具体的位置,错误详细信息,甚至怎么改都给出了提示。还是要适应好这种规范。
mut
在变量名称前加上 mut 就能让它变成 mutable 的,用起来很简单,而且关键在于读者在看代码的时候就能识别这种意图,这个变量是会被改的,用的时候要小心。
我们把 main.rs 做一下修改,加上 mut 再试试:
fn main() {
let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
我们执行下编译,再运行下看看
$ cargo build
========================
Compiling variable v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/variable)
Finished dev [unoptimized + debuginfo] target(s) in 2.84s
$ cargo run
============================
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/variable`
The value of x is: 5
The value of x is: 6
这次就符合预期了,两个print 都输出,mut 生效。
所以,语法上 Rust 其实都支持,具体要不要加 mut 需要开发者自行来评估,选择最适用业务场景的用法。
常量
其实默认不可变的【变量】从直觉上看对我们来说,就是一个常量。为什么还有一个【常量】?
我们来看一下区别:
- 我们使用
const而不是let关键字来声明一个【常量】,并且必须显式地声明类型(不像变量可以自己推断); - 你不能针对【常量】加
mut前缀,【常量】是不可能修改的; - 常量可以在任意 scope 中定义,包括全局定义;
- 只能用【常量表达式】对【常量】赋值,不能出现任何需要在运行时计算才能得出的值给【常量】赋值。
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
这里定义了一个名为 THREE_HOURS_IN_SECONDS 的常量,它的类型是 u32,值可以在编译器就计算出,这样也省得我们直接写 10800(很不好理解为什么冒出来这个数字)
Rust 对【常量】的命名规范是:大写 + 下划线。
【常量】的生命周期取决于所属的 scope,只要项目在运行,且在 scope 内,你就能够访问到这个【常量】。所以我们可以把一些 magic number 收敛起来变成【常量】,加上合理的注释,方便以后维护。
变量隐藏
这里是指 variable shadowing,各个语言中都有,Rust 也不例外。我们在上一篇文章其实已经见过了。一句话:使用一个此前的变量名称来定义一个新的变量。
此时,我们说原先的变量被新的变量给 shadow(隐藏)了。从此以后,编译器看到这个名称,只会关联到新变量,直到这个 scope 完结,或者新变量又被未来更新的变量 shadow。
看个示例,更新我们的 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}");
}
运行后输出:
$ cargo run
=============================================
Compiling variable v0.1.0 (/Users/ag9920/go/src/github.com/ag9920/rust-learn/variable)
Finished dev [unoptimized + debuginfo] target(s) in 1.03s
Running `target/debug/variable`
The value of x in the inner scope is: 12
The value of x is: 6
x 就这样从 5 变成 6,在 {} 这个 scope 内变成 12。理解 scope 范围很重要,你可以把它理解成【局部变量】的作用域。
注意区分不同 scope 下被 shadow 的变量 和 被 mut 修饰的变量。
一旦被 mut 修饰,代表这个变量本身是可以被修改的。
而被 shadow 的变量,代表着在这个 scope 内,有了完全不一样的含义。
事实上如果我们把 {} 内的 let x = x * 2 改成 x = x * 2 这里会直接报错,因为你没有 shadow,那么用的还是原来的 x 变量,而默认变量是 immutable 的,所以不能修改。
这里之所以修改,是因为虽然在这个 scope 里它也叫 x,但跟外面的 x 不是一码事。改了这里的 x,一旦回到上个 scope,你会发现原来的 x 还是以前的值,它是 immutable 的。
另一个需要注意的知识点在于,虽然我们上面举例的 shadow 发生在同类型之间,事实上换 type 也是没问题的。
例如我们可以这样,先声明一个字符串类型的变量,随后保持同名,改成一个数字类型变量。
let spaces = " ";
let spaces = spaces.len();
这样的好处在于我们不用再定义一个 spaces_num, 或者 spaces_str,这一点还是很有用处的,省很多事。否则同一个语义的东西我们要折腾出来好几个名字。
但是注意,还是要区分 shadow 和 mut,上面说的规则是 shadow,不要以为标识了 mut 之后也能串着类型来修改,下面这样是 不 ok 的:
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