learning-rust/part3/所有权关键概念梳理.md

263 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 只是借用了 xx 仍然是所有者
println!("{}", x); // ✅ x 仍然可用
println!("{}", y); // ✅ y 可以通过引用读取
```
**原答误以为** `let y = &x` 会转移所有权 → **完全错误**。与下面对比:
```rust
let x = String::from("hello");
let y = x; // ← 没有 &,这是 movex 失效
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. NLLNon-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` 更通用) |