所有权是 Rust 最核心也最让人困惑的概念,而它的入口是「移动」。这篇用编译器的完整报错和运行时的地址、drop 顺序,把移动讲清楚:它拷贝了什么、没拷贝什么,编译器是怎么追踪「已被移走」的,以及值最终在哪里被释放。
源码:
1 | fn main() {
2 | let s1 = String::from("hello");
3 | let s2 = s1;
4 | println!("{s2}");
5 | println!("{s1}");
6 | }
编译:失败
error[E0382]: borrow of moved value: `s1`
--> <实验目录>/src/u16a.rs:5:16
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 | println!("{s2}");
5 | println!("{s1}");
| ^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let s2 = s1.clone();
| ++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0382`. 源码:
1 | fn main() {
2 | let s1 = String::from("hello");
3 | let p1 = s1.as_ptr();
4 | let s2 = s1; // 移动
5 | println!("移动后堆数据地址不变:{}", p1 == s2.as_ptr());
6 | let s3 = s2.clone(); // 克隆
7 | println!("克隆得到新的堆缓冲区:{}(地址不同)", s2.as_ptr() != s3.as_ptr());
8 | println!("size_of::<String>() = {} 字节", std::mem::size_of::<String>());
9 | }
编译:成功
运行(退出码 0):
移动后堆数据地址不变:true
克隆得到新的堆缓冲区:true(地址不同)
size_of::<String>() = 24 字节String 的内存布局见Box、Rc、Arc 的内存布局。
源码:
1 | fn takes(s: String) { println!("函数里:{s}"); } // s 在函数结束时被 drop
2 | fn gives_back(s: String) -> String { s }
3 | fn main() {
4 | let a = String::from("a");
5 | takes(a);
6 | // println!("{a}"); // 取消注释会报 E0382
7 | let b = String::from("b");
8 | let b = gives_back(b);
9 | println!("拿回来了:{b}");
10 | }
编译:成功
运行(退出码 0):
函数里:a
拿回来了:b
编译:失败
error[E0382]: borrow of moved value: `a`
--> <实验目录>/src/u16d.rs:5:16
|
3 | let a = String::from("a");
| - move occurs because `a` has type `String`, which does not implement the `Copy` trait
4 | takes(a);
| - value moved here
5 | println!("{a}");
| ^ value borrowed here after move
|
note: consider changing this parameter type in function `takes` to borrow instead if owning the value isn't necessary
--> <实验目录>/src/u16d.rs:1:13
|
1 | fn takes(s: String) { println!("{s}"); }
| ----- ^^^^^^ this parameter takes ownership of the value
| |
| in this function
help: consider cloning the value if the performance cost is acceptable
|
4 | takes(a.clone());
| ++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0382`. 源码:
1 | fn consume(_s: String) {}
2 | fn main() {
3 | let s = String::from("x");
4 | for _ in 0..2 { consume(s); }
5 | }
编译:失败
error[E0382]: use of moved value: `s`
--> <实验目录>/src/u16e.rs:4:29
|
3 | let s = String::from("x");
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
4 | for _ in 0..2 { consume(s); }
| ------------- ^ value moved here, in previous iteration of loop
| |
| inside of this loop
|
note: consider changing this parameter type in function `consume` to borrow instead if owning the value isn't necessary
--> <实验目录>/src/u16e.rs:1:16
|
1 | fn consume(_s: String) {}
| ------- ^^^^^^ this parameter takes ownership of the value
| |
| in this function
help: consider cloning the value if the performance cost is acceptable
|
4 | for _ in 0..2 { consume(s.clone()); }
| ++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0382`. 源码:
1 | struct D(&'static str);
2 | impl Drop for D { fn drop(&mut self) { println!(" drop {}", self.0); } }
3 | struct Pair { a: D, b: D }
4 | fn main() {
5 | let p = Pair { a: D("a"), b: D("b") };
6 | let moved = p.a; // 只移走字段 a
7 | println!("移走 a 之后,p.b 还能用:{}", p.b.0);
8 | {
9 | let _inner = moved; // 新所有者在这个块结束时 drop
10 | println!("进入内层块");
11 | }
12 | println!("内层块已结束,main 马上结束");
13 | }
编译:成功
运行(退出码 0):
移走 a 之后,p.b 还能用:b
进入内层块
drop a
内层块已结束,main 马上结束
drop bDrop 的规则见Drop Trait 与资源释放;不想转移所有权时用借用,见不可变借用与可变借用。