跳到主要内容

智能指针

问题

Rust 有哪些智能指针?各自适用什么场景?

答案

智能指针总览

指针所有权可变性线程安全常用场景
Box<T>独占遵循借用规则Send+Sync堆分配、递归类型、Trait Object
Rc<T>共享不可变单线程共享所有权
Arc<T>共享不可变多线程共享所有权
Cell<T>内部可变Copy 类型的内部可变性
RefCell<T>内部可变运行时借用检查
Mutex<T>内部可变多线程互斥访问
RwLock<T>内部可变多线程读写锁

Box

// 1. 堆分配
let b = Box::new(5);
println!("{}", *b); // 解引用

// 2. 递归类型
enum List {
Cons(i32, Box<List>),
Nil,
}
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));

// 3. Trait Object
let shape: Box<dyn Shape> = Box::new(Circle { radius: 1.0 });

Rc(Reference Counting)

use std::rc::Rc;

let a = Rc::new(vec![1, 2, 3]);
let b = Rc::clone(&a); // 引用计数 +1(不拷贝数据)
let c = Rc::clone(&a);

println!("引用计数: {}", Rc::strong_count(&a)); // 3
// 所有 Rc 被 drop 后数据被释放

RefCell(运行时借用检查)

use std::cell::RefCell;

let cell = RefCell::new(vec![1, 2, 3]);

// 运行时借用检查(非编译时)
{
let mut borrow = cell.borrow_mut(); // 可变借用
borrow.push(4);
}
{
let borrow = cell.borrow(); // 不可变借用
println!("{:?}", borrow);
}

// ❌ 运行时 panic:已有不可变借用时不能可变借用
// let r = cell.borrow();
// let w = cell.borrow_mut(); // panic!

Rc + RefCell 组合

use std::cell::RefCell;
use std::rc::Rc;

// 多所有者 + 可变性
let shared = Rc::new(RefCell::new(vec![]));

let a = Rc::clone(&shared);
let b = Rc::clone(&shared);

a.borrow_mut().push(1);
b.borrow_mut().push(2);
println!("{:?}", shared.borrow()); // [1, 2]

Cell

use std::cell::Cell;

// Cell 适用于 Copy 类型,通过 get/set 操作
let cell = Cell::new(42);
cell.set(100);
println!("{}", cell.get()); // 100

// 常用于结构体中的计数器
struct Counter {
count: Cell<u32>,
}
impl Counter {
fn increment(&self) { // 不需要 &mut self
self.count.set(self.count.get() + 1);
}
}

内部可变性对比


常见面试问题

Q1: Box<T> 和直接在栈上存储有什么区别?

答案

Box 将数据分配在堆上,栈上只保存一个指针(8 字节)。适用于:

  • 大数据避免栈溢出
  • 递归类型(编译器需要确定大小)
  • Trait Object(大小不确定)
  • 转移所有权时避免大数据拷贝

Q2: 什么时候用 Rc,什么时候用 Arc?

答案

  • 单线程共享所有权 → Rc(无原子操作开销)
  • 多线程共享所有权 → Arc(原子引用计数)

不要在单线程中使用 Arc——原子操作虽然开销小但非零。

Q3: RefCell 和编译时借用检查有什么区别?

答案

特性编译时检查RefCell 运行时检查
检查时机编译期运行时
违规后果编译错误panic
性能零开销微小开销
灵活性有时过于严格更灵活

RefCell 用于编译器无法证明借用安全但你确信没问题的场景(如图数据结构、观察者模式)。

Q4: Rc 会造成内存泄漏吗?

答案

会。Rc 的循环引用会导致引用计数永远不归零,数据永远不释放。使用 Weak 打破循环:

use std::rc::{Rc, Weak};

struct Node {
parent: RefCell<Weak<Node>>, // 弱引用,不增加计数
children: RefCell<Vec<Rc<Node>>>, // 强引用
}

Q5: 智能指针如何实现 Deref?

答案

所有智能指针都实现 Deref trait,让你可以像操作普通引用一样使用它们:

let b = Box::new(42);
println!("{}", *b); // Deref 解引用
println!("{}", b.abs()); // 自动 Deref 到 i32 的方法

相关链接