参考:https://kaisery.github.io/trpl-zh-cn/ch03-01-variables-and-mutability.html、https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html
不可变变量
含义
一旦值被绑定一个名称上,就不能改变这个值。Rust 中变量默认不可变。
例如: let x = 1;
优势
- 充分利用 Rust 提供的安全性和简单并发性来编写代码
- Rust 编译器不需要追踪一个值如何和在哪可能会被改变,从而使得代码易于推导
- 遮蔽机制 (shadowing):可以使用
let
创建新的变量(或者称为重新定义变量、重新绑定变量)来改变值,甚至类型。如果新的值是变量改变前的类型,使用mut
有相同的作用,然而前后类型不同时,使用mut
是做不到的 ,例如
获取了字符串,然而关注的是其长度 (字符串可以扔掉),可以这样使用
let spaces = " ";
let spaces = spaces.len(); // shadow 上一个 spaces ,并且类型从字符串变成了整型
而同样的场景使用 mut
:
直接改变 spaces
的值不可行:
let mut spaces = " ";
spaces = spaces.len(); // error: expected &str, found usize
重新绑定,虽然程序通过,但得到 not need to be mutable
警告:
let mut spaces = " "; // warning: variable does not need to be mutable
let mut spaces = spaces.len(); // warning: variable does not need to be mutable
let mut spaces = " ";
let spaces = spaces.len(); // warning: variable does not need to be mutable
shadowing 和 scope 同时使用的例子:
fn main() {
// 此绑定生存于 main 函数中
let long_lived_binding = 1;
// 这是一个代码块,比 main 函数拥有更小的作用域
{
// 此绑定只存在于本代码块
let short_lived_binding = 2;
println!("inner short: {}", short_lived_binding);
println!("inner long: {}", long_lived_binding);
// 此绑定*掩蔽*了外面的绑定
let long_lived_binding = 5_f32;
println!("inner long after shadowed: {}", long_lived_binding);
}
// 代码块结束
// 报错!`short_lived_binding` 在此作用域上不存在
// println!("outer short: {}", short_lived_binding);
println!("outer long: {}", long_lived_binding);
// 此绑定同样*掩蔽*了前面的绑定
let long_lived_binding = 'a';
println!("outer long after shadowed: {}", long_lived_binding);
}
打印结果:
inner short: 2
inner long: 1
inner long after shadowed: 5
outer long: 1
outer long after shadowed: a
不可变与常量的区别
- 声明方式:不可变使用
let
,通常编译器会提示使用 snakecase(使用 `连接单词,且每个单词小写);常量使用
const且必须标注类型,通常编译器会提示使用 UPPER_SNAKE_CASE (使用
_` 连接单词,且每个单词大写) - 表达式类型:不可变变量可接受多种表达式;常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值
- 定义/绑定名称次数:不可变变量允许多次把名称绑定新的值,并且这种绑定可以是可变也可以是不可变(但会出现警告),但不能是常量绑定(即用 const 绑定);常量不允许把名称再次绑定值(无论是否为常量绑定),也就是只能定义一次名称。例如下面的例子:
不可变变量允许多次绑定:
fn main() {
let x = 1;
let x = 2; // x 可以再次进行绑定不可变值的操作,或者说此处的 x 隐藏了(shadow)上一行的 x
let mut x = 3; // x 可以再次进行绑定可变值的操作,但是出现 not need to be mutable 警告
println!("{}", x);
}
常量只允许一次绑定:
fn main() {
const Y: u32 = 2;
let Y: u32 = 1; // Y 不可以再次进行 let 绑定值的操作,即便标注相同的类型。常量不能当做变量使用。
println!("{}", Y);
}
fn main() {
const Y: u32 = 2;
const Y: u32 = 1; // Y 不可以再次进行 const 绑定值的操作,即便标注相同的类型。常量只能定义一次(must be defined only once)。
println!("{}", Y);
}
不可变量、常量、静态常/变量的区别:见 “unsafe Rust”
20.1 unsafe Rust
可变变量
含义
允许改变值、表明其他代码将会改变这个变量值的意图。在变量名之前加 mut
来使其可变。
例如 let mut x = 1;
优势
- 与只用不可变变量相比,可变性会让代码更容易编写
- 使用大型数据结构时,适当地使用可变变量,可能比复制和返回新分配的实例更快
(可读性)对于较小的数据结构,总是创建新实例,采用更偏向函数式的编程风格,可能会使代码更易理解
劣势
牺牲性能
- 更容易出现 bug (与编译器作斗争)
变量先声明
可以先声明(declare)变量绑定,后面才将它们初始化(initialize)。但是这种做法很 少用,因为这样可能导致使用未初始化的变量。
编译器禁止使用未经初始化的变量,因为这会产生未定义行为(undefined behavior)。fn main() {
// 声明一个变量绑定
let a_binding;
{
let x = 2;
// 初始化一个绑定
a_binding = x * x;
}
println!("a binding: {}", a_binding);
let another_binding;
// 报错!使用了未初始化的绑定
println!("another binding: {}", another_binding);
// 改正 ^ 注释掉此行
another_binding = 1;
println!("another binding: {}", another_binding);
}