智能指针与内存管理
本章目标: 深入掌握 Rust 智能指针体系,理解内存管理模式
学习时间: 5-7 天
前置知识: 完成
06-Unsafe与链表实现.md
智能指针全景图
Rust 智能指针体系:
所有权模型 引用计数 内部可变性
---------- ---------- ----------
Box<T> Rc<T> Cell<T>
(堆分配) (单线程共享) (Copy类型)
Arc<T> RefCell<T>
(多线程共享) (运行时检查)
Mutex<T>
(线程安全)
特殊场景:
----------
Weak<T> Pin<T>
(弱引用,防循环) (自引用/async)
Cow<T>
(写时复制)
1. Box<T>: 堆分配
使用场景
// 场景1: 编译期未知大小的类型
enum Tree<T> {
Leaf(T),
Node(Box<Tree<T>>, Box<Tree<T>>), // 递归类型必须用 Box
}
// 场景2: 转移大对象所有权而不复制
fn process_large_data(data: Box<[u8; 1_000_000]>) {
// 只移动指针 (8字节), 而非复制100万字节
let _ = data;
}
// 场景3: Trait 对象
trait Drawable {
fn draw(&self);
}
struct Canvas {
elements: Vec<Box<dyn Drawable>>, // 运行时多态
}Box 内存布局
栈: 堆:
+-----------+ +------------------+
| Box { ptr } -----> | 数据 (T 的大小) |
+-----------+ +------------------+
Box<i32>: 栈上8字节(指针), 堆上4字节(i32)
&i32 > Box<i32> 在大多数场景无意义, 因为引用更高效
Box 与 Deref 强制转换
fn main() {
let boxed = Box::new(String::from("hello"));
// Box<String> 自动解引用为 &String -> &str
let len = boxed.len(); // String::len
// Box<T> 实现了 Deref<Target = T>
let s: &str = &*boxed;
// 显式解引用
let owned: String = *boxed; // 消费 Box, 取出 String
}Box::into_raw / from_raw
fn transfer_to_c(value: Box<String>) -> *mut String {
// 将所有权转给 C (或 unsafe 代码)
Box::into_raw(value)
}
unsafe fn reclaim_from_c(ptr: *mut String) {
// 重新接管所有权, 会在 drop 时释放
let _ = Box::from_raw(ptr);
}2. Rc<T>: 引用计数 (单线程)
工作原理
Rc<String("hello")>
栈变量 a: 堆:
+----------+ +------------------+
| Rc { ptr } -----> | strong_count: 2 |
+----------+ | weak_count: 0 |
| data: "hello" |
栈变量 b (clone): +------------------+
+----------+
| Rc { ptr } -----^
+----------+
use std::rc::Rc;
fn rc_basics() {
let a = Rc::new(String::from("hello"));
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 with c: {}", Rc::strong_count(&a)); // 3
} // c drop, 引用计数 -1
println!("Count after c dropped: {}", Rc::strong_count(&a)); // 2
// 当所有 Rc 都 drop 后, 数据才被释放
}Rc 的限制与 Rc<RefCell<T>> 模式
use std::rc::Rc;
use std::cell::RefCell;
// 共享可变状态 (单线程)
#[derive(Debug)]
struct SharedState {
value: i32,
}
fn shared_mutable() {
let state = Rc::new(RefCell::new(SharedState { value: 0 }));
let handle1 = Rc::clone(&state);
let handle2 = Rc::clone(&state);
// 通过不同的 Rc handle 修改共享状态
handle1.borrow_mut().value += 10;
handle2.borrow_mut().value += 20;
println!("Final value: {}", state.borrow().value); // 30
}避免循环引用
use std::rc::{Rc, Weak};
use std::cell::RefCell;
// 父-子关系: 父持有子 (Rc), 子弱引用父 (Weak)
struct Parent {
children: Vec<Rc<Child>>,
}
struct Child {
parent: Weak<Parent>, // 使用 Weak 打破循环
name: String,
}
fn no_cycle() {
let parent = Rc::new(Parent { children: vec![] });
let child = Rc::new(Child {
parent: Rc::downgrade(&parent), // 创建 Weak 引用
name: "child1".to_string(),
});
// 使用 Weak 时需要 upgrade
if let Some(p) = child.parent.upgrade() {
println!("Parent exists, has {} children", p.children.len());
}
// parent 和 child 都可以安全 drop
}3. Cell<T> 与 RefCell<T>: 内部可变性
Cell<T>: Copy 类型的内部可变性
use std::cell::Cell;
// Cell 通过复制来避免借用检查
struct Stats {
hit_count: Cell<u64>, // 可以通过 &self 修改
miss_count: Cell<u64>,
}
impl Stats {
fn record_hit(&self) { // &self, 不是 &mut self!
self.hit_count.set(self.hit_count.get() + 1);
}
fn record_miss(&self) {
self.miss_count.set(self.miss_count.get() + 1);
}
fn hit_rate(&self) -> f64 {
let hits = self.hit_count.get();
let total = hits + self.miss_count.get();
if total == 0 { 0.0 } else { hits as f64 / total as f64 }
}
}RefCell<T>: 运行时借用检查
use std::cell::RefCell;
fn refcell_demo() {
let data = RefCell::new(vec![1, 2, 3]);
// borrow() -> Ref<T> (实现了 Deref)
let view = data.borrow();
println!("{:?}", *view);
drop(view); // 必须释放才能再次借用
// borrow_mut() -> RefMut<T>
data.borrow_mut().push(4);
println!("{:?}", data.borrow()); // [1, 2, 3, 4]
}
// 运行时 panic 情况
fn borrow_panic() {
let data = RefCell::new(42);
let _borrow1 = data.borrow();
let _borrow2 = data.borrow(); // 可以: 多个不可变借用
// data.borrow_mut(); // panic! 已有不可变借用时不能可变借用
}
// try_borrow 避免 panic
fn safe_borrow() {
let data = RefCell::new(42);
let b1 = data.borrow();
match data.try_borrow_mut() {
Ok(_) => println!("got mut borrow"),
Err(e) => println!("borrow failed: {}", e), // 不会 panic
}
}Cell vs RefCell 对比
| 特性 | Cell<T> | RefCell<T> |
|---|---|---|
| 获取内部值 | get() (T: Copy) | borrow() |
| 修改内部值 | set() | borrow_mut() |
| 借用检查 | 不需要 (复制) | 运行时检查 |
| T 的约束 | T: Copy | 无约束 |
| 开销 | 最小 | 小 (引用计数) |
| panic 风险 | 无 | 有 |
4. Arc<T>: 原子引用计数 (多线程)
use std::sync::Arc;
use std::thread;
fn arc_basics() {
let data = Arc::new(vec![1, 2, 3, 4, 5]);
let mut handles = vec![];
for i in 0..5 {
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("Thread {}: {:?}", i, *data_clone);
});
handles.push(handle);
}
for h in handles {
h.join().unwrap();
}
}Arc<Mutex<T>>: 多线程可变状态
use std::sync::{Arc, Mutex};
use std::thread;
fn shared_counter() {
let counter = Arc::new(Mutex::new(0u64));
let handles: Vec<_> = (0..10).map(|_| {
let c = Arc::clone(&counter);
thread::spawn(move || {
let mut count = c.lock().unwrap();
*count += 1;
})
}).collect();
for h in handles {
h.join().unwrap();
}
println!("Final count: {}", *counter.lock().unwrap()); // 10
}5. Pin<T>: 自引用与异步
为什么需要 Pin?
问题: 自引用结构
struct SelfRef {
data: String,
ptr: *const String, // 指向 data
}
// 当 SelfRef 被移动时, data 的地址改变, ptr 变成悬空指针!
解决: Pin<P> 保证 P 指向的值不会被移动
use std::pin::Pin;
// Unpin: 可以安全地移动 (大多数类型)
fn unpin_example() {
let mut x = 42i32;
let mut pinned = Pin::new(&mut x); // i32: Unpin, 这是 ok 的
*pinned = 100;
}
// !Unpin: 不能移动 (async 生成的 Future 等)
use std::marker::PhantomPinned;
struct MyFuture {
data: String,
self_ptr: *const String,
_pin: PhantomPinned, // 标记为 !Unpin
}
impl MyFuture {
fn new(s: String) -> Self {
MyFuture {
data: s,
self_ptr: std::ptr::null(),
_pin: PhantomPinned,
}
}
fn init(self: Pin<&mut Self>) {
let ptr = &self.data as *const String;
// Safety: 我们保证这个结构不会被移动
unsafe { self.get_unchecked_mut().self_ptr = ptr; }
}
}Pin 与异步
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
// async fn 编译后生成实现 Future 的匿名结构
// 该结构是 !Unpin 的, 因为它可能有自引用
// Future trait 的 poll 方法接受 Pin<&mut Self>
trait MyFuture {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
// 使用 Box::pin 在堆上固定
async fn pinned_async() {
let future = async { 42 };
let pinned: Pin<Box<_>> = Box::pin(future);
let result = pinned.await;
println!("{}", result);
}6. Cow<T>: 写时复制
use std::borrow::Cow;
// Cow: Clone-on-Write
// Borrowed 时不复制, 修改时才复制
fn process_string(input: &str) -> Cow<str> {
if input.contains("bad") {
// 需要修改, 克隆为 String
Cow::Owned(input.replace("bad", "good"))
} else {
// 无需修改, 借用原始数据
Cow::Borrowed(input)
}
}
fn cow_demo() {
let s1 = process_string("hello world");
let s2 = process_string("hello bad world");
println!("s1 is_borrowed: {}", matches!(s1, Cow::Borrowed(_))); // true
println!("s2 is_owned: {}", matches!(s2, Cow::Owned(_))); // true
}
// 使用场景: 函数参数接受 &str 或 String
fn accepts_cow<'a>(s: impl Into<Cow<'a, str>>) {
let s = s.into();
println!("{}", s);
}7. 智能指针选择指南
需要堆分配或 Trait 对象?
→ Box<T>
需要共享所有权 (单线程)?
→ Rc<T>
需要内部可变性?
→ Rc<RefCell<T>>
有循环引用?
→ 弱引用用 Weak<T>
需要共享所有权 (多线程)?
→ Arc<T>
需要内部可变性?
→ Arc<Mutex<T>> 或 Arc<RwLock<T>>
需要通过 &self 修改?
→ T: Copy 用 Cell<T>
→ 其他用 RefCell<T>
需要自引用结构或 async?
→ Pin<P>
需要避免不必要的复制?
→ Cow<T>
练习题
练习 1: 实现引用计数指针
use std::cell::Cell;
// 实现简化版 Rc<T>
pub struct SimpleRc<T> {
inner: *mut RcInner<T>,
}
struct RcInner<T> {
value: T,
count: Cell<usize>,
}
impl<T> SimpleRc<T> {
pub fn new(value: T) -> Self {
let inner = Box::into_raw(Box::new(RcInner {
value,
count: Cell::new(1),
}));
SimpleRc { inner }
}
pub fn strong_count(this: &Self) -> usize {
unsafe { (*this.inner).count.get() }
}
}
impl<T> Clone for SimpleRc<T> {
fn clone(&self) -> Self {
unsafe {
let count = (*self.inner).count.get();
(*self.inner).count.set(count + 1);
}
SimpleRc { inner: self.inner }
}
}
impl<T> std::ops::Deref for SimpleRc<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &(*self.inner).value }
}
}
impl<T> Drop for SimpleRc<T> {
fn drop(&mut self) {
unsafe {
let count = (*self.inner).count.get();
if count == 1 {
drop(Box::from_raw(self.inner));
} else {
(*self.inner).count.set(count - 1);
}
}
}
}
fn main() {
let a = SimpleRc::new(42);
let b = a.clone();
println!("count = {}", SimpleRc::strong_count(&a)); // 2
println!("value = {}", *a);
drop(b);
println!("count = {}", SimpleRc::strong_count(&a)); // 1
}练习 2: 使用 Cell 实现懒加载
use std::cell::Cell;
// 使用 Cell 实现惰性计算
struct LazyValue {
computed: Cell<bool>,
value: Cell<i64>,
}
impl LazyValue {
fn new() -> Self {
LazyValue {
computed: Cell::new(false),
value: Cell::new(0),
}
}
fn get(&self) -> i64 {
if !self.computed.get() {
// 模拟昂贵计算
let result = (1..=100i64).sum();
self.value.set(result);
self.computed.set(true);
}
self.value.get()
}
}
fn main() {
let lazy = LazyValue::new();
println!("{}", lazy.get()); // 触发计算: 5050
println!("{}", lazy.get()); // 使用缓存
}练习 3: 图结构 (Rc + RefCell)
use std::rc::Rc;
use std::cell::RefCell;
use std::collections::HashMap;
type NodeId = usize;
struct GraphNode {
id: NodeId,
value: i32,
edges: Vec<Rc<RefCell<GraphNode>>>,
}
struct Graph {
nodes: HashMap<NodeId, Rc<RefCell<GraphNode>>>,
next_id: NodeId,
}
impl Graph {
fn new() -> Self {
Graph { nodes: HashMap::new(), next_id: 0 }
}
fn add_node(&mut self, value: i32) -> NodeId {
let id = self.next_id;
self.next_id += 1;
self.nodes.insert(id, Rc::new(RefCell::new(GraphNode {
id,
value,
edges: vec![],
})));
id
}
fn add_edge(&mut self, from: NodeId, to: NodeId) {
if let (Some(from_node), Some(to_node)) =
(self.nodes.get(&from).cloned(), self.nodes.get(&to).cloned())
{
from_node.borrow_mut().edges.push(to_node);
}
}
fn bfs(&self, start: NodeId) -> Vec<NodeId> {
use std::collections::VecDeque;
let mut visited = vec![];
let mut queue = VecDeque::new();
let mut seen = std::collections::HashSet::new();
if let Some(start_node) = self.nodes.get(&start) {
queue.push_back(Rc::clone(start_node));
seen.insert(start);
}
while let Some(node) = queue.pop_front() {
let node_ref = node.borrow();
visited.push(node_ref.id);
for edge in &node_ref.edges {
let edge_id = edge.borrow().id;
if seen.insert(edge_id) {
queue.push_back(Rc::clone(edge));
}
}
}
visited
}
}
fn main() {
let mut g = Graph::new();
let a = g.add_node(1);
let b = g.add_node(2);
let c = g.add_node(3);
let d = g.add_node(4);
g.add_edge(a, b);
g.add_edge(a, c);
g.add_edge(b, d);
g.add_edge(c, d);
let order = g.bfs(a);
println!("BFS order: {:?}", order);
}练习 4: 引用循环检测
use std::rc::Rc;
use std::cell::RefCell;
// 创建一个循环引用, 观察内存泄漏
fn create_cycle() {
#[derive(Debug)]
struct Node {
next: Option<Rc<RefCell<Node>>>,
}
let a = Rc::new(RefCell::new(Node { next: None }));
let b = Rc::new(RefCell::new(Node { next: Some(Rc::clone(&a)) }));
// 创建循环
a.borrow_mut().next = Some(Rc::clone(&b));
println!("a strong count = {}", Rc::strong_count(&a)); // 2
println!("b strong count = {}", Rc::strong_count(&b)); // 2
// a 和 b drop 后, 引用计数变为 1 而非 0 -> 内存泄漏!
}
// 使用 Weak 解决循环引用
fn no_cycle_with_weak() {
use std::rc::Weak;
struct Node {
next: Option<Weak<RefCell<Node>>>,
}
let a = Rc::new(RefCell::new(Node { next: None }));
let b = Rc::new(RefCell::new(Node {
next: Some(Rc::downgrade(&a)), // Weak 引用
}));
a.borrow_mut().next = Some(Rc::downgrade(&b));
println!("No cycle created!");
// a 和 b 可以正确 drop
}练习 5: Cow 使用场景
use std::borrow::Cow;
// 路径处理: 可能需要也可能不需要修改
fn normalize_path<'a>(path: &'a str) -> Cow<'a, str> {
if path.contains("//") {
Cow::Owned(path.replace("//", "/"))
} else if path.starts_with("/") {
Cow::Borrowed(path) // 不需要修改
} else {
Cow::Owned(format!("/{}", path))
}
}
fn main() {
let paths = vec![
"/home/user/file.txt",
"home/user/file.txt",
"/home//user//file.txt",
];
for path in &paths {
let normalized = normalize_path(path);
println!("{} -> {} (copied: {})", path, normalized,
matches!(normalized, Cow::Owned(_)));
}
}练习 6: 内存泄漏检测
// 演示不同情况下的内存释放
fn memory_tracking_demo() {
struct Tracker {
id: usize,
}
impl Drop for Tracker {
fn drop(&mut self) {
println!("Tracker {} dropped", self.id);
}
}
println!("=== Box ===");
{
let b = Box::new(Tracker { id: 1 });
drop(b); // 立即 drop
println!("After explicit drop");
}
println!("=== Rc (no cycle) ===");
{
let a = Rc::new(Tracker { id: 2 });
let b = Rc::clone(&a);
drop(a);
println!("a dropped, b still alive");
drop(b);
println!("b dropped");
}
println!("=== Rc (with cycle) ===");
// 这里会发生内存泄漏!
{
use std::cell::RefCell;
struct CyclicNode {
_tracker: Tracker,
next: Option<Rc<RefCell<CyclicNode>>>,
}
let a = Rc::new(RefCell::new(CyclicNode { _tracker: Tracker { id: 3 }, next: None }));
let b = Rc::new(RefCell::new(CyclicNode { _tracker: Tracker { id: 4 }, next: Some(Rc::clone(&a)) }));
a.borrow_mut().next = Some(Rc::clone(&b));
// 离开作用域, 但 Tracker 3 和 4 不会打印 "dropped"!
}
println!("After cycle scope (no drop messages above = memory leak)");
}练习 7: 自定义智能指针
// 实现一个带访问限制的智能指针
use std::cell::Cell;
pub struct Limited<T> {
value: T,
remaining: Cell<usize>,
}
impl<T> Limited<T> {
pub fn new(value: T, max_access: usize) -> Self {
Limited { value, remaining: Cell::new(max_access) }
}
pub fn get(&self) -> Option<&T> {
let remaining = self.remaining.get();
if remaining == 0 {
None
} else {
self.remaining.set(remaining - 1);
Some(&self.value)
}
}
pub fn remaining(&self) -> usize {
self.remaining.get()
}
}
fn main() {
let secret = Limited::new("secret_key", 3);
for _ in 0..5 {
match secret.get() {
Some(v) => println!("Got: {} (remaining: {})", v, secret.remaining()),
None => println!("Access denied!"),
}
}
}练习 8: 综合 - 基于 Rc 的 Observable 模式
use std::rc::Rc;
use std::cell::RefCell;
type Callback<T> = Box<dyn Fn(&T)>;
pub struct Observable<T> {
value: T,
observers: Vec<Callback<T>>,
}
impl<T: Clone> Observable<T> {
pub fn new(initial: T) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Observable {
value: initial,
observers: vec![],
}))
}
pub fn subscribe(&mut self, callback: impl Fn(&T) + 'static) {
self.observers.push(Box::new(callback));
}
pub fn set(&mut self, new_value: T) {
self.value = new_value;
for observer in &self.observers {
observer(&self.value);
}
}
pub fn get(&self) -> &T {
&self.value
}
}
fn main() {
let counter = Observable::new(0i32);
counter.borrow_mut().subscribe(|v| println!("Observer 1: {}", v));
counter.borrow_mut().subscribe(|v| println!("Observer 2: value doubled = {}", v * 2));
counter.borrow_mut().set(10);
counter.borrow_mut().set(20);
println!("Final: {}", counter.borrow().get());
}标准库源码导读
Rc<T> 的实现
// 简化的 Rc 内部结构
// 来源: std/src/rc.rs
struct RcBox<T: ?Sized> {
strong: Cell<usize>,
weak: Cell<usize>,
value: T,
}
pub struct Rc<T: ?Sized> {
ptr: NonNull<RcBox<T>>,
phantom: PhantomData<RcBox<T>>,
}关键要学习的方法:
Rc::clone()- 如何增加引用计数Drop for Rc<T>- 如何减少计数并决定是否释放Rc::downgrade()- 如何创建 Weak 引用
小结
| 类型 | 适用场景 | 线程安全 | 可变性 | 开销 |
|---|---|---|---|---|
Box<T> | 堆分配/递归类型 | ✅ (T: Send) | 正常 | 零开销 |
Rc<T> | 共享所有权 | ❌ 单线程 | 不可变 | 小 |
Arc<T> | 共享所有权 | ✅ 多线程 | 不可变 | 原子操作 |
Cell<T> | 内部可变 | ❌ 单线程 | Copy类型 | 最小 |
RefCell<T> | 内部可变 | ❌ 单线程 | 任意类型 | 运行时检查 |
Mutex<T> | 互斥访问 | ✅ 多线程 | 任意类型 | 锁开销 |
Weak<T> | 弱引用 | 同 Rc/Arc | 不可变 | 小 |
Pin<T> | 防止移动 | 取决于T | 正常 | 零开销 |
Cow<T> | 写时复制 | ✅ | 写时获取 | 条件克隆 |
下一步
- 完成所有 8 道练习题
- 理解为什么
Rc<RefCell<T>>常见但有局限性 - 阅读
08-宏系统与元编程.md
理解智能指针, 你就理解了 Rust 内存管理的灵魂! 🦀