
Box、Rc、Arc 三剑客:堆上数据的"智能管家"
还记得上篇我们说的吗?堆上数据就像自由市场,随便圈地但得自己收拾。
但 Rust 说:"太麻烦了,我给你请几个管家吧!"
于是就有了智能指针。它们比普通指针多了啥?多了"智能"——知道什么时候该分配内存,什么时候该释放,还能处理各种复杂的所有权关系。
我第一次看到 Box、Rc、Arc 这三个家伙时,脑子里全是:"这仨有啥区别?我到底该用哪个?"
别急,今天咱们就把这仨管家摸得明明白白。
普通指针就像个地址标签,只告诉你东西在哪儿。
智能指针像是个快递柜:
use std::rc::Rc;
fn main() {
let data = Rc::new(); // 智能指针
// data 离开作用域时,自动检查还有没有人用
// 没人用了就自动释放
}
Box 的人设: "我就帮你把东西放堆上,其他不管。"
特点:
fn main() {
let b = Box::new(); // 5 放在堆上
println!("b = {}", b);
// b 离开作用域,堆上的 5 自动释放
}
什么时候用 Box?
Rc 的人设: "东西大家共享,最后一个走的记得关灯。"
Rc = Reference Counting(引用计数)
特点:
use std::rc::Rc;
fn main() {
let data = Rc::new();
let data2 = Rc::clone(&data); // 计数 +1
let data3 = Rc::clone(&data); // 计数 +1
println!("计数:{}", Rc::strong_count(&data)); // 输出:3
// data3 离开作用域,计数 -1
// data2 离开作用域,计数 -1
// data 离开作用域,计数 -1,变成 0,释放数据
}
什么时候用 Rc?
Arc 的人设: "跟 Rc 一样,但我能多线程。"
Arc = Atomic Reference Counting(原子引用计数)
特点:
use std::sync::Arc;
use std::thread;
fn main() {
let data = Arc::new();
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("线程里:{}", data_clone);
});
handle.join().unwrap();
println!("主线程:{}", data);
}
什么时候用 Arc?
Deref 的人设: "我虽然是指针,但你可以当我就是数据本身。"
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.
}
}
fn main() {
let mb = MyBox();
println!("{}", *mb); // 可以解引用
println!("{}", mb); // 甚至可以不解引用(自动 deref)
}
Rust 的自动 deref:
let s = String::from("hello");
let slice: &str = &s; // 自动 deref,String → &str
Drop 的人设: "我要走了,临走前有些事得处理。"
struct Cleaner {
name: String,
}
impl Drop for Cleaner {
fn drop(&mut self) {
println!("{} 正在清理资源...", self.name);
// 这里可以释放文件、网络连接等
}
}
fn main() {
let c = Cleaner {
name: String::from("清洁工"),
};
println!("清洁工开始工作");
// c 离开作用域,自动调用 drop()
}
// 输出:
// 清洁工开始工作
// 清洁工正在清理资源...
手动提前 drop:
fn main() {
let c = Cleaner {
name: String::from("清洁工"),
};
println!("开始工作");
drop(c); // 手动提前释放
println!("工作结束");
// 这里不会再 drop 一次
}
fn main() {
// 基本用法
let b = Box::new();
println!("b = {}", b); // 输出:5
// Box 里的数据可以修改(如果是可变的)
let mut b2 = Box::new();
*b2 = ; // 解引用修改
println!("b2 = {}", b2); // 输出:20
// Box 可以嵌套
let nested = Box::new(Box::new(Box::new("深度嵌套")));
println!("{}", ***nested);
}
// 链表节点:每个节点指向下一个节点
enum List {
Cons(i32, Box<List>), // Box 让递归类型成为可能
Nil,
}
use List::{Cons, Nil};
fn main() {
let list = Cons(,
Box::new(Cons(,
Box::new(Cons(,
Box::new(Nil))))));
// 打印链表
print_list(&list);
}
fn print_list(list: &List) {
match list {
Cons(val, next) => {
print!("{} -> ", val);
print_list(next);
}
Nil => println!("END"),
}
}
// 输出:1 -> 2 -> 3 -> END
为什么需要 Box? 没有 Box 的话,编译器不知道 List 有多大(因为它可以无限嵌套)。Box 是个固定大小的指针,编译器就知道怎么分配内存了。
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
let a = Rc::new(Cons(,
Rc::new(Cons(,
Rc::new(Nil)))));
// b 和 c 共享 a 的所有权
let b = Cons(, Rc::clone(&a));
let c = Cons(, Rc::clone(&a));
println!("计数:{}", Rc::strong_count(&a)); // 输出:3
// 可以遍历共享的列表
print_list(&b);
print_list(&c);
}
fn print_list(list: &List) {
match list {
Cons(val, next) => {
print!("{} -> ", val);
print_list(next);
}
Nil => println!("END"),
}
}
use std::sync::Arc;
use std::thread;
use std::time::Duration;
fn main() {
let data = Arc::new(vec![, , ]);
let mut handles = vec![];
for i in .. {
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("线程 {} 读取:{:?}", i, data_clone);
thread::sleep(Duration::from_millis());
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("主线程:计数 = {}", Arc::strong_count(&data));
}
struct DatabaseConnection {
url: String,
}
impl Drop for DatabaseConnection {
fn drop(&mut self) {
println!("断开数据库连接:{}", self.url);
// 实际代码里这里会真的断开连接
}
}
fn main() {
{
let db = DatabaseConnection {
url: String::from("mysql://localhost:3306"),
};
println!("数据库连接已建立");
// db 在这里离开作用域,自动断开连接
}
println!("作用域结束,连接已断开");
}
// 输出:
// 数据库连接已建立
// 断开数据库连接:mysql://localhost:3306
// 作用域结束,连接已断开
use std::rc::Rc;
use std::thread;
fn main() {
let data = Rc::new();
let data_clone = Rc::clone(&data);
let handle = thread::spawn(move || {
println!("线程里:{}", data_clone);
});
handle.join().unwrap();
}
编译器错误:
error[E0277]: `Rc<{integer}>` cannot be sent between threads safely
--> src/main.rs:7:30
|
7 | let handle = thread::spawn(move || {
| ------------- ^------
| | |
| ________________|_____within this `{closure@...}`
| | |
| | required by a bound introduced by this call
|
= help: the trait `Send` is not implemented for `Rc<{integer}>`
翻译: "Rc 不能跨线程!要用 Arc!"
// ❌ 错误选择
use std::rc::Rc;
fn share_across_threads() {
let data = Rc::new();
thread::spawn(move || {
println!("{}", data); // ❌ 编译错误!
});
}
// ✅ 正确选择
use std::sync::Arc;
fn share_across_threads() {
let data = Arc::new();
thread::spawn(move || {
println!("{}", data); // ✅ 可以
});
}
选择指南:
BoxRcArcuse std::rc::{Rc, RefCell};
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
next: RefCell<Option<Rc<Node>>>,
}
fn main() {
let a = Rc::new(Node {
value: ,
next: RefCell::new(None),
});
let b = Rc::new(Node {
value: ,
next: RefCell::new(Some(Rc::clone(&a))),
});
// ❌ 循环引用!
a.next.borrow_mut().replace(Rc::clone(&b));
println!("a 计数:{}", Rc::strong_count(&a)); // 2
println!("b 计数:{}", Rc::strong_count(&b)); // 2
// 即使离开作用域,计数也不会变成 0,内存泄漏!
}
解决方案: 用 Weak 弱引用(下篇讲内部可变性时细说)
fn main() {
let b = Box::new();
// ❌ 有些情况需要显式解引用
let x: i32 = *b; // 取出值
// ✅ 但很多情况 Rust 自动 deref
fn print_num(n: &i32) {
println!("{}", n);
}
print_num(&b); // 自动 deref
}
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct TreeNode {
value: i32,
children: RefCell<Vec<Rc<TreeNode>>>,
}
fn main() {
let root = Rc::new(TreeNode {
value: ,
children: RefCell::new(vec![]),
});
let child1 = Rc::new(TreeNode {
value: ,
children: RefCell::new(vec![]),
});
let child2 = Rc::new(TreeNode {
value: ,
children: RefCell::new(vec![]),
});
// 添加子节点
root.children.borrow_mut().push(Rc::clone(&child1));
root.children.borrow_mut().push(Rc::clone(&child2));
println!("根节点:{:?}", root);
println!("根节点有 {} 个子节点", root.children.borrow().len());
}
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// 共享计数器
let counter = Arc::new(Mutex::new());
let mut handles = vec![];
for _ in .. {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += ;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("最终计数:{}", *counter.lock().unwrap());
// 输出:最终计数:10
}

核心要点:
选择指南:
BoxRcArc下篇预告:
等等,刚才树形结构的例子里,RefCell 是个啥?为什么 Rc 还要配个 RefCell?这就涉及到 Rust 的内部可变性了——明明是不可变的,怎么又能改了?下篇咱们来揭开这个"精神分裂"的谜团!
互动问题:
你觉得智能指针和普通指针最大的区别是啥?有没有被循环引用坑过?评论区聊聊!