智能指针
这篇讲 Rust 的智能指针——Box<T>、Rc<T>、Arc<T> 和 RefCell<T>。当所有权系统的基础规则不够用时,这些类型提供了灵活性——但每种都有明确的语义和代价。
Box<T>——堆分配
Box<T> 将数据分配到堆上,返回指向它的指针。主要用途:
存储编译时大小未知的类型
fn main() {
// 递归类型——编译器不知道 List 有多大
// 用 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))))));
}保存实现了某个 trait 的类型(trait 对象)
trait Draw {
fn draw(&self);
}
struct Button {
label: String,
}
impl Draw for Button {
fn draw(&self) {
println!("[Button] {}", self.label);
}
}
struct TextBox {
content: String,
}
impl Draw for TextBox {
fn draw(&self) {
println!("[TextBox] {}", self.content);
}
}
fn main() {
// Box<dyn Draw> 可以存储任何实现了 Draw 的类型
let components: Vec<Box<dyn Draw>> = vec![
Box::new(Button { label: "OK".into() }),
Box::new(TextBox { content: "Hello".into() }),
];
for component in components {
component.draw();
}
}Rc<T>——引用计数(单线程)
Rc<T> 允许多个所有者共享同一份数据。只在单线程中使用:
use std::rc::Rc;
fn main() {
let a = Rc::new(vec![1, 2, 3]);
println!("count after creating a = {}", Rc::strong_count(&a)); // 1
let b = Rc::clone(&a); // 不复制数据,只增加引用计数
println!("count after creating b = {}", Rc::strong_count(&a)); // 2
{
let c = Rc::clone(&a);
println!("count after creating c = {}", Rc::strong_count(&a)); // 3
} // c 离开作用域,计数减 1
println!("count after c goes out = {}", Rc::strong_count(&a)); // 2
println!("a: {:?}, b: {:?}", a, b);
// Rc 只提供不可变访问。要和 RefCell 配合才能修改数据
}Rc<T> 不是线程安全的——它使用非原子计数来提升性能。跨线程共享请用 Arc<T>。编译器会拒绝你在线程中使用 Rc<T>,因为 Rc<T> 没有实现 Send。Arc<T>——原子引用计数(多线程)
Arc<T> 是 Rc<T> 的线程安全版本(A = Atomic)。前面并发章节已经用过:
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();
}
}RefCell<T>——内部可变性
RefCell<T> 允许在拥有不可变引用时修改内部数据——将借用规则的检查从编译期推迟到运行时:
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
{
let mut v = data.borrow_mut(); // 可变借用
*v += 1;
} // 可变借用在此结束
println!("data = {}", data.borrow()); // 不可变借用 → 6
}违反借用规则会在运行时 panic:
fn main() {
let data = RefCell::new(5);
let r1 = data.borrow(); // 不可变借用
let r2 = data.borrow_mut(); // ❌ 运行时 panic!已经有一个不可变借用
println!("{r1}");
}组合使用
常见组合模式:
| 组合 | 用途 |
|---|---|
Rc<RefCell<T>> | 单线程下多个所有者的可变数据 |
Arc<Mutex<T>> | 多线程下安全的可变共享 |
Arc<RwLock<T>> | 多读少写场景(读写锁) |
use std::rc::Rc;
use std::cell::RefCell;
// 单线程场景:多个所有者 + 可修改
#[derive(Debug)]
struct Node {
value: i32,
children: Vec<Rc<RefCell<Node>>>,
}
fn main() {
let leaf = Rc::new(RefCell::new(Node {
value: 3,
children: vec![],
}));
let branch = Rc::new(RefCell::new(Node {
value: 5,
children: vec![Rc::clone(&leaf)],
}));
// 修改 leaf 的 value
leaf.borrow_mut().value = 10;
println!("branch: {:?}", branch);
}Deref 和 Drop Trait
智能指针通过 Deref trait 实现了 * 解引用操作,让智能指针可以像普通引用一样使用:
fn main() {
let x = Box::new(5);
// *x 能工作是因为 Box<T> 实现了 Deref<Target=T>
println!("{}", *x); // 5
// 隐式 Deref 强制转换
let s = Box::new(String::from("hello"));
// &s → &Box<String> → &String → &str(自动转换链)
print_str(&s);
}
fn print_str(s: &str) {
println!("{s}");
}Drop trait 定义了值离开作用域时的清理行为:
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer { data: String::from("my stuff") };
let d = CustomSmartPointer { data: String::from("other stuff") };
println!("CustomSmartPointers created.");
} // d 先 drop,c 后 drop(栈的逆序)
一句话小结
Box<T>:堆分配,trait 对象,递归类型Rc<T>:单线程引用计数,多个所有者Arc<T>:多线程引用计数(配合Mutex使用)RefCell<T>:运行时借用检查,内部可变性Deref:让智能指针像引用一样使用Drop:离开作用域时自动清理
Rust 进阶系列到此完结。从基础语法到所有权系统,从 Web 框架到并发异步,你已经掌握了 Rust 的核心能力栈。回到 Rust 基础 复习,或者开始用 Rust 写你的下一个项目吧。
练习
- 实现一个简单的二叉树,节点用
Box<Node>表示左右子树。 - 用
Rc<RefCell<i32>>实现两个变量共享同一份计数器数据,分别递增后打印。
参考答案
// 1. 二叉树
#[derive(Debug)]
struct TreeNode {
value: i32,
left: Option<Box<TreeNode>>,
right: Option<Box<TreeNode>>,
}
impl TreeNode {
fn new(value: i32) -> Self {
TreeNode { value, left: None, right: None }
}
fn insert(&mut self, value: i32) {
if value < self.value {
match &mut self.left {
Some(node) => node.insert(value),
None => self.left = Some(Box::new(TreeNode::new(value))),
}
} else {
match &mut self.right {
Some(node) => node.insert(value),
None => self.right = Some(Box::new(TreeNode::new(value))),
}
}
}
}
// 2. 共享计数器
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let counter = Rc::new(RefCell::new(0));
let c1 = Rc::clone(&counter);
let c2 = Rc::clone(&counter);
*c1.borrow_mut() += 10;
*c2.borrow_mut() += 20;
println!("counter = {}", counter.borrow()); // 30
}最后更新于