263 lines
7.6 KiB
Markdown
263 lines
7.6 KiB
Markdown
# Rust 所有权关键概念梳理
|
||
|
||
> 基于练习题作答中的误区整理,配合纠正说明。
|
||
|
||
---
|
||
|
||
## 1. 三条核心规则
|
||
|
||
| 规则 | 说明 |
|
||
|------|------|
|
||
| 每个值有且只有一个**所有者** | 变量绑定 = 所有权 |
|
||
| 同一时刻只能有**一个可变引用**,或**任意多个不可变引用** | 读写互斥 |
|
||
| 所有者离开作用域,值被 `drop` | RAII 式自动释放 |
|
||
|
||
---
|
||
|
||
## 2. Move(所有权转移)
|
||
|
||
```rust
|
||
let s1 = String::from("hello");
|
||
let s2 = s1; // s1 的所有权移动给 s2
|
||
// println!("{}", s1); // ❌ s1 已失效
|
||
```
|
||
|
||
**常见误区:** 认为 move 之后还能继续用原变量。堆数据(`String`、`Vec`、`Box` 等)的赋值/传参默认是 move,源变量立即失效。
|
||
|
||
---
|
||
|
||
## 3. Copy 类型(自动复制)
|
||
|
||
```rust
|
||
let x = 42;
|
||
let y = x; // i32 实现了 Copy,自动复制
|
||
println!("{}", x); // ✅ x 仍然有效
|
||
```
|
||
|
||
实现了 `Copy` 的类型(`i32`、`bool`、`f64`、`char`、`&T` 等)赋值时会自动复制,不发生 move。
|
||
**注意:** 包含堆数据的类型(`String`、`Vec` 等)**不实现** `Copy`。
|
||
|
||
---
|
||
|
||
## 4. Clone(显式深拷贝)
|
||
|
||
```rust
|
||
let s1 = String::from("hello");
|
||
let s2 = s1.clone(); // 显式深拷贝,两个变量各自独立
|
||
println!("{} {}", s1, s2); // ✅
|
||
```
|
||
|
||
与 Copy 的区别:clone 是显式的、可能有开销;Copy 是隐式的、按位复制、无开销。
|
||
|
||
---
|
||
|
||
## 5. 引用(Borrow)—— 不转移所有权 ⚠️ 关键
|
||
|
||
> **`&` 就是借用,从!不!转!移!所!有!权!**
|
||
|
||
```rust
|
||
let x = String::from("hello");
|
||
let y = &x; // y 只是借用了 x,x 仍然是所有者
|
||
println!("{}", x); // ✅ x 仍然可用
|
||
println!("{}", y); // ✅ y 可以通过引用读取
|
||
```
|
||
|
||
**原答误以为** `let y = &x` 会转移所有权 → **完全错误**。与下面对比:
|
||
|
||
```rust
|
||
let x = String::from("hello");
|
||
let y = x; // ← 没有 &,这是 move!x 失效
|
||
let z = &x; // ← 有 &,引用,x 仍在
|
||
```
|
||
|
||
| 写法 | 行为 |
|
||
|------|------|
|
||
| `let y = x;` | move,所有权转移 |
|
||
| `let y = &x;` | borrow,借用,x 不动 |
|
||
| `let y = x.clone();` | 深拷贝,各自独立 |
|
||
|
||
---
|
||
|
||
## 6. 不可变引用(`&T`)
|
||
|
||
```rust
|
||
let s = String::from("hello");
|
||
let r1 = &s;
|
||
let r2 = &s; // ✅ 允许多个不可变引用共存
|
||
println!("{} {} {}", s, r1, r2); // ✅ 全部可用
|
||
```
|
||
|
||
规则:**多个不可变引用可以同时存在**,原值也可以被读取。
|
||
|
||
---
|
||
|
||
## 7. 可变引用(`&mut T`)
|
||
|
||
```rust
|
||
let mut s = String::from("hello");
|
||
let r1 = &mut s; // 可变引用
|
||
r1.push_str(" world");
|
||
// let r2 = &mut s; // ❌ 同一作用域不能有两个可变引用
|
||
```
|
||
|
||
规则:**同一时刻只能有一个可变引用**。
|
||
|
||
---
|
||
|
||
## 8. 不可变引用与可变引用互斥 ⚠️ 1-6 错误点
|
||
|
||
```rust
|
||
let mut s = String::from("hello");
|
||
let r1 = &s; // 不可变借用
|
||
let r2 = &s; // 不可变借用
|
||
let r3 = &mut s; // ❌ 已有不可变借用时,不能创建可变借用
|
||
println!("{} {} {}", r1, r2, r3);
|
||
```
|
||
|
||
**原答认为能编译 → 不能。** 不可变和可变引用不能共存于重叠的作用域中。
|
||
|
||
---
|
||
|
||
## 9. NLL(Non-Lexical Lifetimes)—— 3-3 的修复原理
|
||
|
||
Rust 2018+ 引入 NLL:**引用的生命周期结束于它最后一次被使用的语句,而非作用域末尾。**
|
||
|
||
```rust
|
||
let mut s = String::from("hello");
|
||
|
||
let r1 = &mut s;
|
||
r1.push_str(", world");
|
||
println!("{}", r1); // ← r1 最后一次使用,NLL 在此结束其借用
|
||
|
||
let r2 = &mut s; // ✅ r1 已失效,可以创建新的可变引用
|
||
r2.push_str("!");
|
||
println!("{}", r2);
|
||
```
|
||
|
||
同一段代码在 `let r1 = &mut s;` 和 `let r2 = &mut s;` 之间如果没有 NLL,按词法作用域会冲突。NLL 使借用更短、更精确。
|
||
|
||
**但 NLL 不破坏规则**:如果 r1 在 r2 创建后仍然被使用(比如 println 在最后),编译仍然失败。
|
||
|
||
---
|
||
|
||
## 10. 悬垂引用(Dangling Reference)—— 3-2 真正原因
|
||
|
||
```rust
|
||
fn dangle() -> &String { // ❌ 返回引用
|
||
let s = String::from("hello");
|
||
&s // s 在函数结束时被 drop,引用悬垂
|
||
}
|
||
```
|
||
|
||
**原答写"返回值被移动了" → 不准确。** 真正原因:返回的引用指向一个即将被释放的局部变量。Rust 编译期拒绝这种代码。
|
||
|
||
修复:
|
||
|
||
```rust
|
||
fn dangle() -> String { // ✅ 返回所有权
|
||
let s = String::from("hello");
|
||
s
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 切片与借用冲突 —— 3-4
|
||
|
||
```rust
|
||
let s = String::from("hello world");
|
||
let first = &s[0..5]; // first 是切片引用(不可变借用)
|
||
s.clear(); // ❌ clear() 需要 &mut self,与 first 冲突
|
||
println!("{}", first);
|
||
```
|
||
|
||
**核心冲突:不可变引用(切片)和可变引用(clear)不能共存。** 修复方法:
|
||
|
||
```rust
|
||
let s = String::from("hello world");
|
||
let first = &s[0..5];
|
||
println!("first = {}", first); // 先使用 first
|
||
let mut s = s; // 重新绑定,first 的借用已结束
|
||
s.clear();
|
||
```
|
||
|
||
---
|
||
|
||
## 12. `str` vs `String` —— 4-4 错误点
|
||
|
||
| 类型 | 说明 |
|
||
|------|------|
|
||
| `String` | 堆上分配、可增长的字符串。有所有权。`Sized`。 |
|
||
| `&str` | 字符串切片引用。借用。`Sized`(引用本身是指针+长度)。 |
|
||
| `str` | **动态大小类型(DST)**,编译期大小未知。**不能直接作为参数/变量类型。** |
|
||
|
||
```rust
|
||
fn consume_and_print(s: str) { } // ❌ str 是 DST
|
||
fn consume_and_print(s: String) { } // ✅ 拿走所有权
|
||
fn consume_and_print(s: &str) { } // ✅ 借用(推荐用于只读场景)
|
||
|
||
fn append_exclamation(s: &mut str) { } // ❌ str 是 DST
|
||
fn append_exclamation(s: &mut String) {} // ✅ 可变引用
|
||
```
|
||
|
||
---
|
||
|
||
## 13. `&str` 是借用,不会转移所有权 —— 5-3 核心纠正
|
||
|
||
```rust
|
||
let x = String::from("hello");
|
||
let y = &x; // 不可变引用,x 仍然拥有 String
|
||
let z = &x; // 另一个不可变引用
|
||
println!("{} {} {}", x, y, z); // ✅ 全部合法
|
||
```
|
||
|
||
**原答说"所有权被转移给 y 和 z" → 彻底错误。**
|
||
|
||
所有权转移只有两种方式:
|
||
1. **赋值没有 `&`**:`let y = x;`
|
||
2. **传参没有 `&`**:`fn foo(s: String)` + `foo(x);`
|
||
|
||
有 `&` 就是借用,不是转移。
|
||
|
||
---
|
||
|
||
## 14. 借用后修改的影响 —— 5-2 输出纠正
|
||
|
||
```rust
|
||
let mut s = String::from("rust");
|
||
let r3 = &mut s;
|
||
r3.push_str(" is great"); // ⚠️ 通过可变引用修改了 s 的内容
|
||
println!("{}", r3); // "rust is great"
|
||
// r3 不再使用
|
||
|
||
let s2 = s; // s 现在是 "rust is great",移动给 s2
|
||
println!("{}", s2); // "rust is great"
|
||
```
|
||
|
||
**原答认为最后输出是 "rust" → 忽视了 r3 对 s 的修改。**
|
||
|
||
---
|
||
|
||
## 15. 常见错误速查
|
||
|
||
| 错误类型 | 示例 | 编译器信息 |
|
||
|----------|------|-----------|
|
||
| use after move | `let y = x; println!("{}", x);` | `E0382: use of moved value` |
|
||
| 两个可变引用 | `let r1 = &mut s; let r2 = &mut s;` | `E0499: cannot borrow as mutable more than once` |
|
||
| 不可变+可变冲突 | `let r = &s; let rm = &mut s;` | `E0502: cannot borrow as mutable, immutable borrow exists` |
|
||
| 悬垂引用 | `fn f() -> &String { &s }` | `E0106: missing lifetime specifier` / `E0515` |
|
||
| 返回局部引用 | `fn f() -> &String { &s }` where s is local | 生命周期错误 |
|
||
| DST 作参数 | `fn f(s: str) { }` | `E0277: the size is not known at compile time` |
|
||
|
||
---
|
||
|
||
## 16. 决策总结
|
||
|
||
| 需求 | 签名 |
|
||
|------|------|
|
||
| 只读取,原值还要用 | `fn f(s: &str)` 或 `fn f(s: &String)` |
|
||
| 只读取,可能存下来 | `fn f(s: &str) -> String` 返回新值 |
|
||
| 修改原值 | `fn f(s: &mut String)` |
|
||
| 拿走所有权 | `fn f(s: String)` |
|
||
| 函数只读 | 优先用 `&str`(比 `&String` 更通用) |
|