Rust 入门教程(五):所有权与借用(核心机制)
Rust 最大的特色,不是宏,不是性能,而是它独有的 所有权系统(Ownership System)。
这个机制帮助我们在**没有垃圾回收器(GC)**的情况下,依然保证内存安全。
今天我们就来理解 Rust 的灵魂:
✅ 所有权规则
✅ 移动语义 vs 复制语义
✅ 引用与借用(&、&mut)
✅ 生命周期初探
1️⃣ 所有权机制详解
在 Rust 中,每一个值(value)都有一个所有者(owner),并且在任意时刻,值只能有一个所有者。
当所有者离开作用域时,值就会被自动释放(drop),这就是 Rust 内存安全的根基。
规则总结
- 每个值都有一个变量作为它的所有者
- 同一时间只能有一个所有者
- 当所有者离开作用域时,值会被释放
示例
fn main() {{let s = String::from("hello"); // s 拥有这个字符串println!("{}", s);} // s 离开作用域,内存被释放
}
对比 C/C++:你得手动
free()
或者调用析构函数。
对比 Java/Go:由垃圾回收器自动回收。
在 Rust:所有权 + 作用域规则自动帮你管理内存。
2️⃣ 移动语义 vs 复制语义
Rust 区分 移动(move) 和 复制(copy) 两种语义。
移动语义(Move)
当把一个变量赋值给另一个变量时,所有权会转移。
fn main() {let s1 = String::from("hello");let s2 = s1; // s1 的所有权移动到 s2// println!("{}", s1); // ❌ 错误:s1 已经失效println!("{}", s2);
}
为什么?因为
String
存在堆内存,直接复制指针会导致“双重释放”错误,所以 Rust 选择转移所有权。
复制语义(Copy)
对于存储在栈上的简单数据类型(整数、布尔、浮点数等),Rust 使用 复制语义。
fn main() {let x = 5;let y = x; // x 被复制,而不是转移println!("x = {}, y = {}", x, y); // ✅ x 依然有效
}
哪些类型会自动 Copy
?
- 基本数值类型(i32, u64, f64...)
- bool、char
- 元组(如果里面的元素都实现了 Copy)
3️⃣ 引用与借用(&、&mut)
有时我们并不想转移所有权,只是想临时使用某个值,这就需要 引用(reference)。
不可变引用(&)
fn main() {let s = String::from("hello");let r1 = &s;let r2 = &s;println!("r1 = {}, r2 = {}", r1, r2); // ✅ 可以有多个不可变引用
}
规则:
- 可以同时存在多个不可变引用
- 但不能和可变引用共存
可变引用(&mut)
fn main() {let mut s = String::from("hello");let r = &mut s;r.push_str(", world");println!("{}", r);
}
规则:
- 同一作用域中,只能有一个可变引用
- 避免数据竞争(data race)
错误示例:
fn main() {let mut s = String::from("hi");let r1 = &mut s;let r2 = &mut s; // ❌ 同时两个可变引用
}
4️⃣ 生命周期初探
生命周期(lifetime) 是 Rust 用来保证引用有效性的机制。
你可以理解为:编译器在帮你追踪引用的作用域。
示例
fn main() {let r;{let s = String::from("hello");r = &s; // ❌ s 在这里被释放,r 将变成悬垂引用}// println!("{}", r);
}
Rust 编译器会拒绝编译这段代码,因为它能推导出 r
的生命周期比 s
长,存在安全风险。
生命周期标注(了解)
在函数中,有时需要显式标注生命周期,告诉编译器不同引用之间的关系。
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {if s1.len() > s2.len() {s1} else {s2}
}
这里的 'a
是生命周期参数,表示 s1
和 s2
的生命周期至少要和返回值一样长。
📌 总结
今天我们学习了 Rust 的核心机制:
- 所有权:每个值有唯一所有者,作用域结束时释放
- 移动语义 vs 复制语义:堆数据转移,栈数据复制
- 引用与借用:
&
不可变借用,&mut
可变借用(不能混用) - 生命周期:编译器追踪引用的有效范围,避免悬垂引用