Rust 变量和可变性
本笔记主要来自于 Rust 程序设计语言 中文版 [3.1],旨在记录个人学习过程中的重点和心得体会。在记录过程中,笔者会对文档中的某些内容进行摘抄或修改,并添加自己的注释或说明。如有不当之处,请指正。
在 Rust 中,变量默认是不可变的,意味着一个值绑定到一个变量名后,就不能再更改它。
fn main() {
let num = 0;
println!("num = {}", num);
num = 2;
println!("num = {}", num)
}
例如上面这段代码,如果运行那么会抛出一条错误信息:
# clover @ MacBook-Pro in ~/dev/rust/learn/variables on git:master x [9:47:26] C:101
$ cargo build
Compiling variables v0.1.0 (/Users/clover/dev/rust/learn/variables)
error[E0384]: cannot assign twice to immutable variable `num`
--> src/main.rs:5:5
|
2 | let num = 0;
| ---
| |
| first assignment to `num`
| help: consider making this binding mutable: `mut num`
...
5 | num = 2;
| ^^^^^^^ 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
这段代码的错误原因是 cannot assign twice to immutable variable
(不可以对不可变变量进行二次赋值)。值得一提的是,如果变量没有绑定初始值,那么是可以通过符号 =
绑定值,这是被允许的。
let num;
num = 0;
该引用来自[3.1]
当我们尝试改变一个前面指定为不可变的值时我们会得到编译期错误,这点很重要,因为这种情况很可能导致 bug。如果我们代码的一部分假设某个值永远不会更改,而代码的另一部分更改了该值,那很可能第一部分代码以不可意料的方式运行。这个 bug 的根源在实际开发中可能很难追踪,特别是第二部分代码只是偶尔变更了原来的值。Rust 编译器保证了当我们声明了一个值不会改变时,那它就真的不可改变,所以你不必亲自跟踪这个值。这可以使得代码更容易理解
在实际开发中,我们的变量通常都会被修改,所以可以在变量名前面加上 mut
将其声明为可变变量。
fn main() {
let mut num: i32 = 0;
println!("num = {}", num);
num = 1;
println!("num = {}", num);
}
此时使用 cargo run
命令去运行它
# clover @ MacBook-Pro in ~/dev/rust/learn/variables on git:master x [11:12:44]
$ cargo run
Compiling variables v0.1.0 (/Users/clover/dev/rust/learn/variables)
Finished dev [unoptimized + debuginfo] target(s) in 1.02s
Running `target/debug/variables`
num = 0
num = 1
常量
它与不可变变量类似,常量是绑定到一个常量名且不允许更改的值。但是常量和变量存在一定差异,因为常量不允许使用 mut
修饰常量不仅默认不可变,而且自始至终都不可变。产量使用 const
来声明而不是 let
,并且值类型必须注明。
常量可以在任意作用域内声明,包括全局作用域。
最后一个不同点是,常量只能设置常量表达式,而不是函数调用的结果或只能在运行时计算得到的值。
const AUTHOR_AGE: &str = 2 * 10;
Rust 中的常量命名规范是:字母全部大写并且使用下划线分割单词。Rust 编译器能够在编译时计算一些简单的操作,这可以让我们方便理解和验证的方式写出这个值,而不是将常量设置为值 20。
在声名的作用域内,程序运行的整个过程中它都有效。对于某些固定不变且使用广泛的值,我们都可以定义为一个常量使其更容易维护。
例如我们有一个常量 const AUTHOR_NAME: &str = "clover";
,它用于记录当前程序的作者,如果有一天程序作者发生改变,那么只需要修改这一处即可!
遮蔽
fn main() {
let x = 2;
let x = x + 1;
{
let x = x * 5;
println!("inner scope x: {}", x)
}
println!("x: {}", x)
}
先将数值2绑定到 x
,通过重复使用 let x
来遮蔽之前声明的 x
,并且将原来的值加1,所以 x
变成3。在内部作用域内,再次遮蔽之前的 x
并将值乘5得到15。当遮蔽结束时,内部遮蔽结束且将 x
还原回3。
执行以上重新可以得到结果:
# clover @ MacBook-Pro in ~/dev/rust/learn/variables on git:master x [11:56:41] C:101
$ cargo run
Compiling variables v0.1.0 (/Users/clover/dev/rust/learn/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.70s
Running `target/debug/variables`
inner scope x: 15
x: 3
遮蔽和将变量标记为
mut
的方式不同,因为除非我们再次使用let
关键字,否则若是我们不小心尝试重新赋值给这个变量,我们将得到一个编译错误。通过使用let
,我们可以对一个值进行一些转换,但在这些转换完成后,变量将是不可变的。
(其实就是和其它语言一样,Rust 也是使用就近原则来查找指定变量,而 let
允许重复声明)
mut
和遮蔽变量的另外一个区别就是我们再次通过 let
关键字来创建一个新的变量,所以我们可以改变值的类型和重复使用变量的名称。假设程序需要用户输入字符然后显示输入字符数,这种需求实际上就是需要一个数字而已:
let token: &str = "abc";
let token: i32 = token.len(); // 3
第一个 token
是一个字符类型,第二个 token
变成了数字。所以变量遮蔽可以让我们不再取新的变量名而是使用简单的 token
。
我认为在同一个作用域中频繁使用遮蔽变量会让代码变得很难理解,所以在大部分场景中,我们使用简单易懂的变量名就好。
如果我们使用 mut
来声明变量,那么编译期就会得到一个错误:
let mut token: &str = "";
token = token.len();
该错误表明我们不能修改变量的类型:
# clover @ MacBook-Pro in ~/dev/rust/learn/variables on git:master x [9:11:49]
$ cargo run
Compiling variables v0.1.0 (/Users/clover/dev/rust/learn/variables)
error[E0308]: mismatched types
--> src/main.rs:20:13
|
19 | let mut token: &str = "";
| ---- expected due to this type
20 | token = token.len();
| ^^^^^^^^^^^ expected `&str`, found `usize`
|
help: try removing the method call
|
20 - token = token.len();
20 + token = token;
|
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` due to previous error