Rust 智能指针
`Box`、`Rc`、`RefCell`、`Weak`,以及内部可变性。
Box
fn main() {
let b = Box::new(5);
println!("{}", b);
println!("{}", *b); // 解引用
// 递归类型必须用 Box(否则大小无限)
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
println!("{:?}", list);
}5
5
Cons(1, Cons(2, Nil))enum List { Cons(i32, List) } fails: the compiler cannot size List. With Box the size is known (one pointer) — the one use of Box with no substitute.
Rc
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("共享数据"));
println!("计数 {}", Rc::strong_count(&a));
let b = Rc::clone(&a); // 只增计数,不复制数据
println!("计数 {}", Rc::strong_count(&a));
{
let c = Rc::clone(&a);
println!("计数 {}", Rc::strong_count(&a));
}
println!("计数 {}", Rc::strong_count(&a));
}计数 1
计数 2
计数 3
计数 2Rc::clone does not copy data, it increments the count — unlike a.clone() on a String, which deep-copies. The Rc:: prefix exists to signal this clone is cheap.
RefCell
use std::cell::RefCell;
fn main() {
let c = RefCell::new(5);
*c.borrow_mut() += 1;
println!("{}", c.borrow());
// 运行时检查借用规则
let b1 = c.borrow();
let b2 = c.borrow();
println!("{} {}", b1, b2);
drop(b1);
drop(b2);
// let m = c.borrow_mut(); // 已有不可变借用时会 panic
}6
6 6RefCell moves borrow checking from compile time to runtime: violations panic instead of failing the build. It enables mutating through an immutable reference — interior mutability.
RefCell panics at runtime, possibly in production. Use it only when the compiler genuinely cannot prove what you know is safe, and keep borrow() scopes tiny.Rc<RefCell<T>>
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
struct Node {
value: i32,
children: Vec<Rc<RefCell<Node>>>,
}
fn main() {
let leaf = Rc::new(RefCell::new(Node { value: 2, children: vec![] }));
let root = Rc::new(RefCell::new(Node {
value: 1,
children: vec![Rc::clone(&leaf)],
}));
root.borrow_mut().children.push(Rc::new(RefCell::new(
Node { value: 3, children: vec![] },
)));
println!("{}", root.borrow().children.len());
println!("{}", leaf.borrow().value);
}2
2Rc<RefCell<T>> gives multiple owners plus mutation on one thread — common for graphs, trees and observers. It is **not Send**; use Arc<Mutex<T>> across threads.
Weak 打破循环引用
use std::cell::RefCell;
use std::rc::{Rc, Weak};
struct Node {
name: String,
parent: RefCell<Weak<Node>>, // 弱引用,不增计数
}
fn main() {
let parent = Rc::new(Node {
name: "父".into(),
parent: RefCell::new(Weak::new()),
});
let child = Rc::new(Node {
name: "子".into(),
parent: RefCell::new(Rc::downgrade(&parent)),
});
println!("父计数 {}", Rc::strong_count(&parent));
println!("{}", child.parent.borrow().upgrade().unwrap().name);
}父计数 1
父Two Rcs pointing at each other never reach zero and leak. Make the back edge a Weak (from Rc::downgrade) so it does not count as a strong reference and the cycle breaks.
选哪个
| Need | Use |
|---|---|
| 只是放到堆上 | Box<T> |
| 多个所有者,只读(单线程) | Rc<T> |
| 多个所有者 + 可改(单线程) | Rc<RefCell<T>> |
| 多个所有者(多线程) | Arc<T> |
| 多个所有者 + 可改(多线程) | Arc<Mutex<T>> |
| 避免循环引用 | Weak<T> |
Box
fn main() {
let b = Box::new(5);
println!("{}", b);
println!("{}", *b); // 解引用
// 递归类型必须用 Box(否则大小无限)
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
println!("{:?}", list);
}5
5
Cons(1, Cons(2, Nil))enum List { Cons(i32, List) } 编译不过:编译器算不出 List 的大小。加上 Box 后大小就确定了(一个指针)。这是 Box 最不可替代的用途。
Rc
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("共享数据"));
println!("计数 {}", Rc::strong_count(&a));
let b = Rc::clone(&a); // 只增计数,不复制数据
println!("计数 {}", Rc::strong_count(&a));
{
let c = Rc::clone(&a);
println!("计数 {}", Rc::strong_count(&a));
}
println!("计数 {}", Rc::strong_count(&a));
}计数 1
计数 2
计数 3
计数 2Rc::clone 不复制数据,只是把计数加一。这和 a.clone()(对 String 是深拷贝)完全不同——名字里带 Rc:: 就是为了提醒读者这次克隆很便宜。
RefCell
use std::cell::RefCell;
fn main() {
let c = RefCell::new(5);
*c.borrow_mut() += 1;
println!("{}", c.borrow());
// 运行时检查借用规则
let b1 = c.borrow();
let b2 = c.borrow();
println!("{} {}", b1, b2);
drop(b1);
drop(b2);
// let m = c.borrow_mut(); // 已有不可变借用时会 panic
}6
6 6RefCell 把借用检查从编译期挪到运行时:违反规则时 panic 而不是编译失败。它让「不可变引用内部也能改数据」成为可能——这叫内部可变性。
RefCell 的 panic 发生在运行时,可能上线才发现。只在「编译期确实证明不了但逻辑上安全」时用它,并且把 borrow() 的作用域压到最小。Rc<RefCell<T>>
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
struct Node {
value: i32,
children: Vec<Rc<RefCell<Node>>>,
}
fn main() {
let leaf = Rc::new(RefCell::new(Node { value: 2, children: vec![] }));
let root = Rc::new(RefCell::new(Node {
value: 1,
children: vec![Rc::clone(&leaf)],
}));
root.borrow_mut().children.push(Rc::new(RefCell::new(
Node { value: 3, children: vec![] },
)));
println!("{}", root.borrow().children.len());
println!("{}", leaf.borrow().value);
}2
2Rc<RefCell<T>> 是单线程下「多个所有者 + 可修改」的组合。它在图、树、观察者模式里很常见,但**不是 Send**——多线程要用 Arc<Mutex<T>>。
Weak 打破循环引用
use std::cell::RefCell;
use std::rc::{Rc, Weak};
struct Node {
name: String,
parent: RefCell<Weak<Node>>, // 弱引用,不增计数
}
fn main() {
let parent = Rc::new(Node {
name: "父".into(),
parent: RefCell::new(Weak::new()),
});
let child = Rc::new(Node {
name: "子".into(),
parent: RefCell::new(Rc::downgrade(&parent)),
});
println!("父计数 {}", Rc::strong_count(&parent));
println!("{}", child.parent.borrow().upgrade().unwrap().name);
}父计数 1
父两个 Rc 互相持有会让计数永远不归零,内存泄漏。把「反向边」改成 Weak(Rc::downgrade 得到),就不会增加强引用计数,循环被打断。
选哪个
| 需求 | 用 |
|---|---|
| 只是放到堆上 | Box<T> |
| 多个所有者,只读(单线程) | Rc<T> |
| 多个所有者 + 可改(单线程) | Rc<RefCell<T>> |
| 多个所有者(多线程) | Arc<T> |
| 多个所有者 + 可改(多线程) | Arc<Mutex<T>> |
| 避免循环引用 | Weak<T> |