feat(part3): 添加Rust所有权概念详解文档和练习题

添加了完整的Rust所有权系统学习材料,包括:
- 详细的所有权概念解释文档,涵盖移动、借用、切片等核心概念
- 包含基础题、填空题、编程题等多种类型的练习题
- 创建Cargo项目框架和示例代码
- 涵盖所有权规则、引用与借用、可变引用等关键知识点
This commit is contained in:
Yuhang Wu 2026-07-08 11:34:01 +08:00
parent 8a87daba0b
commit 73e25a8e12
4 changed files with 1004 additions and 0 deletions

6
part3/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "part3"
version = "0.1.0"
edition = "2024"
[dependencies]

3
part3/src/main.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

440
part3/所有权概念.md Normal file
View File

@ -0,0 +1,440 @@
# Rust 所有权Ownership概念详解
> 所有权是 Rust 最独特的特性,它让 Rust 无需垃圾回收器就能保证内存安全。
## 目录
- [1. 什么是所有权](#1-什么是所有权)
- [2. 所有权规则](#2-所有权规则)
- [3. 变量作用域](#3-变量作用域)
- [4. String 类型与内存分配](#4-string-类型与内存分配)
- [5. 移动Move](#5-移动move)
- [6. 克隆Clone](#6-克隆clone)
- [7. Copy 特征](#7-copy-特征)
- [8. 所有权与函数](#8-所有权与函数)
- [9. 返回值与作用域](#9-返回值与作用域)
- [10. 引用与借用](#10-引用与借用)
- [11. 可变引用](#11-可变引用)
- [12. 悬垂引用](#12-悬垂引用)
- [13. 切片类型](#13-切片类型)
- [14. 总结](#14-总结)
---
## 1. 什么是所有权
所有权是一套用于管理内存的规则。在大部分编程语言中,内存管理通过以下方式处理:
- **手动管理**(如 C/C++):程序员显式分配和释放内存
- **垃圾回收**(如 Java/Go运行时自动回收不再使用的内存
- **所有权系统**Rust通过编译时检查的一套规则管理内存
### 为什么需要所有权?
- 无需垃圾回收,性能更高
- 无需手动管理内存,更安全
- 在编译期消除内存错误
---
## 2. 所有权规则
三条核心规则:
1. Rust 中的**每一个值**都有一个**所有者**owner
2. 值在任一时刻**有且仅有一个**所有者
3. 当所有者离开作用域,值会被**自动释放**(调用 `drop`
---
## 3. 变量作用域
```rust
fn main() {
// s 在这里无效,尚未声明
let s = "hello"; // s 从此处开始有效
// 可以使用 s
println!("{}", s);
} // 此作用域结束s 不再有效
```
---
## 4. String 类型与内存分配
字符串字面量 `"hello"` 是不可变的,编译时已知,存储在二进制文件中。对于在运行时可变的字符串,使用 `String` 类型。
```rust
fn main() {
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() 追加字符串
println!("{}", s); // 输出: hello, world!
}
```
### 内存分配与释放
`String` 类型的数据存储在**堆**上:
- 栈:存储指针、长度、容量(固定大小,编译时已知)
- 堆:存储实际的字符串内容(编译时大小未知,运行时动态分配)
```text
栈 堆
┌──────────┐ ┌──────────┐
│ ptr │────────>│ h e l l o│
│ len: 5 │ │ w o r l d│
│ cap: 5 │ └──────────┘
└──────────┘
```
当变量离开作用域时Rust 自动调用 `drop` 函数,将堆内存归还给操作系统。
---
## 5. 移动Move
```rust
let x = 5;
let y = x; // x 的值被复制到 y栈上数据Copy 类型)
println!("{}", x); // ✅ 正常工作x 仍可用
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权**移动**到了 s2
// println!("{}", s1); // ❌ 编译错误s1 已失效
println!("{}", s2); // ✅ 正常工作
```
### 为什么需要移动?
如果 Rust 对堆数据也做浅拷贝,当 `s1``s2` 同时离开作用域时,它们都会尝试释放同一块堆内存,导致**二次释放**错误。
Rust 通过**移动**解决了这个问题:赋值后旧变量失效,只有一个所有者负责释放内存。
```text
移动前: 移动后:
s1 → [堆内存] s2 → [堆内存]
s1 已失效 ❌
```
---
## 6. 克隆Clone
如果确实需要深度复制堆数据,使用 `clone` 方法:
```rust
let s1 = String::from("hello");
let s2 = s1.clone(); // 在堆上创建一份完整副本
println!("s1 = {}, s2 = {}", s1, s2); // ✅ 两者都可用
```
```text
克隆后:
s1 → [堆内存1: "hello"]
s2 → [堆内存2: "hello"]
```
- `clone` 会执行**深拷贝**,包括堆数据
- 代价较高,仅在确实需要时使用
---
## 7. Copy 特征
对于**完全存储在栈上**的数据类型Rust 提供了 `Copy` 特征:
```rust
let x = 5;
let y = x; // x 被复制,而非移动
println!("{}", x); // ✅ 仍可使用
```
### 具有 Copy 的类型
- 所有整数类型:`u32`、`i64` 等
- 所有浮点类型:`f32`、`f64`
- 布尔类型:`bool`
- 字符类型:`char`
- 元组(仅当所有元素都实现了 Copy`(i32, bool)` ✅,`(i32, String)` ❌
### 不具有 Copy 的类型
- `String`
- `Vec<T>`
- 任何实现了 `Drop` trait 的类型
---
## 8. 所有权与函数
将值传递给函数时,会发生**移动**或**复制**,规则与赋值相同:
```rust
fn main() {
let s = String::from("hello");
takes_ownership(s); // s 的所有权移动到函数内
// println!("{}", s); // ❌ 编译错误s 已失效
let x = 5;
makes_copy(x); // x 被复制到函数内i32 是 Copy 类型)
println!("{}", x); // ✅ x 仍然可用
} // x 离开作用域,然后 s 离开作用域(但 s 已被移动,不会发生什么)
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string 离开作用域drop 被调用,内存释放
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // some_integer 离开作用域,无特殊操作(栈数据)
```
---
## 9. 返回值与作用域
函数的返回值也可以**转移所有权**
```rust
fn main() {
let s1 = gives_ownership(); // 返回值将所有权移给 s1
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2); // s2 移入函数,函数返回后又移给 s3
// println!("{}", s2); // ❌ s2 已失效
println!("s1 = {}, s3 = {}", s1, s3);
} // s1、s3 离开作用域并被释放s2 已被移动,不触发释放
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string // 返回 some_string所有权移出
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // 返回 a_string所有权移出
}
```
### 元组返回多个值
```rust
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("'{}' 的长度是 {}", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length) // 返回字符串和它的长度
}
```
这种写法有些繁琐Rust 提供了**引用**来简化。
---
## 10. 引用与借用
**引用**允许在不获取所有权的情况下使用值。
```rust
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 传递引用,不转移所有权
println!("'{}' 的长度是 {}", s1, len); // ✅ s1 仍可使用
}
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
s.len()
} // s 离开作用域,但由于它不拥有值的所有权,不会调用 drop
```
```text
s1 → [堆内存: "hello"]
&s1引用指向 s1 的值)
```
### 引用的规则
1. 引用**不获取**所有权
2. 创建引用的行为称为**借用**borrowing
3. 借用者离开作用域时,不会释放底层数据
---
## 11. 可变引用
默认情况下,引用是**不可变的**。要修改借用的值,需要使用**可变引用**
```rust
fn main() {
let mut s = String::from("hello");
change(&mut s); // 传递可变引用
println!("{}", s); // 输出hello, world!
}
fn change(some_string: &mut String) {
some_string.push_str(", world!");
}
```
### 可变引用的限制
**在任意给定时间,只能拥有以下两者之一:**
- 一个可变引用
- 任意数量的不可变引用
```rust
let mut s = String::from("hello");
// ✅ 不可变引用可以同时存在多个
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// ✅ 不可变引用不再使用后,可以创建可变引用
let r3 = &mut s;
println!("{}", r3);
```
```rust
let mut s = String::from("hello");
let r1 = &s; // 不可变借用
let r2 = &s; // 不可变借用
let r3 = &mut s; // ❌ 编译错误!不能同时存在不可变和可变引用
println!("{}, {}, {}", r1, r2, r3);
```
```rust
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // ❌ 编译错误!不能同时存在两个可变引用
println!("{}, {}", r1, r2);
```
**这样设计的好处:在编译时防止数据竞争!**
> 数据竞争发生的三个条件:
> 1. 两个或多个指针同时访问同一数据
> 2. 至少有一个指针用于写入
> 3. 没有同步数据访问的机制
Rust 的规则直接在编译期消灭了数据竞争。
---
## 12. 悬垂引用
**悬垂引用**是指向已被释放内存的指针。Rust 编译器保证永远不会产生悬垂引用:
```rust
fn main() {
let reference_to_nothing = dangle(); // ❌ 编译错误!
}
fn dangle() -> &String {
let s = String::from("hello");
&s // 返回 s 的引用
} // s 离开作用域,内存被释放;但返回的引用仍指向已释放的内存
```
编译器错误信息:
```
error[E0106]: missing lifetime specifier
|
| fn dangle() -> &String {
| ^ expected named lifetime parameter
```
正确的做法是直接返回 `String`,将所有权移出去:
```rust
fn no_dangle() -> String {
let s = String::from("hello");
s // 所有权移出
}
```
---
## 13. 切片类型
**切片**是一种不获取所有权的引用,允许引用集合中一段连续的元素。
### 字符串切片
```rust
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
```
语法简写:
```rust
let s = String::from("hello world");
let hello = &s[..5]; // 从 0 开始
let world = &s[6..]; // 到末尾
let whole = &s[..]; // 整个字符串
```
### 字符串字面量就是切片
```rust
let s = "Hello, world!"; // s 的类型是 &str即字符串切片
```
### 其他切片
```rust
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // 类型是 &[i32],包含 [2, 3]
assert_eq!(slice, &[2, 3]);
```
---
## 14. 总结
| 概念 | 说明 |
|------|------|
| **所有权** | 每个值有且仅有一个所有者;所有者离开作用域时值被释放 |
| **移动** | 赋值/传参时转移所有权,原变量失效 |
| **克隆** | 深拷贝堆数据,两个变量独立 |
| **Copy** | 栈上数据的自动复制(整数、布尔等基础类型) |
| **引用** | 不获取所有权的指针,允许多个存在 |
| **可变引用** | 可修改数据的引用,同时只能有一个 |
| **切片** | 对集合局部元素的引用 |
| **借用规则** | 任意时刻:要么一个可变引用,要么多个不可变引用 |
### 借用检查器保证了
- **内存安全**:无悬垂指针、无二次释放、无使用后释放
- **线程安全**:无数据竞争
- **零运行时开销**:所有检查在编译时完成
> 所有权系统可能会让初学者感到"Rust 编译器在跟你作对",但请记住:**编译器不是在刁难你,而是在保护你写出的每一行代码都是内存安全的。** 当你熟悉所有权之后,这些规则会变得自然且合理。

555
part3/所有权练习题.md Normal file
View File

@ -0,0 +1,555 @@
# Rust 所有权练习题
> 建议先手动写出每道题的答案,再运行代码验证。
## 一、基础题:判断正误
判断以下代码能否通过编译。如果可以,说明原因;如果不能,指出错误原因。
### 题目 1-1
```rust
fn main() {
let s = String::from("hello");
let t = s;
println!("{}", s);
}
```
### 题目 1-2
```rust
fn main() {
let x = 42;
let y = x;
println!("x = {}, y = {}", x, y);
}
```
### 题目 1-3
```rust
fn main() {
let s = String::from("rust");
let t = s.clone();
println!("s = {}, t = {}", s, t);
}
```
### 题目 1-4
```rust
fn main() {
let s1 = String::from("hello");
let s2 = &s1;
let s3 = &s1;
println!("{} {} {}", s1, s2, s3);
}
```
### 题目 1-5
```rust
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
```
### 题目 1-6
```rust
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s;
println!("{}, {}, {}", r1, r2, r3);
}
```
---
## 二、填空题:补充代码
补全下列代码使其能通过编译。
### 题目 2-1转移所有权
```rust
fn main() {
let s = String::from("rustacean");
takes_ownership(________); // 填空,使 s 的所有权进入函数
// println!("{}", s); // 此行若取消注释会报错
}
fn takes_ownership(s: String) {
println!("{}", s);
}
```
### 题目 2-2借用的生命周期
```rust
fn main() {
let s = String::from("hello");
let len = get_length(________); // 填空:传递引用取得长度
println!("'{}' 的长度是 {}", s, len);
}
fn get_length(s: &String) -> usize {
s.len()
}
```
### 题目 2-3可变引用
```rust
fn main() {
let ________ s = String::from("hello"); // 填空:声明可变变量
append_world(________); // 填空:传递可变引用
println!("{}", s); // 期望输出hello, world!
}
fn append_world(s: &mut String) {
s.push_str(", world!");
}
```
### 题目 2-4作用域技巧
```rust
fn main() {
let mut s = String::from("hello");
{ // 进入新作用域
let r1 = ________; // 填空:创建不可变引用
println!("{}", r1);
} // r1 离开作用域
let r2 = ________; // 填空:现在可以创建可变引用了
r2.push_str(" world");
println!("{}", r2);
}
```
---
## 三、找出并修复错误
以下每段代码都有编译错误,请指出错误并写出修正后的代码。
### 题目 3-1
```rust
fn main() {
let s1 = String::from("hello");
let s2 = s1;
print_both(s1, s2);
}
fn print_both(a: String, b: String) {
println!("{} and {}", a, b);
}
```
### 题目 3-2
```rust
fn main() {
let s = dangle();
println!("{}", s);
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
```
### 题目 3-3
```rust
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
r1.push_str(", world");
let r2 = &mut s;
r2.push_str("!");
println!("{}", r1);
println!("{}", r2);
}
```
### 题目 3-4
```rust
fn main() {
let s = String::from("hello world");
let first = &s[0..5];
s.clear(); // 清空字符串
println!("first = {}", first);
}
```
---
## 四、编程题
### 题目 4-1计算单词数量
编写一个函数 `count_words(s: &str) -> usize`,计算字符串中的单词数量(以空格分隔)。
```rust
fn count_words(s: &str) -> usize {
// 你的代码
}
fn main() {
let s = String::from("hello world rust");
let count = count_words(&s);
println!("'{}' 有 {} 个单词", s, count); // 期望输出3
// 验证 s 的所有权未被取走
println!("原始字符串仍然可用: {}", s);
}
```
### 题目 4-2获取首尾单词
编写函数 `first_and_last(s: &str) -> (&str, &str)`,返回字符串的首单词和尾单词。
```rust
fn first_and_last(s: &str) -> (&str, &str) {
// 你的代码
}
fn main() {
let s = String::from("Rust is a systems programming language");
let (first, last) = first_and_last(&s);
println!("首单词: '{}', 尾单词: '{}'", first, last);
// 期望输出:首单词: 'Rust', 尾单词: 'language'
println!("原字符串: {}", s);
}
```
### 题目 4-3字符串处理
编写一个函数,接收一个字符串,返回一个新的字符串,将其中的每个单词首字母大写,其余字母小写。
```rust
fn title_case(s: &str) -> String {
// 你的代码
// 提示:可以使用 split_whitespace、to_uppercase、to_lowercase、collect 等方法
}
fn main() {
let s = String::from("hello WORLD rust");
let result = title_case(&s);
println!("输入: {}", s);
println!("输出: {}", result);
// 期望输出Hello World Rust
// 验证 s 的所有权未被取走
println!("原字符串: {}", s);
}
```
### 题目 4-4参数所有权设计
请合理设计以下函数的参数类型(传值 / 传引用 / 传可变引用):
```rust
// fn capitalize(s: ???) // 1. 只读取,不修改,调用后原变量还要用
// fn consume_and_print(s: ???) // 2. 拿走所有权,调用后原变量不再使用
// fn append_exclamation(s: ???)// 3. 修改原字符串
fn capitalize(s: ________) -> String {
let mut result = s.clone();
if let Some(c) = result.get_mut(0..1) {
c.make_ascii_uppercase();
}
result
}
fn consume_and_print(s: ________) {
println!("消费了: {}", s);
}
fn append_exclamation(s: ________) {
s.push_str("!");
}
fn main() {
let s = String::from("hello");
let capitalized = capitalize(________); // 填空:调用 capitalize
println!("{}", s); // s 仍可用
consume_and_print(________); // 填空:调用 consume_and_print传入 s
// println!("{}", s); // 若取消注释会报错
let mut t = String::from("hello");
append_exclamation(________); // 填空:调用 append_exclamation
println!("{}", t); // 期望输出hello!
}
```
### 题目 4-5数组切片操作
编写函数实现以下功能:
```rust
// 返回数组前 n 个元素的和
fn sum_of_first_n(arr: &[i32], n: usize) -> i32 {
// 你的代码
// 提示:使用切片 &arr[..n]
}
// 判断一个切片是否包含目标值
fn contains(arr: &[i32], target: i32) -> bool {
// 你的代码
}
fn main() {
let numbers = [10, 20, 30, 40, 50];
println!("前 3 个元素的和: {}", sum_of_first_n(&numbers, 3)); // 期望60
println!("包含 30 吗? {}", contains(&numbers, 30)); // 期望true
println!("包含 99 吗? {}", contains(&numbers, 99)); // 期望false
}
```
---
## 五、综合思考题
### 题目 5-1
以下代码为什么不能通过编译?提出两种修复方法,并说明各自的优缺点。
```rust
fn main() {
let mut data = vec![1, 2, 3];
let first = &data[0];
data.push(4);
println!("first = {}", first);
}
```
### 题目 5-2
分析以下代码,回答:
1. 写出每一步所有权的状态变化
2. 最终输出是什么?
```rust
fn main() {
let mut s = String::from("rust");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// r1, r2 此后不再使用
let r3 = &mut s;
r3.push_str(" is great");
println!("{}", r3);
// r3 此后不再使用
let s2 = s; // 移动 s
// println!("{}", s); // 若取消注释会怎样?
println!("{}", s2);
}
```
### 题目 5-3
以下代码在 Rust 中是否合法?为什么?
```rust
fn main() {
let x = 5;
let y = &x;
let z = &x;
println!("{} {} {}", x, y, z);
}
```
如果将 `x` 的类型从 `i32` 改为 `String`,同样的结构是否仍然合法?
```rust
fn main() {
let x = String::from("hello");
let y = &x;
let z = &x;
println!("{} {} {}", x, y, z);
}
```
### 题目 5-4
Rust 的所有权系统与 C++ 的 RAII、Java 的垃圾回收各自有什么优缺点?请简要对比分析。
---
## 参考答案
> 请独立完成再查看答案。
<details>
<summary>点击展开答案</summary>
### 一、基础题
**1-1**:❌ 编译错误。`s` 的所有权已移动到 `t``s` 已失效。
**1-2**:✅ 通过编译。`i32` 实现了 `Copy`,赋值时自动复制。
**1-3**:✅ 通过编译。`clone` 创建了深拷贝,两个变量独立。
**1-4**:✅ 通过编译。不可变引用可以同时存在多个。
**1-5**:❌ 编译错误。同一作用域不能同时存在两个可变引用。
**1-6**:❌ 编译错误。已有不可变引用时不能创建可变引用。
### 二、填空题
**2-1**`takes_ownership(s);`
**2-2**`let len = get_length(&s);`
**2-3**
```rust
let mut s = String::from("hello");
append_world(&mut s);
```
**2-4**
```rust
let r1 = &s;
let r2 = &mut s;
```
### 三、修复错误
**3-1**`s1` 已被移动给 `s2`,不能再传给函数。修复:
```rust
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 克隆一份
print_both(s1, s2);
}
```
**3-2**:返回了局部变量的引用,生命周期不够。修复:
```rust
fn dangle() -> String { // 返回 String 而非 &String
let s = String::from("hello");
s // 所有权移出
}
```
**3-3**`r1` 和 `r2` 的可变借用时间重叠。修复:将两次借用限制在各自的作用域内,或使用 `Non-Lexical Lifetimes` 特性Rust 2018+ 已默认启用,但此处 `r1``r2` 创建后仍被使用)。修复方法:
```rust
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
r1.push_str(", world");
println!("{}", r1);
// r1 在 println! 后不再使用
let r2 = &mut s;
r2.push_str("!");
println!("{}", r2);
}
```
**3-4**`clear()` 需要一个可变引用,但 `first` 是一个不可变引用,违反了借用规则。修复:先使用 `first`,再 `clear`
```rust
fn main() {
let s = String::from("hello world");
let first = &s[0..5];
println!("first = {}", first); // 先使用
let mut s = s; // 重新绑定
s.clear();
}
```
### 四、编程题
**4-1**
```rust
fn count_words(s: &str) -> usize {
s.split_whitespace().count()
}
```
**4-2**
```rust
fn first_and_last(s: &str) -> (&str, &str) {
let words: Vec<&str> = s.split_whitespace().collect();
(words[0], words[words.len() - 1])
}
```
**4-3**
```rust
fn title_case(s: &str) -> String {
s.split_whitespace()
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(c) => c.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase(),
}
})
.collect::<Vec<String>>()
.join(" ")
}
```
**4-4**
```rust
fn capitalize(s: &str) -> String { /* ... */ }
fn consume_and_print(s: String) { /* ... */ }
fn append_exclamation(s: &mut String) { /* ... */ }
// 调用方式:
let capitalized = capitalize(&s); // 传引用
consume_and_print(s); // 传值,所有权移入
append_exclamation(&mut t); // 传可变引用
```
**4-5**
```rust
fn sum_of_first_n(arr: &[i32], n: usize) -> i32 {
arr[..n].iter().sum()
}
fn contains(arr: &[i32], target: i32) -> bool {
arr.contains(&target)
}
```
### 五、综合思考题
**5-1**`first` 是对 `data` 元素的不可变引用,`push` 需要一个可变引用,违反借用规则。
- 方法一:调整顺序,先 push 再取引用 —— 简单但有时逻辑不允许
- 方法二:使用索引 `let first = data[0]`(需要 Copy 类型)—— 避免了借用但只适用于 Copy 类型
**5-2**
- 步骤:创建 `s` → 两次不可变借用r1, r2→ 打印后 r1, r2 释放 → 一次可变借用r3→ 打印后 r3 释放 → 移动 `s``s2`
- 输出:`rust and rust` / `rust is great` / `rust is great`
- 取消注释会报错:`s` 已移动
**5-3**:两者都合法。创建引用不会转移 `x` 的所有权,无论 `x` 是什么类型。只要引用和原值不冲突即可(此处都是不可变引用)。
**5-4**(简要):
- **C++ RAII**:灵活、性能极高,但依赖程序员自觉,容易出现野指针、重复释放
- **Java GC**:使用方便,不会出现悬挂指针,但存在运行时开销、不可预测的停顿
- **Rust 所有权**:编译期零成本抽象,保证内存安全/线程安全,但学习曲线陡峭
</details>