现在,请深吸一口气,坐直身体。我们要进入 第三章:所有权 (Ownership) 与借用 (Borrowing)。
⚠️ 警告:这是 Rust 学习曲线中最陡峭的部分。
- 在 C/C++ 中,你需要手动管理内存(malloc/free),容易忘释放(内存泄漏)或释放两次(崩溃)。
- 在 Java/Python/Go 中,有垃圾回收器(GC)帮你自动回收,但会消耗性能,导致程序偶尔卡顿。
- Rust 选择了第三条路:通过一套编译时的规则(所有权)来管理内存。编译器非常严格,如果你违反规则,代码根本无法编译通过。
这一章一旦顿悟,你就算正式“入门” Rust 了!
请新建项目:
cargo new ownership_test
cd ownership_test
🚀 第三章:所有权 (Ownership)
3.1 栈 (Stack) 与 堆 (Heap) —— 前置知识
为了理解 Rust 为什么这么做,你需要简单了解内存:
- 栈 (Stack):像一摞盘子。存取极快,但大小必须固定。比如
i32、char、[i32; 5]都存在栈上。 - 堆 (Heap):像一个杂乱的大仓库。如果你需要存一段文字(String),因为不知道用户会输入多长,所以必须存在堆上。你把数据扔进仓库,管理员给你一张小纸条(指针),上面写着数据存放的位置。你把这张小纸条放在栈上。
3.2 所有权的三大铁律
请务必死记硬背这三条规则:
- Rust 中的每一个值都有一个 所有者 (Owner)(变量)。
- 同一时间,该值只能有一个所有者。
- 当所有者离开作用域(Scope)时,这个值将被丢弃(Drop,即释放内存)。
3.3 变量作用域 (Scope)
这和其他语言类似,用花括号 {} 界定。
fn main() {
{ // --- s 在这里开始有效
let s = "hello"; // s 是 valid 的
// 使用 s ...
} // --- 作用域结束,s 不再有效
// 这里的 s 已经被清理了
}
3.4 移动 (Move) —— Rust 最独特的行为
这是新手最容易懵的地方。
情况 A:栈上的数据(拷贝)
fn main() {
let x = 5;
let y = x; // 把 5 拷贝一份给 y
println!("x = {}, y = {}", x, y);
}
这很正常,x 和 y 都等于 5。因为整数大小固定,都在栈上,拷贝极快。Rust 称这些类型实现了 Copy 特征。
情况 B:堆上的数据(移动)
我们要引入 String 类型。不同于字符串字面量("hello",它是写死在代码里的),String 是动态的,分配在堆上。
请务必运行这段代码,并观察报错:
fn main() {
let s1 = String::from("hello");
let s2 = s1; // <--- 关键步骤!
// 下面这行会报错!
println!("s1 = {}", s1);
}
为什么报错?
s1在堆上申请了内存存 "hello",并在栈上存了一个指针指向它。let s2 = s1;发生时,Rust 并没有 把堆上的 "hello" 复制一份(深拷贝太慢了)。- 它只是把
s1栈上的指针复制给了s2。 - 关键点:为了保证内存安全(避免二次释放),Rust 认为
s1已经失效了! 这个操作叫 移动 (Move)。 - 就像现实生活中,我把手里的苹果给了你,我就没有苹果了。
修正:克隆 (Clone)
如果你真的想完整复制一份堆上的数据(即使这比较消耗性能),必须显式调用 .clone():
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝:堆上的数据也被复制了
println!("s1 = {}, s2 = {}", s1, s2); // 正常运行
}
3.5 所有权与函数
把变量传给函数,也遵循同样的规则:传进去,就没了。
fn main() {
let s = String::from("hello"); // s 进入作用域
takes_ownership(s); // s 的值被【移动】进了函数
// 从这里开始,s 不再有效了!
// println!("{}", s); // ❌ 如果取消注释,会报错
let x = 5; // x 进入作用域
makes_copy(x); // x 是 i32,实现了 Copy 特征
// 所以 x 只是把值传进去了,后面还能用
println!("x 依然可用: {}", x);
} // main 结束
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string 离开作用域,drop 被调用,内存释放
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
}
痛苦点:
如果我想在调用 takes_ownership 之后继续使用 s 怎么办?难道必须让函数把 s 再 return 回来吗?
// 笨办法:把所有权拿进去,再拿出来
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1); // s1 移动进去了,s2 移动出来了
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length) // 返回 s 和长度
}
这样做太麻烦了!于是,Rust 引入了 “引用” (Reference)。
🔗 第 3.5 章:引用与借用 (References and Borrowing)
这就是“借用”:我可以使用你的东西,但不获取它的所有权。
1. 不可变引用 (&)
我们在参数类型前面加 &,传参时也加 &。
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 传引用,不传所有权
// s1 依然有效!因为我们只是把它“借”出去了
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
s.len()
} // s 离开作用域,但因为它没有所有权,什么也不会发生(不会 drop String)
尝试修改借用的值?
fn change(some_string: &String) {
some_string.push_str(", world"); // ❌ 报错!
}
规则:借来的书(不可变引用),你是不能在上面乱涂乱画的。
2. 可变引用 (&mut)
如果你想修改,必须使用 可变引用。
fn main() {
let mut s = String::from("hello"); // 变量本身必须是 mut
change(&mut s); // 传入可变引用
println!("{}", s); // 输出 hello, world
}
fn change(some_string: &mut String) { // 接收可变引用
some_string.push_str(", world");
}
3. 借用的终极规则 (重点!)
Rust 为了防止多线程数据竞争,立下了严格的规矩:
在同一时间,对于同一个数据,你只能拥有以下二者之一:
- A. 任意数量的不可变引用 (
&T) (大家都可以看书,没人能改) - B. 唯一的一个可变引用 (
&mut T) (只有一个人能拿回家改,别人都不能看了)
代码实验:违反规则
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
// let r3 = &mut s; // ❌ 报错!已经有 r1, r2 在借阅了,不能再借给 r3 去修改
println!("{}, {}", r1, r2);
}
但是,注意作用域。如果 r1 和 r2 用完了(也就是最后一次使用已经在代码上方了),编译器很聪明,知道后面没人看了,就可以创建可变引用了(这叫 NLL - Non-Lexical Lifetimes)。
📝 第三章总结与作业
这一章非常“烧脑”,但请不要放弃。
总结:
- 所有权:每个值有一个所有者,变量赋值给别人,自己就废了(针对堆数据)。
- Move:
String赋值是 Move,i32赋值是 Copy。 - 引用 (
&):允许你不获取所有权也能访问数据。 - 借用规则:要么有很多读者(不可变),要么有一个作者(可变),不能同时存在。
作业:
- 创建一个函数
add_suffix,接收一个&mut String,在字符串后面追加 " - Rust is cool!"。 - 在
main函数中,创建一个String,调用这个函数,然后打印修改后的字符串。 - 思考题(不用写代码):为什么 Rust 不允许同时存在
&s(不可变) 和&mut s(可变)?如果允许,当我在读取&s的时候,另一个人通过&mut s吧数据清空了,会发生什么?(这就是 Rust 防止的“数据竞争”)。