Rust 最独特的特性之一就是 所有权(Ownership),它让 Rust 在没有垃圾回收器(GC)的情况下仍能保证内存安全。理解所有权的工作原理非常重要。
1. 所有权的基本概念
所有权是一组规则,用来控制 Rust 如何管理内存。所有程序都需要管理运行时如何使用内存。常见的几种方式:
- 垃圾回收(GC):例如 Java、Go,自动跟踪和清理不再使用的内存。
- 手动管理:例如 C++,程序员必须显式分配和释放内存。
- 所有权机制:Rust 使用所有权系统,在编译期检查规则,不影响运行时性能。
所有权规则(简要)
- 每个值都有一个所有者(owner)。
- 对于不实现
Copy的类型(如String),同一时间只能有一个所有者(即移动语义)。 - 当所有者离开作用域时,值会被自动丢弃,内存释放。
注意:对于实现了 Copy trait 的简单类型(如整型、浮点、布尔、字符,以及不含堆数据的元组),赋值会复制 Copy,而不是 Move。
2. 变量的作用域
变量的生命周期与作用域相关:
1 | fn main() { |
3. String 类型
字符串字面量(如 "hello")被硬编码到程序中,不可变。如果需要可变或运行时获取的字符串,可以使用 String,它在堆上分配内存。
1 | fn main() { |
4. 内存与分配
String 的工作流程:
- 在调用
String::from或其它分配时,向堆请求内存。 - 当变量超出作用域时,Rust 自动调用
drop,释放该内存。
1 | fn main() { |
5. Move 与 Clone
Move(移动语义)
对于不实现 Copy 的类型,赋值或传参会发生 move:
1 | fn main() { |
这样设计是为了防止出现 二次释放(double free) 的安全问题(两个变量同时指向同一堆内存,且各自尝试释放)。
Clone(深拷贝)
如果需要真正复制堆上的数据,使用 clone():
1 | fn main() { |
Copy(栈上数据的自动复制)
实现了 Copy 的类型在赋值时会按位复制(不会发生 Move),例如:整数、浮点、布尔、字符,以及不包含堆数据的元组。
1 | fn main() { |
6. 函数传值与返回值
传参时的所有权行为
将值传给函数时也会发生 Move 或 Copy:
1 | fn main() { |
如果你在 takes_ownership(s); 之后再使用 s,会编译错误,因为 s 的所有权已被移动。
返回值也会移动所有权
1 | fn main() { |
注意: 如果不想在函数间频繁移动所有权,通常可以使用引用(借用)。
7. 引用与借用(References & Borrowing)
引用是指向数据的指针,但不拥有数据:
不可变引用
1 | fn main() { |
可变引用
默认引用是不可变的。若要修改被借用的数据,使用可变引用 &mut:
1 | fn main() { |
可变引用的限制:在任一给定时间,对某个值最多只能有一个可变引用。这避免了并发写入导致的数据竞争。数据竞争会被以下三种行为触发:
两个或多个指针同时访问同一块内存。
至少有一个指针正在写入数据。
访问没有任何同步机制保护。
错误示例:多个可变引用
1 | let mut s = String::from("hello"); |
通过作用域解决
1 | let mut s = String::from("hello"); |
不可变引用与可变引用不能同时存在:如果有不可变引用仍在使用,则不能创建可变引用;但如果不可变引用的作用域结束且不再使用,就可以随后创建可变引用。
示例如下:
可变引用与不可变引用冲突
1 | let mut s = String::from("hello"); |
合理利用作用域
1 | fn main() { |
8. 悬垂引用(Dangling References)
悬垂引用是指引用指向的值已被释放,但引用仍被使用。Rust 的所有权系统和借用规则在编译期防止了悬垂引用。
错误示例(编译失败):
1 | fn dangle() -> &String { |
正确做法:返回值的所有权(而不是引用),或确保引用指向的值在调用者作用域内保持有效:
1 | fn no_dangle() -> String { |
9. 切片(Slice)
切片是对集合(如字符串、数组)中连续元素的引用,不取得所有权。
字符串切片
1 | fn main() { |
简写与尾部省略:
1 | let s = String::from("hello"); |
数组切片
1 | let a = [1, 2, 3, 4, 5]; |
总结
- 所有权是 Rust 的核心机制,用以在编译期保证内存安全。
- 了解
Move、Clone、Copy的区别有助于写出高效且无内存错误的代码。 - 引用(借用)允许临时使用数据而不转移所有权,且借用规则在编译期防止数据竞争和悬垂引用。
- 切片提供对集合部分的安全引用,不会取得所有权。
Hooray!所有权学习完成!!!