悬垂引用(指向已释放内存的引用)是 C/C++ 里最常见的崩溃来源,Rust 在编译期就把它们拒之门外。这篇把三种典型的悬垂场景各编译一遍、读编译器的报错,再用 unsafe 偷看一眼 Vec 重新分配时元素真的搬家了 —— 这就是编译器拒绝它们的原因。
源码:
1 | fn dangle() -> &String {
2 | let s = String::from("hello");
3 | &s
4 | }
5 | fn main() { println!("{}", dangle()); }
编译:失败
error[E0106]: missing lifetime specifier
--> <实验目录>/src/u18a.rs:1:16
|
1 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
|
1 | fn dangle() -> &'static String {
| +++++++
help: instead, you are more likely to want to return an owned value
|
1 - fn dangle() -> &String {
1 + fn dangle() -> String {
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0106`.
源码:
1 | fn dangle<'a>() -> &'a String {
2 | let s = String::from("hello");
3 | &s
4 | }
5 | fn main() { println!("{}", dangle()); }
编译:失败
error[E0515]: cannot return reference to local variable `s`
--> <实验目录>/src/u18b.rs:3:5
|
3 | &s
| ^^ returns a reference to data owned by the current function
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0515`. 源码:
1 | fn main() {
2 | let r;
3 | {
4 | let x = 5;
5 | r = &x;
6 | }
7 | println!("r = {}", r);
8 | }
编译:失败
error[E0597]: `x` does not live long enough
--> <实验目录>/src/u18c.rs:5:13
|
4 | let x = 5;
| - binding `x` declared here
5 | r = &x;
| ^^ borrowed value does not live long enough
6 | }
| - `x` dropped here while still borrowed
7 | println!("r = {}", r);
| - borrow later used here
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0597`. 源码:
1 | fn main() {
2 | let mut v = vec![1, 2, 3];
3 | let first = &v[0];
4 | v.push(4);
5 | println!("first = {}", first);
6 | }
编译:失败
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> <实验目录>/src/u18d.rs:4:5
|
3 | let first = &v[0];
| - immutable borrow occurs here
4 | v.push(4);
| ^^^^^^^^^ mutable borrow occurs here
5 | println!("first = {}", first);
| ----- immutable borrow later used here
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0502`.
为什么要禁止?用 unsafe 的裸指针亲眼看看 push 之后元素搬家了(只是为了观察,真实代码不要这么写):
编译:成功
运行(退出码 0):
push 之前 cap = 3,v[0] 的地址 = 0x100b65300
push 之后 cap = 6,v[0] 的地址 = 0x100b65170
地址变了:true(旧地址上的内存已经被释放)借用规则的细节见不可变借用与可变借用、可变借用为什么不能同时存在;容量增长见Vec 扩容机制。
源码:
1 | fn longest_owned() -> String { let s = String::from("hello"); s } // 返回所有权
2 | fn main() {
3 | println!("{}", longest_owned());
4 | let r; let x = 5; r = &x; // x 活得比 r 久
5 | println!("r = {r}");
6 | let mut v = vec![1, 2, 3];
7 | let first = v[0]; // 拷贝出值,不再持有引用
8 | v.push(4);
9 | println!("first = {first},v = {:?}", v);
10 | }
编译:成功
运行(退出码 0):
hello
r = 5
first = 1,v = [1, 2, 3, 4]