Rust的所有权系统在编译期就杜绝了大多数内存问题,但有些场景需要更灵活的内存管理策略。Box、Rc、Arc这三个智能指针覆盖了最常见的需求。
Box<T>:堆分配
Box<T>是最简单的智能指针,将数据分配到堆上,栈上只保留一个指针。
基本用法
fn main() {
let b = Box::new(5);
println!("b = {}", b); // 自动解引用,可以直接使用
} // b离开作用域,堆上的数据自动释放
典型场景:递归类型
递归类型的大小在编译期无法确定,必须用Box间接引用:
// 这段代码无法编译——编译器不知道List有多大
// enum List {
// Cons(i32, List),
// Nil,
// }
// 用Box解决
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
Deref Trait
Box实现了Deref trait,所以可以像普通引用一样使用:
fn hello(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
let s = Box::new(String::from("Rust"));
hello(&s); // Box<String> -> &String -> &str,自动解引用链
}
Rc<T>:引用计数
当一份数据需要多个所有者时,Rc<T>(Reference Counting)登场。它在运行时追踪引用计数,计数归零时释放数据。
限制:只能用在单线程环境。
use std::rc::Rc;
fn main() {
let a = Rc::new(vec![1, 2, 3]);
println!("引用计数: {}", Rc::strong_count(&a)); // 1
let b = Rc::clone(&a); // 增加引用计数,不是深拷贝
println!("引用计数: {}", Rc::strong_count(&a)); // 2
{
let c = Rc::clone(&a);
println!("引用计数: {}", Rc::strong_count(&a)); // 3
} // c离开作用域
println!("引用计数: {}", Rc::strong_count(&a)); // 2
}
经典用例:共享图节点
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
fn main() {
let shared_tail = Rc::new(List::Cons(5, Rc::new(List::Cons(10, Rc::new(List::Nil)))));
// a 和 b 共享同一个尾部
let a = List::Cons(3, Rc::clone(&shared_tail));
let b = List::Cons(4, Rc::clone(&shared_tail));
}
Weak<T>:打破循环引用
Rc的引用计数有个问题:如果两个Rc互相引用,计数永远不会归零,导致内存泄漏。Weak<T>不增加强引用计数,专门用来打破循环。
use std::rc::{Rc, Weak};
use std::cell::RefCell;
struct Node {
value: i32,
parent: RefCell<Weak<Node>>, // 弱引用指向父节点
children: RefCell<Vec<Rc<Node>>>, // 强引用指向子节点
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
// 设置leaf的父节点(用弱引用)
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
// 通过弱引用访问父节点
if let Some(parent) = leaf.parent.borrow().upgrade() {
println!("leaf的父节点值: {}", parent.value);
}
println!("branch强引用: {}", Rc::strong_count(&branch)); // 1
println!("branch弱引用: {}", Rc::weak_count(&branch)); // 1
}
规则很清晰:父->子用强引用(Rc),子->父用弱引用(Weak)。
Arc<T>:线程安全版Rc
Arc(Atomically Reference Counted)和Rc功能一样,区别在于引用计数的增减使用原子操作,因此可以安全地跨线程共享。
use std::sync::Arc;
use std::thread;
fn main() {
let data = Arc::new(vec![1, 2, 3, 4, 5]);
let mut handles = vec![];
for i in 0..3 {
let data = Arc::clone(&data);
let handle = thread::spawn(move || {
let sum: i32 = data.iter().sum();
println!("线程{}: sum = {}", i, sum);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
Arc + Mutex:可变共享
Arc只提供共享的不可变访问。如果需要修改数据,配合Mutex:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("最终计数: {}", *counter.lock().unwrap()); // 10
}
使用场景总结
| 智能指针 | 所有权 | 线程安全 | 可变性 | 典型场景 |
|---|---|---|---|---|
Box<T> |
唯一 | 可Send | 独占可变 | 递归类型、大数据堆分配、trait对象 |
Rc<T> |
共享 | 否 | 不可变(配合RefCell) | 单线程共享数据、图/树结构 |
Weak<T> |
无 | 否 | - | 打破Rc循环引用 |
Arc<T> |
共享 | 是 | 不可变(配合Mutex) | 多线程共享数据 |
选择原则:
- 只需要堆分配 ->
Box - 单线程多所有者 ->
Rc(需要可变性加RefCell) - 多线程多所有者 ->
Arc(需要可变性加Mutex或RwLock) - 有循环引用风险 -> 在合适的方向使用
Weak