陪老 K 学 Rust (五)
可变与不变
1 赋值与绑定
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn uses_foobar(foobar: &Foobar) {
println!("I consumed a Foobar: {:?}", foobar);
}
fn main() {
let mut x = Foobar(0);
let mut z = Foobar(1);
let mut y = &mut x;
uses_foobar(y);
y.0 = 1;
uses_foobar(y);
y = &mut z;
uses_foobar(y);
y.0 = 3;
uses_foobar(y);
}
在很多语言中(其实也包括 Rust), let
的含义并不是声明一个变量,而是进行一个值 绑定
操作,也就是把一个值和一个名称关联起来,从这一点上来说 绑定
比 赋值
更形象。
2 可变与不变
还记得 C 关于 const
关键字的 常量指针
与 指针常量
的问题吗?const char * const p = &str;
, 我们就以分析 const
的方法来分析 mut
关键字:
let mut x: Foobar = Foobar(0);
这种形式中,mut
修饰的是绑定关系还是值本身?mut
只修饰变量,即修饰变量和值的绑定关系,不修饰值本身。let mut y: &mut Foobar = &mut x;
这种引用形式中,第一个mut
限定的是绑定关系,也就是y
可以是x
的引用绑定,也可以是其他值的引用绑定。 第二个mut
限定的是被应用的值本身,即值本身的内容是否可以被此引用修改。第三个mut
的作用等同于第二个mut
, 在使用类型推断的情况下,这一点就更为明显:let mut y = &mut x;
.
2.1 修改值
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let x = Foobar(0);
println!("{:?}", x);
x.0 = 10;
println!("{:?}", x);
}
编译输出:
--> l20.rs:13:5
|
11 | let x = Foobar(0);
| - help: consider changing this to be mutable: `mut x`
12 | println!("{:?}", x);
13 | x.0 = 10;
| ^^^^^^^^ cannot assign
error: aborting due to previous error
结论:非 mut
绑定不能修改值的内容。
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let mut x = Foobar(0);
println!("{:?}", x);
x.0 = 10;
println!("{:?}", x);
}
编译运行输出:
Foobar(0)
Foobar(10)
Dropping a Foobar: Foobar(10
结论: mut
绑定可以修改值的内容。
2.2 修改绑定关系
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let x = Foobar(0);
let y = Foobar(1);
println!("{:?}", x);
x = y;
println!("{:?}", x);
}
编译输出:
error[E0384]: cannot assign twice to immutable variable `x`
--> l20.rs:14:5
|
11 | let x = Foobar(0);
| -
| |
| first assignment to `x`
| help: make this binding mutable: `mut x`
...
14 | x = y;
| ^ cannot assign twice to immutable variable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0384`
结论:非 mut
绑定不能修改绑定关系。
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let mut x = Foobar(0);
println!("{:?}", x);
let y = Foobar(1);
println!("{:?}", y);
x = y;
println!("{:?}", x);
x.0 = 10;
println!("{:?}", x);
}
编译运行输出:
Foobar(0)
Foobar(1)
Dropping a Foobar: Foobar(0)
Foobar(1)
Foobar(10)
Dropping a Foobar: Foobar(10)
结论: mut
绑定可以修改绑定关系,并且可以修改值的内容。这个修改与 y
原来是否是 mut
无关。
2.3 重置绑定
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let x = Foobar(0);
println!("{:?}", x);
let x = Foobar(1);
println!("{:?}", x);
}
编译运行输出:
Foobar(0)
Foobar(1)
Dropping a Foobar: Foobar(1)
Dropping a Foobar: Foobar(0
结论: 无论是否是 mut
绑定,都可以重新绑定。
2.4 可变性修改
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let x = Foobar(0);
println!("{:?}", x);
let mut y = x;
println!("{:?}", y);
y.0 = 1;
println!("{:?}", y);
}
运行输出:
Foobar(0)
Foobar(0)
Foobar(1)
Dropping a Foobar: Foobar(1)
根据以上代码,下面的 mutable move 也就很好理解了。
#[derive(Debug)]
struct Foobar(i32);
fn main() {
let x = Foobar(1);
foo(x);
}
fn foo(mut x: Foobar) {
x.0 = 2; // changes the 0th value inside the product
println!("{:?}", x);
}
运行输出:
Foobar(2)
2.5 不变引用不变值绑定
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let x = Foobar(0);
let y = &x; // let y: &Foobar = &x;
println!("{:?}", x);
println!("{:?}", y);
}
编译运行输出:
Foobar(0)
Foobar(0)
Dropping a Foobar: Foobar(0)
println!("{:?}", x)
难道不会接管x
的所有权吗?注意: println! 是宏而不是函数,你焉不知这个宏看上去是用的x
, 在背后用的是&x
呢?
2.6 不变引用可变值绑定
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let mut x = Foobar(0);
println!("{:?}", x);
let y = &mut x; // let y: &mut Foobar = &mut x;
println!("{:?}", y);
y.0 = 10;
println!("{:?}", y);
}
编译运行输出:
Foobar(0)
Foobar(0)
Foobar(10)
Dropping a Foobar: Foobar(10)
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let mut x = Foobar(0);
println!("{:?}", x);
let y = &mut x; // let y: &mut Foobar = &mut x;
println!("{:?}", y);
let mut z = Foobar(1);
y = &mut z;
println!("{:?}", y);
}
编译报错:
error[E0384]: cannot assign twice to immutable variable `y`
--> l20.rs:16:5
|
13 | let y = &mut x; // let y: &mut Foobar = &mut x;
| -
| |
| first assignment to `y`
| help: make this binding mutable: `mut y`
...
16 | y = &mut z;
| ^^^^^^^^^^ cannot assign twice to immutable variable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0384`.
结论: y
是不变引用,其引用的值被 mut
修饰为可变。即: y
的绑定关系不能修改,但是 y
指向的值可以被修改。
2.7 可变引用不变值绑定
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let x = Foobar(0);
println!("{:?}", x);
let mut y = &x; // let mut y: &Foobar = &mut x;
println!("{:?}", y);
let z = Foobar(1);
y = &z;
println!("{:?}", y);
}
运行输出:
Foobar(0)
Foobar(0)
Foobar(1)
Dropping a Foobar: Foobar(1)
Dropping a Foobar: Foobar(0)
结论:可变引用可以改变绑定关系, y
并不特殊,也遵循可变绑定和不变绑定。
2.8 可变引用可变值绑定
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let mut x = Foobar(0);
println!("{:?}", x);
let mut y = &mut x; // let mut y: &Foobar = &mut x;
println!("{:?}", y);
let mut z = Foobar(1);
y = &mut z;
println!("{:?}", y);
}
运行输出:
Foobar(0)
Foobar(0)
Foobar(1)
Dropping a Foobar: Foobar(1)
Dropping a Foobar: Foobar(0)
结论:可变引用可以改变绑定关系, y
并不特殊,也遵循可变绑定和不变绑定。
2.9 不变引用的共享性
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let x = Foobar(0);
let y = &x; // let y: &Foobar = &x;
let z = &x; // let z: &Foobar = &x;
println!("{:?}", x);
println!("{:?}", y);
println!("{:?}", z);
}
运行输出:
Foobar(0)
Foobar(0)
Foobar(0)
Dropping a Foobar: Foobar(0)
结论: x
, y
, z
随便用。
2.10 可变引用的排他性
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let mut x = Foobar(0);
let y = &mut x; // let y: &mut Foobar = &mut x;
let z = &x; // let z: &Foobar = &x;
println!("{:?}", x);
println!("{:?}", y);
println!("{:?}", z);
}
编译报错:
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
--> l20.rs:13:13
|
12 | let y = &mut x; // let y: &Foobar = &x;
| ------ mutable borrow occurs here
13 | let z = &x; // let z: &Foobar = &x;
| ^^ immutable borrow occurs here
14 | println!("{:?}", x);
15 | println!("{:?}", y);
| - mutable borrow later used here
error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
--> l20.rs:14:22
|
12 | let y = &mut x; // let y: &Foobar = &x;
| ------ mutable borrow occurs here
13 | let z = &x; // let z: &Foobar = &x;
14 | println!("{:?}", x);
| ^ immutable borrow occurs here
15 | println!("{:?}", y);
| - mutable borrow later used here
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0502`.
结论:
println!
宏的确是转换成了引用。- 在
y
可变借用了x
, 以后,println!
的不变引用被拒绝。
2.11 强制不变引用和强制可变引用
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let mut x = Foobar(0);
let y = &x; // let y: &Foobar = &x;
let z = &x; // let z: &Foobar = &x;
println!("{:?}", x);
println!("{:?}", y);
println!("{:?}", z);
}
编译运行输出:
warning: variable does not need to be mutable
--> l20.rs:11:9
|
11 | let mut x = Foobar(0);
| ----^
| |
| help: remove this `mut`
|
= note: `#[warn(unused_mut)]` on by default
Foobar(0)
Foobar(0)
Foobar(0)
Dropping a Foobar: Foobar(0)
除了一个 x
的未使用的 mut
限定意外,运行没毛病,也就是: 可以以不变的方式引用可变绑定. 那我们反过来,以可变的方式应用不变绑定呢?
#[derive(Debug)]
struct Foobar(i32);
impl Drop for Foobar {
fn drop(&mut self) {
println!("Dropping a Foobar: {:?}", self);
}
}
fn main() {
let x = Foobar(0);
let y = &mut x; // let y: &mut Foobar = &mut x;
println!("{:?}", y);
}
编译报错:
error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
--> l20.rs:12:13
|
11 | let x = Foobar(0);
| - help: consider changing this to be mutable: `mut x`
12 | let y = &mut x; // let y: &mut Foobar = &mut x;
| ^^^^^^ cannot borrow as mutable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0596`.
结论: 不能 把不变绑定强制转换成可变引用。
扩展一下思路,在函数参数传递的场景下,
mut
的原则又是什么呢?
fn uses_foobar(foobar: &Foobar)
fn uses_foobar(mut foobar: &Foobar)
fn uses_foobar(foobar: &mut Foobar)
fn uses_foobar(mut foobar: &mut Foobar)
3 再论可变与不变
由以上的栗子可知: Foobar
自身完全没有权利决定自己的内容是可变的还是不变的,其内容能否可变,取决于在其被绑定时的绑定方式。象 Foopbar
这种元组还不是特别明显,以 struct
作为参考:
struct Greet {
age: i32,
score: i32
}
fn main() {
let f1 = Greet{age: 18, score: 60};
let mut f2 = Greeg{age: 20, score: 80};
}
在以上代码中,实际上 Greet
的字段都是默认可变的。:( 这听上去怎么和 Rust 的值默认不变相矛盾?
在其他一些语言中, let
和 var
来分别代表不变绑定和可变绑定(如:swift),并且可变和不可变的作用是单一的,只用来限定绑定关系是否可变。值本身的内容由值的类型来决定,这么说有些抽象,还是拿 Greet
的栗子来说:
struct Greet {
let age: Int32,
var score: Int32,
}
func main() {
let f1 = Greet(age: 10, score: 60)
f1.score = 80 // Ok, 因为 score 是 var, 可变的.
f1 = Greet(age: 20, score: 80) // Nope, 因为 f1 是 let, 不变的,不能改变绑定关系。
var f2 = Greet(age: 10, score: 60)
f2.age = 10 // Nope: 虽然 f2 是可变的,但是 age 在 struct 内部是不变的。
f2 = Greet(age: 20, score: 80) // Ok, f2 可以重复绑定。
}
相对比来说, swift 的模型貌似更符合一个正常的心智模型,而 Rust 确是怪怪的,私自以为 rust 对于 mut
的处理非常不合理,一个数据类型是否可变居然不取决于其自身的设计。在设计之初,没有不可变的选择。:(, 相反在这一点上 swift
更加合理。
4 胡乱说的模型
如果 Rust 代码的语法是这样的,可能一致性更好一些:
fn main() {
let x: mut Foobar = mut Foobar(0);
let mut y: mut Foobar = mut Foobar(1);
let mut z = mut Foobar(3);
let o: Foobar = Foobar(4);
}
这样,第一个 mut
修饰绑定关系,第二个 mut
修饰内容就和 借用/引用
保持一致了。:) 可惜现实不是这样的,我们姑且把 let x: mut Foobar = mut Foobar(0);
这种看成是默认的语法糖吧。