引言
所有权(Ownership)是Rust最独特和最重要的特性,它使Rust能够在没有垃圾回收器的情况下保证内存安全。今天我们将深入学习Rust的所有权系统。
一、什么是所有权?
1.1 所有权规则
Rust的所有权系统有三条核心规则:
- Rust中的每个值都有一个被称为其**所有者(owner)**的变量
- 值在任何时刻只能有一个所有者
- 当所有者离开作用域时,值将被丢弃(dropped)
1.2 作用域
rust
fn main() {
{ // s不可用,尚未声明
let s = "hello"; // 从此处开始,s可用
println!("{}", s); // 使用s
} // s的作用域结束,s不再可用
// println!("{}", s); // ❌ 错误:s已离开作用域
}
二、String类型与所有权
2.1 String vs 字符串字面量
rust
fn main() {
// 字符串字面量:不可变,固定大小
let s1 = "hello";
// String类型:可变,大小可变
let mut s2 = String::from("hello");
s2.push_str(", world!");
println!("{}", s2);
}
2.2 内存分配
rust
fn main() {
let s = String::from("hello");
// s在堆上分配内存
// 当s离开作用域时,Rust自动调用drop函数释放内存
} // s在这里被drop
三、移动(Move)
3.1 基本类型的复制
rust
fn main() {
let x = 5;
let y = x; // 复制
println!("x = {}, y = {}", x, y); // ✅ 都可用
}
3.2 String的移动
rust
fn main() {
let s1 = String::from("hello");
let s2 = s1; // 移动!s1不再有效
// println!("{}", s1); // ❌ 错误:s1已被移动
println!("{}", s2); // ✅ s2可用
}
为什么?
- String由三部分组成:指针、长度、容量
- 赋值时只复制这三个值(栈上数据)
- 为避免二次释放,s1变为无效
3.3 移动语义
rust
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1的所有权移动到s2
// s1不再有效
// let s3 = s1; // ❌ 错误:s1已被移动
let s3 = s2; // s2的所有权移动到s3
println!("{}", s3);
}
四、克隆(Clone)
4.1 深拷贝
如果确实需要深拷贝堆上的数据:
rust
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝
println!("s1 = {}, s2 = {}", s1, s2); // ✅ 都可用
}
注意:clone操作可能很耗时,因为它会复制堆上的数据。
4.2 Copy trait
某些类型实现了Copy trait,赋值时会自动复制:
rust
fn main() {
// 这些类型都实现了Copy
let x = 5; // i32
let y = x;
let a = true; // bool
let b = a;
let c = 'z'; // char
let d = c;
let e = (1, 2); // 元组(所有元素都是Copy)
let f = e;
// 都可用
println!("x={}, y={}", x, y);
println!("a={}, b={}", a, b);
}
实现Copy的类型:
- 所有整数类型
- 布尔类型
- 所有浮点类型
- 字符类型
- 元组(仅当其所有字段都是Copy)
五、所有权与函数
5.1 传递参数
rust
fn main() {
let s = String::from("hello");
takes_ownership(s); // s的所有权移动到函数
// println!("{}", s); // ❌ 错误:s已被移动
let x = 5;
makes_copy(x); // x是Copy类型,会复制
println!("x = {}", x); // ✅ x仍然可用
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string在这里被drop
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
}
5.2 返回值
rust
fn main() {
let s1 = gives_ownership(); // 函数返回值的所有权转移给s1
println!("{}", s1);
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2); // s2移入,返回值移给s3
// println!("{}", s2); // ❌ s2已被移动
println!("{}", s3); // ✅ s3可用
}
fn gives_ownership() -> String {
let some_string = String::from("yours");
some_string // 返回,所有权移出
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // 返回,所有权移出
}
5.3 返回多个值
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) // 返回String和长度
}
六、实战示例
示例1:交换变量
rust
fn main() {
let s1 = String::from("hello");
let s2 = String::from("world");
println!("交换前: s1={}, s2={}", s1, s2);
let (s1, s2) = swap(s1, s2);
println!("交换后: s1={}, s2={}", s1, s2);
}
fn swap(a: String, b: String) -> (String, String) {
(b, a)
}
示例2:字符串处理
rust
fn main() {
let s = String::from("hello");
let s = add_exclamation(s);
println!("{}", s);
}
fn add_exclamation(mut s: String) -> String {
s.push('!');
s // 返回所有权
}
示例3:构建器模式
rust
struct Config {
host: String,
port: u16,
}
impl Config {
fn new() -> Self {
Config {
host: String::from("localhost"),
port: 8080,
}
}
fn host(mut self, host: String) -> Self {
self.host = host;
self // 返回self,允许链式调用
}
fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
}
fn main() {
let config = Config::new()
.host(String::from("127.0.0.1"))
.port(3000);
println!("{}:{}", config.host, config.port);
}
七、所有权的好处
7.1 内存安全
rust
fn main() {
let s = String::from("hello");
// Rust在编译时防止以下问题:
// 1. 空指针
// 2. 悬垂指针
// 3. 双重释放
// 4. 内存泄漏
}
7.2 无需垃圾回收
rust
fn main() {
let s = String::from("hello");
// 编译时就知道何时释放内存
// 无需运行时垃圾回收
// 性能可预测
}
八、常见模式
8.1 临时所有权转移
rust
fn main() {
let s = String::from("hello");
let len = calculate_length_ownership(s);
// ❌ s已不可用
// println!("{}", s);
}
fn calculate_length_ownership(s: String) -> usize {
s.len()
} // s被drop
8.2 使用引用(预告)
rust
fn main() {
let s = String::from("hello");
let len = calculate_length_ref(&s); // 传递引用
println!("字符串'{}'的长度是{}", s, len); // ✅ s仍可用
}
fn calculate_length_ref(s: &String) -> usize {
s.len()
} // s(引用)离开作用域,但不会drop原值
九、练习题
练习1:修复所有权错误
rust
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1移动到s2
// 方案1:使用s2
println!("{}", s2);
// 方案2:克隆s1
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{} {}", s1, s2);
// 方案3:使用引用(下一篇)
}
练习2:函数所有权
rust
fn main() {
let s = String::from("hello");
process(s);
// 如果后续还需要使用s:
// 方案1:返回所有权
let s = String::from("hello");
let s = process_and_return(s);
println!("{}", s);
// 方案2:使用引用(下一篇)
}
fn process(s: String) {
println!("{}", s);
}
fn process_and_return(s: String) -> String {
println!("{}", s);
s
}
练习3:字符串拼接
rust
fn main() {
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
// 方式1:s1会被移动
let s3 = s1 + &s2;
println!("{}", s3);
// println!("{}", s1); // ❌ s1已移动
println!("{}", s2); // ✅ s2可用
// 方式2:使用format!(不移动)
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = format!("{}{}", s1, s2);
println!("{} {} {}", s1, s2, s3); // 都可用
}
十、所有权流程图
创建值
↓
值拥有所有者
↓
┌───────────────┐
│ 所有权操作? │
└───────────────┘
↓ ↓
移动 克隆
↓ ↓
新所有者 两个所有者
↓ ↓
离开作用域 各自离开作用域
↓ ↓
值被drop 各自被drop
十一、与其他语言对比
C++
cpp
// C++:需要手动管理内存
std::string* s = new std::string("hello");
delete s; // 必须手动释放
Java
java
// Java:垃圾回收器自动管理
String s = new String("hello");
// 不确定何时回收,有性能开销
Rust
rust
// Rust:编译时确定,零开销
let s = String::from("hello");
// 离开作用域自动释放,无运行时开销
十二、常见错误
错误1:使用已移动的值
rust
fn main() {
let s1 = String::from("hello");
let s2 = s1;
// ❌ 错误
// println!("{}", s1);
// ✅ 正确
println!("{}", s2);
}
错误2:返回局部变量的引用
rust
// ❌ 错误:返回悬垂引用
// fn dangle() -> &String {
// let s = String::from("hello");
// &s
// } // s被drop,返回的引用无效
// ✅ 正确:返回所有权
fn no_dangle() -> String {
let s = String::from("hello");
s
}
总结
本文学习了:
✅ 所有权的三条规则
✅ 移动(Move)语义
✅ 克隆(Clone)操作
✅ Copy trait
✅ 所有权与函数
核心要点:
- 每个值有且仅有一个所有者
- 赋值或传参会移动所有权
- Copy类型会复制,不移动
- 离开作用域时自动释放内存