智能指针与内存管理

本章目标: 深入掌握 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 内存管理的灵魂! 🦀