在第四篇文章中,我们学习了所有权、借用和生命周期------它们构成了 Rust 内存安全的基础。但在实际开发中,有些场景用简单的栈分配和借用无法优雅地表达:需要在堆上分配数据、需要多个所有者共享同一份数据、需要在不可变引用中修改数据、需要跨线程共享所有权。这些场景需要更强大的工具------智能指针和内部可变性。本文从 Box 讲起,深入讲解 Rc、RefCell、Arc、Mutex 的使用场景与组合方式,并通过一个多所有权场景的实战帮你掌握进阶内存管理。
一、什么是智能指针?
智能指针是一种数据结构,它的行为类似指针,但拥有额外的元数据和能力。Rust 中的智能指针通常实现了两个关键 trait:
Deref:允许智能指针像引用一样使用(通过 * 解引用)。
Drop:在值离开作用域时自动执行清理逻辑。
Rust 标准库提供了多种智能指针,覆盖不同的使用场景:

二、Box:最简单的堆分配
Box 是最基础的智能指针,它将数据存储在堆上,栈上只保留一个指针。
2.1 基本用法
rust
fn main() {
let b = Box::new(5);
println!("b = {}", b); // 自动解引用
println!("*b = {}", *b); // 显式解引用
// Box 离开作用域时,堆上的数据自动释放
}
2.2 使用场景一:递归类型
Rust 需要知道每个类型的大小。递归类型(如链表)如果不使用 Box,大小将无法确定:
rust
// ❌ 编译错误:递归类型大小无限
// 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))))));
println!("链表创建成功");
}
Box 的大小是固定的(指针大小),因此 List 的大小也是确定的。
2.3 使用场景二:大对象转移所有权
当需要转移一个大对象的所有权但不想拷贝数据时,Box 可以避免栈上的大量拷贝:
rust
fn process(data: Box<[u8; 1024 * 1024]>) {
// 只传递指针,不拷贝 1MB 数据
println!("处理 {} 字节", data.len());
}
fn main() {
let big_data = Box::new([0u8; 1024 * 1024]);
process(big_data); // 所有权转移,但只移动指针
}
2.4 使用场景三:Trait 对象
Box 是存放 trait 对象的标准方式:
rust
trait Animal {
fn speak(&self);
}
struct Dog;
struct Cat;
impl Animal for Dog {
fn speak(&self) { println!("汪汪!"); }
}
impl Animal for Cat {
fn speak(&self) { println!("喵喵!"); }
}
fn main() {
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog),
Box::new(Cat),
];
for animal in &animals {
animal.speak();
}
}
三、Rc:单线程的引用计数
Rc(Reference Counting)允许多个所有者共享同一份数据。它维护一个引用计数器,当计数归零时自动释放数据。
3.1 基本用法
rust
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("shared data"));
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
} // a 和 b 离开作用域,计数归零,数据释放
💡 注意:Rc::clone(&a) 不会深拷贝数据,只增加引用计数。它比 a.clone() 更清晰地表达"增加引用"的意图。
3.2 使用场景:共享不可变数据
rust
use std::rc::Rc;
#[derive(Debug)]
struct Node {
value: i32,
children: Vec<Rc<Node>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
children: vec![],
});
let branch = Rc::new(Node {
value: 5,
children: vec![Rc::clone(&leaf)], // 共享 leaf
});
// leaf 现在有 2 个所有者
println!("leaf 引用计数: {}", Rc::strong_count(&leaf));
println!("branch: {:?}", branch);
}
3.3 Rc 的限制
Rc 不是线程安全的。它使用非原子操作维护引用计数,在多线程中使用会导致数据竞争。多线程场景应使用 Arc。
四、RefCell:内部可变性
内部可变性是一种设计模式:即使拥有不可变引用,也允许修改内部数据。
4.1 为什么需要 RefCell?
Rust 的借用规则在编译期执行:不可变引用不能修改数据。但有些场景下,你需要在"持有不可变引用"时修改数据------例如实现观察者模式、图结构等。
RefCell 将借用检查从编译期移到运行时。它允许你在持有不可变引用时,通过 borrow_mut() 获取可变引用。
rust
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
// 不可变借用
println!("值: {}", data.borrow());
// 可变借用(即使 data 本身不可变)
*data.borrow_mut() += 10;
println!("修改后: {}", data.borrow());
}
4.2 运行时借用检查
RefCell 在运行时检查借用规则。违反规则会导致 panic:
rust
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
let r1 = data.borrow();
let r2 = data.borrow(); // ✅ 多个不可变借用可以
// let r3 = data.borrow_mut(); // ❌ 运行时 panic:已存在不可变借用
}
RefCell 的 borrow() 和 borrow_mut() 返回智能指针:
borrow() 返回 Ref
borrow_mut() 返回 RefMut
两者都在离开作用域时自动释放借用。
4.3 try_borrow:避免 panic
rust
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
let r1 = data.borrow();
match data.try_borrow_mut() {
Ok(mut r) => *r += 1,
Err(e) => println!("借用失败: {}", e),
}
}
五、Rc<RefCell>:多所有者 + 可变访问
将 Rc 和 RefCell 组合,可以实现多个所有者共享可变数据:
rust
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let shared = Rc::new(RefCell::new(vec![1, 2, 3]));
let a = Rc::clone(&shared);
let b = Rc::clone(&shared);
// 通过 a 修改
a.borrow_mut().push(4);
// 通过 b 读取
println!("b 看到的数据: {:?}", b.borrow()); // [1, 2, 3, 4]
}
5.1 实战:图结构
rust
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
neighbors: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let node1 = Rc::new(Node {
value: 1,
neighbors: RefCell::new(vec![]),
});
let node2 = Rc::new(Node {
value: 2,
neighbors: RefCell::new(vec![Rc::clone(&node1)]),
});
// 给 node1 添加邻居(即使 node1 被共享)
node1.neighbors.borrow_mut().push(Rc::clone(&node2));
println!("node1 的邻居数: {}", node1.neighbors.borrow().len());
println!("node1 引用计数: {}", Rc::strong_count(&node1));
}
六、Arc:多线程的引用计数
Arc(Atomic Reference Counting)是 Rc 的线程安全版本。它使用原子操作维护引用计数:
rust
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 || {
println!("线程 {} 看到: {:?}", i, data);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
Rc vs Arc:

💡 原则:单线程用 Rc,多线程用 Arc。不要为了"以后可能多线程"而盲目使用 Arc。
七、Mutex:多线程的内部可变性
Mutex(互斥锁)提供跨线程的内部可变性。它确保同一时刻只有一个线程能访问数据:
rust
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
}
关键点:
lock() 返回 LockResult<MutexGuard>,获取锁可能失败(其他线程 panic 时)。
MutexGuard 是智能指针,离开作用域时自动释放锁。
Arc<Mutex> 是多线程共享可变状态的经典模式。
八、RwLock:读写锁
RwLock 允许多个读者同时访问,但写者独占:
rust
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let data = Arc::new(RwLock::new(vec![1, 2, 3]));
// 多个读者
let mut readers = vec![];
for i in 0..3 {
let data = Arc::clone(&data);
readers.push(thread::spawn(move || {
let r = data.read().unwrap();
println!("读者 {}: {:?}", i, *r);
}));
}
// 一个写者
let data_w = Arc::clone(&data);
let writer = thread::spawn(move || {
let mut w = data_w.write().unwrap();
w.push(4);
println!("写者完成");
});
for r in readers { r.join().unwrap(); }
writer.join().unwrap();
println!("最终数据: {:?}", *data.read().unwrap());
}
Mutex vs RwLock:

