Rust 中给一个变量赋值这件事情上,和其他语言的差别很大:
let argu = 12;
这段代码,在 Rust 中可以这样拆分:
let
: 绑定
argu
: 位置表达式(左值)
12
: 值表达式(右值)
相应的,上述代码做了三件事:
- argu:对位置表达式求值,得到一个内存位置的值,例如: 0x6fffde…
- 12: 对值表达式求值,得到一个具体的值: 12
- let 将两个结果绑定
位置表达式
位置表达式的目的就是获得一个内存位置,也就是内存地址,Rust 中位置表达式可以分为:
- 解引用位置表达式:*p
- 数组索引位置表达式:argu[idx]
- 字段点语法位置表达式: obj.field
- 变量初始化: let mut val = 9
其他的基本都是值表达式;位置表达式执行环境,叫位置上下文。
位置表达式在值上下文环境中的绑定行为: Copy 和 Move
当一个位置表达式出现在值的执行环境中时,通常有两种形式:
- copy :复制,栈内容的复制
- move:转移所有权,旧的的位置表达式不可用。
例如:
let a = 9; let b = a; // a 是一个位置表达式,出现在值表达式的位置(=右边为值表达式的执行环境); // 此时发生了 Copy,因为 a 是基本数据类型,实现了 Copy trait, // 所以,把 a 中的值,拷贝一份,绑定到 b 上。 a; // a 依然可用 let s = String::from("hi"); // String 是堆上的数据 let s1 = s; // s 出现在值上下文,s 又是String类型,发生Move,转移所有权给 s1; s; // Error, s 不可用。
可变绑定
Rust 从函数式那里借鉴了很多,除了面向表达式;另一大特性就是默认不可变。即默认定义的变量都是不可变的绑定。一旦某个内存位置绑定了一个值,就不允许再次修改。
let a = 12; a = 13; // Error;❌
如果想要修改,就需要显式的声明为可变:
let mut a = 12; a = 13; // ✅
或者使用遮蔽绑定:
let a = 12; let a = 13; // ✅,这事实上是一种重新绑定。后续 a 依然是不可变的,只不过这里将 a 重新绑定为 13 a = 14; // ❌
可变引用
Rust 虽然也支持类似于 C 的原始指针(Raw Pointer),但是一般只能在 Unsafe Rust 模式下使用,在 Safe Rust 模式下,Rust 推荐使用引用:
let a = 12; let p = &a; // 对 a 的引用借用
由于 a 是不可变的,所以 p 也是个不可变引用。引用相比原始指针,多了很多 Safe Rust 的编译期安全检查,对于不可变数据,是无法使用可变引用的:
let a = 12; let p = &mut a; // ❌
因为原始数据是不可变的,所以 Rust 限制了,指向它的引用也不能修改原始值,这充分体现了 Rust 这门语言设计的不可变一致性。在 C 语言中,只要拿到指针,就可以对内存随意修改。Rust 中用引用 + 编译期检查来确保不可变一致性。如果想要使用可变引用,就必须将原值声明了可变值:
let mut a = 12; let p = &mut a; // ✅
此外,可变引用同一时间只能有一个,也叫独占引用;因为可变引用可以修改原值。而不可变引用可以同时存在多个,也叫共享引用;因为不可变引用只访问原值,不修改原值。这些都是 Rust 在编译期检查的。