Rust 所有权、借用和生命周期完全指南
Rust 最独特和最强大的特性:所有权系统。通过编译期检查实现内存安全,无需垃圾回收。
目录
所有权(Ownership)
为什么需要所有权?
在传统编程语言中:
- 手动管理内存(C/C++):程序员负责 malloc/free,容易导致内存泄漏、悬垂指针
- 垃圾回收(Java/Go):运行时自动回收,但有性能开销和不确定的暂停时间
- Rust 的方案:编译期所有权检查,零运行时开销 + 内存安全
所有权三大规则
// 规则 1:每个值都有一个所有者(owner)
// 规则 2:同一时间只能有一个所有者
// 规则 3:当所有者离开作用域,值被丢弃(dropped)
1. 基本概念:移动(Move)
fn main() {
// s1 是 String 的所有者
let s1 = String::from("hello");
// 所有权从 s1 移动到 s2
let s2 = s1;
// ❌ 错误:s1 的值已经被移动,不能再使用
// println!("{}", s1); // 编译错误:value borrowed here after move
// ✅ 正确:s2 现在是所有者
println!("{}", s2); // 输出:hello
}
为什么会移动?
// String 在堆上分配内存
// 栈上存储:指针、长度、容量
// 堆上存储:实际字符串数据
let s1 = String::from("hello");
// 栈:s1 -> 指针 -> 堆:"hello"
let s2 = s1;
// 栈:s2 -> 指针 -> 堆:"hello"
// 栈:s1 (无效)
// 如果允许 s1 和 s2 同时有效,当它们离开作用域时
// 会尝试释放同一块内存两次(double free)
// Rust 通过移动语义避免这个问题
2. 复制(Copy)类型
对于存储在栈上的简单类型,Rust 会自动复制而不是移动:
fn main() {
// i32 实现了 Copy trait
let x = 5;
let y = x;
// ✅ x 和 y 都可以使用
println!("x = {}, y = {}", x, y); // 输出:x = 5, y = 5
}
实现 Copy 的类型:
// 所有整数类型:i8, i16, i32, i64, i128, u8, u16, u32, u64, u128
// 浮点类型:f32, f64
// 布尔类型:bool
// 字符类型:char
// 元组(如果所有元素都是 Copy):(i32, i32)
// 数组(如果元素是 Copy):[i32; 5]
fn example() {
let tuple = (1, 2, 3);
let tuple2 = tuple; // 复制,不是移动
println!("{:?}", tuple); // ✅ 仍然可用
let array = [1, 2, 3];
let array2 = array; // 复制,不是移动
println!("{:?}", array); // ✅ 仍然可用
}
3. 所有权与函数
fn main() {
let s = String::from("hello");
// s 的所有权移动到函数中
takes_ownership(s);
// ❌ 错误:s 的值已经被移动
// println!("{}", s);
let x = 5;
// x 是 Copy 类型,传递副本
makes_copy(x);
// ✅ 正确:x 仍然可用
println!("{}", x);
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string 离开作用域,调用 drop,释放内存
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // some_integer 离开作用域,没有特殊操作
4. 返回值与所有权
fn main() {
let s1 = gives_ownership(); // 函数返回值的所有权移动给 s1
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 移动到函数,返回值移动给 s3
// ❌ s2 不可用
// ✅ s1 和 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. 克隆(Clone)
如果确实需要深拷贝堆上的数据:
fn main() {
let s1 = String::from("hello");
// 显式克隆
let s2 = s1.clone();
// ✅ 两者都可用
println!("s1 = {}, s2 = {}", s1, s2);
}
// 克隆的成本
fn expensive_clone() {
let large_vec = vec![1; 1_000_000]; // 100 万个元素
// ⚠️ 这会复制 100 万个元素
let cloned = large_vec.clone();
// 建议:尽量使用引用而不是克隆
}
借用(Borrowing)
为什么需要借用?
问题:每次传递值都移动所有权很麻烦
fn main() {
let s = String::from("hello");
let len = calculate_length(s);
// ❌ s 已被移动,不能再使用
// println!("The length of '{}' is {}", s, len);
}
fn calculate_length(s: String) -> usize {
s.len()
} // s 离开作用域并被释放
解决方案:借用(创建引用,不获取所有权)
1. 不可变借用(Immutable Borrowing)
fn main() {
let s = String::from("hello");
// 创建不可变引用(借用)
let len = calculate_length(&s);
// ✅ s 仍然可用
println!("The length of '{}' is {}", s, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
// s 是引用,离开作用域不会释放数据
}
引用规则:
fn main() {
let s = String::from("hello");
// 可以有多个不可变引用
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{}, {}, {}", r1, r2, r3); // ✅ 正确
}
2. 可变借用(Mutable Borrowing)
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");
}
3. 借用规则(重要!)
// 规则:在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用
fn main() {
let mut s = String::from("hello");
// ❌ 错误示例 1:同时有多个可变引用
let r1 = &mut s;
let r2 = &mut s; // ❌ 编译错误:cannot borrow `s` as mutable more than once
// println!("{}, {}", r1, r2);
// ❌ 错误示例 2:同时有可变引用和不可变引用
let r1 = &s; // 不可变引用
let r2 = &s; // 不可变引用
let r3 = &mut s; // ❌ 编译错误:cannot borrow as mutable
// println!("{}, {}, {}", r1, r2, r3);
}
为什么有这些限制?
// 防止数据竞争(data race)
fn data_race_example() {
let mut data = vec![1, 2, 3];
// 假设允许同时有可变和不可变引用
let r1 = &data; // 读取引用
let r2 = &mut data; // 写入引用
r2.push(4); // 修改数据,可能导致重新分配内存
// println!("{:?}", r1); // r1 指向的内存可能已失效!
// Rust 在编译期就阻止了这种情况
}
4. 引用的作用域
fn main() {
let mut s = String::from("hello");
// ✅ 正确:引用的作用域在最后一次使用后结束
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// r1 和 r2 的作用域在这里结束
// ✅ 可以创建可变引用
let r3 = &mut s;
println!("{}", r3);
}
5. 悬垂引用(Dangling References)
Rust 编译器防止悬垂引用:
// ❌ 错误示例
fn dangle() -> &String {
let s = String::from("hello");
&s // ❌ 编译错误:返回局部变量的引用
} // s 离开作用域被释放,引用指向无效内存
// ✅ 正确做法
fn no_dangle() -> String {
let s = String::from("hello");
s // 移动所有权给调用者
}
6. 引用作为参数
fn main() {
let mut numbers = vec![1, 2, 3, 4, 5];
// 不可变借用:只读访问
print_vec(&numbers);
// 可变借用:可以修改
double_vec(&mut numbers);
println!("{:?}", numbers); // [2, 4, 6, 8, 10]
}
fn print_vec(v: &Vec<i32>) {
for num in v {
print!("{} ", num);
}
println!();
}
fn double_vec(v: &mut Vec<i32>) {
for num in v.iter_mut() {
*num *= 2; // 解引用并修改
}
}
7. 切片(Slice)- 特殊的引用
fn main() {
let s = String::from("hello world");
// 字符串切片
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
// 语法糖
let hello = &s[..5]; // 从开头到索引 5
let world = &s[6..]; // 从索引 6 到结尾
let whole = &s[..]; // 整个字符串
println!("{}, {}", hello, world);
}
// 切片的实际用途
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn use_first_word() {
let mut s = String::from("hello world");
let word = first_word(&s);
// ❌ 错误:不能在有不可变引用时修改
// s.clear(); // 编译错误
println!("first word: {}", word);
}
8. 数组切片
fn main() {
let a = [1, 2, 3, 4, 5];
// 数组切片
let slice = &a[1..3];
println!("{:?}", slice); // [2, 3]
// 函数使用切片
let sum = sum_slice(&a[..]);
println!("sum: {}", sum);
}
fn sum_slice(slice: &[i32]) -> i32 {
slice.iter().sum()
}
生命周期(Lifetime)
为什么需要生命周期?
// 问题:引用的有效期是多久?
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
// ❌ 编译错误:missing lifetime specifier
// 编译器不知道返回的引用是 x 还是 y,无法确定其生命周期
1. 生命周期标注语法
// 生命周期参数以 ' 开头,通常使用 'a, 'b, 'c
// 不改变引用的实际生命周期,只是描述引用之间的关系
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// 含义:返回值的生命周期与参数中生命周期较短的那个相同
fn main() {
let string1 = String::from("long string");
let string2 = String::from("short");
let result = longest(string1.as_str(), string2.as_str());
println!("longest: {}", result);
}
2. 生命周期规则详解
// 示例 1:有效的使用
fn example1() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
} // result, string2 离开作用域
}
// 示例 2:无效的使用
fn example2() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
} // ❌ string2 离开作用域,result 可能指向无效内存
// println!("The longest string is {}", result); // 编译错误
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
3. 深入理解生命周期
// 'a 表示一个生命周期,是一个泛型参数
// 例 1:返回值的生命周期与一个参数关联
fn first_word<'a>(s: &'a str) -> &'a str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
// 例 2:返回值的生命周期与第一个参数关联
fn longest_string<'a>(x: &'a str, y: &str) -> &'a str {
x // 只返回 x,所以只需要 x 的生命周期
}
// 例 3:多个生命周期参数
fn longest_with_announcement<'a, 'b>(
x: &'a str,
y: &'b str,
ann: &str,
) -> &'a str {
println!("Announcement: {}", ann);
if x.len() > y.len() {
x
} else {
// 如果要返回 y,需要改为 -> &'b str 或使用相同的生命周期
x
}
}
4. 结构体中的生命周期
// 结构体持有引用时需要生命周期标注
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
// 方法中的生命周期
fn level(&self) -> i32 {
3
}
// 返回引用的方法
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
// i 的生命周期不能超过 novel
let i = ImportantExcerpt {
part: first_sentence,
};
println!("Excerpt: {}", i.part);
}
5. 生命周期省略规则
编译器可以在某些情况下自动推断生命周期:
// 规则 1:每个引用参数都有自己的生命周期
fn print(s: &str) {
// 等价于:fn print<'a>(s: &'a str)
println!("{}", s);
}
// 规则 2:如果只有一个输入生命周期,它被赋予所有输出生命周期
fn first_word(s: &str) -> &str {
// 等价于:fn first_word<'a>(s: &'a str) -> &'a str
&s[..1]
}
// 规则 3:如果有多个输入生命周期,其中一个是 &self 或 &mut self,
// self 的生命周期被赋予所有输出生命周期
struct StrWrapper<'a> {
s: &'a str,
}
impl<'a> StrWrapper<'a> {
fn get_str(&self) -> &str {
// 等价于:fn get_str(&'a self) -> &'a str
self.s
}
}
// 需要显式标注的情况
fn longest(x: &str, y: &str) -> &str {
// ❌ 编译器无法推断,需要显式标注
if x.len() > y.len() { x } else { y }
}
6. 静态生命周期
// 'static 表示整个程序运行期间都有效
// 字符串字面量有 'static 生命周期
let s: &'static str = "I have a static lifetime.";
// 不要轻易使用 'static
fn wrong_use() -> &'static str {
let s = String::from("hello");
// &s // ❌ 错误:s 不是静态的
"hello" // ✅ 字符串字面量是静态的
}
// 正确使用 'static 的场景
static GLOBAL_CONFIG: &str = "configuration";
fn use_static() {
println!("{}", GLOBAL_CONFIG);
}
7. 复杂的生命周期示例
// 示例 1:结构体方法返回引用
struct Context<'a>(&'a str);
impl<'a> Context<'a> {
fn parse(&self) -> Result<(), &str> {
// 返回的 &str 生命周期与 self 相同
Err(&self.0[1..])
}
}
// 示例 2:多个生命周期
struct Parser<'c, 's> {
context: &'c Context<'c>,
input: &'s str,
}
impl<'c, 's> Parser<'c, 's> {
fn parse(&self) -> Result<(), &'s str> {
// 返回的引用与 input 的生命周期相同
self.context.parse()
}
}
// 示例 3:生命周期约束
struct Ref<'a, T: 'a>(&'a T);
// T: 'a 表示 T 中的所有引用都必须活得比 'a 长
智能指针
1. Box - 堆分配
fn main() {
// 在堆上分配
let b = Box::new(5);
println!("b = {}", b);
// 递归类型必须使用 Box
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
2. Rc - 引用计数
use std::rc::Rc;
fn main() {
// 多个所有者共享数据
let a = Rc::new(5);
// 克隆 Rc 增加引用计数,不克隆数据
let b = Rc::clone(&a);
let c = Rc::clone(&a);
println!("count: {}", Rc::strong_count(&a)); // 3
{
let d = Rc::clone(&a);
println!("count: {}", Rc::strong_count(&a)); // 4
}
println!("count: {}", Rc::strong_count(&a)); // 3
}
// Rc 的实际应用
fn rc_example() {
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
use List::{Cons, Nil};
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
// b 和 c 共享 a 的所有权
}
3. RefCell - 内部可变性
use std::cell::RefCell;
fn main() {
// 运行时借用检查
let data = RefCell::new(5);
// 获取不可变引用
let r1 = data.borrow();
let r2 = data.borrow();
println!("{}, {}", r1, r2);
drop(r1);
drop(r2);
// 获取可变引用
let mut r3 = data.borrow_mut();
*r3 += 1;
println!("{}", r3);
}
// RefCell 允许内部可变性
pub trait Messenger {
fn send(&self, msg: &str);
}
pub struct MockMessenger {
sent_messages: RefCell<Vec<String>>,
}
impl MockMessenger {
pub fn new() -> MockMessenger {
MockMessenger {
sent_messages: RefCell::new(vec![]),
}
}
}
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
// self 是不可变的,但可以修改内部数据
self.sent_messages.borrow_mut().push(String::from(message));
}
}
4. Rc - 多所有者 + 可变
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
// 多个所有者,可变数据
let value = Rc::new(RefCell::new(5));
let a = Rc::clone(&value);
let b = Rc::clone(&value);
let c = Rc::clone(&value);
*a.borrow_mut() += 10;
println!("{}", value.borrow()); // 15
*b.borrow_mut() += 20;
println!("{}", value.borrow()); // 35
}
实战示例
示例 1:实现链表
type Link<T> = Option<Box<Node<T>>>;
struct Node<T> {
elem: T,
next: Link<T>,
}
pub struct LinkedList<T> {
head: Link<T>,
}
impl<T> LinkedList<T> {
pub fn new() -> Self {
LinkedList { head: None }
}
pub fn push(&mut self, elem: T) {
let new_node = Box::new(Node {
elem,
next: self.head.take(),
});
self.head = Some(new_node);
}
pub fn pop(&mut self) -> Option<T> {
self.head.take().map(|node| {
self.head = node.next;
node.elem
})
}
pub fn peek(&self) -> Option<&T> {
self.head.as_ref().map(|node| &node.elem)
}
pub fn peek_mut(&mut self) -> Option<&mut T> {
self.head.as_mut().map(|node| &mut node.elem)
}
}
impl<T> Drop for LinkedList<T> {
fn drop(&mut self) {
let mut cur_link = self.head.take();
while let Some(mut boxed_node) = cur_link {
cur_link = boxed_node.next.take();
}
}
}
fn test_list() {
let mut list = LinkedList::new();
list.push(1);
list.push(2);
list.push(3);
assert_eq!(list.pop(), Some(3));
assert_eq!(list.peek(), Some(&2));
assert_eq!(list.pop(), Some(2));
assert_eq!(list.pop(), Some(1));
assert_eq!(list.pop(), None);
}
示例 2:实现迭代器
pub struct LinkedList<T> {
head: Link<T>,
}
type Link<T> = Option<Box<Node<T>>>;
struct Node<T> {
elem: T,
next: Link<T>,
}
// 不可变迭代器
pub struct Iter<'a, T> {
next: Option<&'a Node<T>>,
}
impl<T> LinkedList<T> {
pub fn iter(&self) -> Iter<T> {
Iter {
next: self.head.as_deref(),
}
}
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
self.next.map(|node| {
self.next = node.next.as_deref();
&node.elem
})
}
}
// 可变迭代器
pub struct IterMut<'a, T> {
next: Option<&'a mut Node<T>>,
}
impl<T> LinkedList<T> {
pub fn iter_mut(&mut self) -> IterMut<T> {
IterMut {
next: self.head.as_deref_mut(),
}
}
}
impl<'a, T> Iterator for IterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
self.next.take().map(|node| {
self.next = node.next.as_deref_mut();
&mut node.elem
})
}
}
fn test_iterator() {
let mut list = LinkedList::new();
list.push(1);
list.push(2);
list.push(3);
// 不可变迭代
for elem in list.iter() {
println!("{}", elem);
}
// 可变迭代
for elem in list.iter_mut() {
*elem *= 2;
}
}
示例 3:实现缓存
use std::collections::HashMap;
use std::hash::Hash;
struct Cacher<T, U, V>
where
T: Fn(&U) -> V,
U: Eq + Hash,
{
calculation: T,
cache: HashMap<U, V>,
}
impl<T, U, V> Cacher<T, U, V>
where
T: Fn(&U) -> V,
U: Eq + Hash + Clone,
V: Clone,
{
fn new(calculation: T) -> Cacher<T, U, V> {
Cacher {
calculation,
cache: HashMap::new(),
}
}
fn value(&mut self, arg: U) -> V {
match self.cache.get(&arg) {
Some(v) => v.clone(),
None => {
let v = (self.calculation)(&arg);
self.cache.insert(arg, v.clone());
v
}
}
}
}
fn expensive_calculation(intensity: &u32) -> u32 {
println!("calculating slowly...");
std::thread::sleep(std::time::Duration::from_secs(2));
*intensity
}
fn use_cacher() {
let mut cacher = Cacher::new(expensive_calculation);
println!("{}", cacher.value(10)); // 慢
println!("{}", cacher.value(10)); // 快(使用缓存)
println!("{}", cacher.value(20)); // 慢
println!("{}", cacher.value(20)); // 快(使用缓存)
}
示例 4:本项目中的所有权应用
// src/services/user_service.rs
use sqlx::MySqlPool;
use crate::models::user::User;
use crate::utils::cache::RedisCache;
pub struct UserService {
// 拥有 MySqlPool 的所有权
db: MySqlPool,
// 拥有 RedisCache 的所有权
cache: RedisCache,
}
impl UserService {
// 取得所有权
pub fn new(db: MySqlPool, cache: RedisCache) -> Self {
Self { db, cache }
}
// 借用 self(不可变引用)
pub async fn get_user(&self, user_id: i64) -> Result<Option<User>> {
let cache_key = format!("user:{}", user_id);
// 借用 cache
if let Some(cached) = self.cache.get(&cache_key).await? {
// 反序列化返回(移动所有权给调用者)
return Ok(Some(serde_json::from_str(&cached)?));
}
// 借用 db
let user = sqlx::query_as::<_, User>(
"SELECT * FROM users WHERE id = ?"
)
.bind(user_id)
.fetch_optional(&self.db) // &self.db 是借用
.await?;
// 返回 Option<User>(移动所有权)
Ok(user)
}
// 借用 self(可变引用)- 如果需要修改内部状态
// 注意:在本例中,即使修改数据,也只需要不可变引用
// 因为 MySqlPool 和 RedisCache 内部使用了内部可变性
}
// 使用示例
async fn use_service() {
let pool = create_pool().await;
let cache = RedisCache::new("redis://localhost").await.unwrap();
// service 拥有 pool 和 cache 的所有权
let service = UserService::new(pool, cache);
// 借用 service
if let Ok(Some(user)) = service.get_user(1).await {
// user 拥有数据的所有权
println!("{:?}", user);
}
// service 离开作用域,pool 和 cache 被清理
}
示例 5:生命周期在本项目中的应用
// src/models/user.rs
// 结构体持有引用,需要生命周期标注
pub struct UserView<'a> {
username: &'a str,
email: &'a str,
}
impl<'a> UserView<'a> {
// 从 User 创建视图(借用)
pub fn from_user(user: &'a User) -> Self {
UserView {
username: &user.username,
email: &user.email,
}
}
// 返回的引用与 self 的生命周期相同
pub fn get_username(&self) -> &str {
self.username
}
}
// 使用示例
fn use_user_view() {
let user = User {
id: 1,
username: String::from("alice"),
email: String::from("alice@example.com"),
// ...
};
// 视图借用 user 的数据
let view = UserView::from_user(&user);
println!("{}", view.get_username());
// view 的生命周期不能超过 user
}
常见问题
1. 如何选择使用 & 还是 &mut?
// 规则:
// - 需要修改数据:使用 &mut
// - 只需要读取数据:使用 &
// - 多个函数同时读取:使用 &(可以有多个)
// - 一个函数独占修改:使用 &mut(只能有一个)
fn read_only(data: &Vec<i32>) {
// 只读,不修改
for item in data {
println!("{}", item);
}
}
fn modify(data: &mut Vec<i32>) {
// 修改数据
data.push(42);
}
fn main() {
let mut numbers = vec![1, 2, 3];
read_only(&numbers); // 借用
read_only(&numbers); // 可以多次借用
modify(&mut numbers); // 可变借用
// modify(&mut numbers); // ❌ 不能同时有多个可变借用
}
2. 如何理解 "借用检查器"?
// 借用检查器在编译期检查:
// 1. 没有悬垂引用
// 2. 不会同时有可变和不可变引用
// 3. 不会有多个可变引用
fn borrow_checker() {
let mut s = String::from("hello");
// 阶段 1:不可变借用
let r1 = &s;
let r2 = &s;
println!("{} {}", r1, r2);
// r1 和 r2 的作用域结束
// 阶段 2:可变借用(安全,因为之前的不可变引用已经结束)
let r3 = &mut s;
r3.push_str(" world");
println!("{}", r3);
}
3. 什么时候使用 Clone?
// 使用 Clone 的场景:
// 1. 需要真正的副本
// 2. 多个所有者需要独立修改数据
// 3. 跨线程传递数据
fn when_to_clone() {
let original = vec![1, 2, 3];
// 场景 1:需要独立的副本
let mut copy = original.clone();
copy.push(4);
// original 不受影响
// 场景 2:传递给会取得所有权的函数
process_vec(original.clone());
// original 仍然可用
// 场景 3:避免,如果可以用引用
// ❌ 不必要的克隆
let unnecessary = original.clone();
print_vec(&unnecessary);
// ✅ 更好的方式
print_vec(&original);
}
fn process_vec(v: Vec<i32>) {
// 取得所有权
}
fn print_vec(v: &Vec<i32>) {
// 只是借用
}
4. String vs &str
// String:拥有所有权的字符串(堆分配)
// &str:字符串切片(引用)
fn string_vs_str() {
// String - 可变、可增长
let mut s = String::from("hello");
s.push_str(" world");
// &str - 不可变、固定大小
let s1: &str = "hello"; // 字符串字面量
let s2: &str = &s; // String 的引用
let s3: &str = &s[0..5]; // 切片
// 函数参数:优先使用 &str(更灵活)
fn print_str(s: &str) {
println!("{}", s);
}
print_str("literal"); // ✅
print_str(&s); // ✅
print_str(&s[..]); // ✅
// 如果需要所有权或修改,使用 String
fn take_ownership(s: String) {
// 拥有 s
}
}
5. Vec vs &[T]
// Vec<T>:拥有所有权的动态数组
// &[T]:数组切片(引用)
fn vec_vs_slice() {
// Vec - 可变、可增长
let mut v = vec![1, 2, 3];
v.push(4);
// &[T] - 引用,不拥有数据
let slice: &[i32] = &v;
let slice2: &[i32] = &v[1..3];
// 函数参数:优先使用 &[T](更灵活)
fn sum_slice(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
let array = [1, 2, 3];
sum_slice(&array); // ✅ 数组
sum_slice(&v); // ✅ Vec
sum_slice(slice); // ✅ 切片
}
6. 如何处理 "cannot move out of borrowed content"?
struct Container {
data: String,
}
fn move_error() {
let c = Container {
data: String::from("hello"),
};
let r = &c;
// ❌ 错误:不能从借用的内容中移出
// let s = r.data;
// ✅ 解决方案 1:克隆
let s = r.data.clone();
// ✅ 解决方案 2:使用引用
let s = &r.data;
// ✅ 解决方案 3:如果不再需要 c,获取所有权
let s = c.data; // c 被移动
}
7. 循环引用问题
use std::rc::Rc;
use std::cell::RefCell;
// ❌ 问题:循环引用导致内存泄漏
struct Node {
next: Option<Rc<RefCell<Node>>>,
}
fn memory_leak() {
let a = Rc::new(RefCell::new(Node { next: None }));
let b = Rc::new(RefCell::new(Node { next: Some(Rc::clone(&a)) }));
// 创建循环
a.borrow_mut().next = Some(Rc::clone(&b));
// a 和 b 相互引用,引用计数永远不会归零
}
// ✅ 解决方案:使用 Weak<T>
use std::rc::Weak;
struct BetterNode {
next: Option<Rc<RefCell<BetterNode>>>,
prev: Option<Weak<RefCell<BetterNode>>>, // 使用 Weak 避免循环
}
总结
所有权规则速查
| 概念 | 规则 | 用途 |
|---|---|---|
| 所有权 | 每个值有唯一所有者 | 自动内存管理 |
| 移动 | 默认转移所有权 | 避免双重释放 |
| 复制 | Copy 类型自动复制 | 简单类型高效 |
| 借用 | 创建引用不获取所有权 | 临时访问数据 |
| 不可变引用 | 可以有多个 &T | 共享只读访问 |
| 可变引用 | 只能有一个 &mut T | 独占修改访问 |
| 生命周期 | 引用有效期标注 | 防止悬垂引用 |
关键原则
- 默认移动:赋值和传参默认移动所有权
- 明确借用:使用
&创建引用 - 单一可变:同时只能有一个可变引用
- 引用安全:引用的生命周期不能超过数据
- 编译期检查:所有检查在编译期完成,零运行时开销
实践建议
// ✅ 好的实践
// 1. 参数优先使用引用
fn process(data: &Vec<i32>) { }
// 2. 返回值可以移动所有权
fn create() -> Vec<i32> {
vec![1, 2, 3]
}
// 3. 需要修改才用 &mut
fn modify(data: &mut Vec<i32>) {
data.push(42);
}
// 4. 使用切片而不是具体类型
fn flexible(s: &str) { } // 而不是 &String
fn flexible2(nums: &[i32]) { } // 而不是 &Vec<i32>
// 5. 生命周期让编译器推断
fn auto_lifetime(s: &str) -> &str { s }
// 而不是:fn explicit<'a>(s: &'a str) -> &'a str { s }
调试建议
// 遇到借用检查错误时:
// 1. 检查引用的作用域
// 2. 确认是否需要 &mut
// 3. 考虑使用 clone()(临时方案)
// 4. 重新设计数据结构
// 5. 使用智能指针(Rc, RefCell)
记住:Rust 的所有权系统看起来复杂,但它在编译期就防止了大量的运行时错误!掌握它是成为 Rust 高手的关键。