九、Weak:打破循环引用
Rc 和 Arc 的循环引用会导致内存泄漏。Weak 提供弱引用,不增加强引用计数:
rust
use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
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 的父节点为 branch(弱引用)
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
// 通过 weak 访问父节点
if let Some(parent) = leaf.parent.borrow().upgrade() {
println!("leaf 的父节点值: {}", parent.value);
}
// branch 的强引用计数为 1(只有外部变量),不会被 leaf 持有
println!("branch 强引用计数: {}", Rc::strong_count(&branch));
println!("branch 弱引用计数: {}", Rc::weak_count(&branch));
}
使用原则:
父节点对子节点用 Rc(强引用)
子节点对父节点用 Weak(弱引用)
需要访问时用 upgrade() 获取临时强引用
十、小结
Box :最简单的堆分配,用于递归类型、大对象和 trait 对象。
Rc :单线程多所有者,通过引用计数共享不可变数据。
RefCell :内部可变性,将借用检查从编译期移到运行时。
Rc<RefCell> :单线程多所有者 + 可变访问。
Arc :多线程版本的 Rc,使用原子操作。
Mutex :多线程互斥访问,Arc<Mutex> 是共享可变状态的经典模式。
RwLock :读写锁,适合读多写少的场景。
Weak :弱引用,用于打破循环引用。