跳至内容

智能指针

这篇讲 Rust 的智能指针——Box<T>Rc<T>Arc<T>RefCell<T>。当所有权系统的基础规则不够用时,这些类型提供了灵活性——但每种都有明确的语义和代价。

Box<T>——堆分配

Box<T> 将数据分配到堆上,返回指向它的指针。主要用途:

存储编译时大小未知的类型

fn main() {
    // 递归类型——编译器不知道 List 有多大
    // 用 Box 打破"无限大小"的循环
    enum List {
        Cons(i32, Box<List>),
        Nil,
    }

    use List::{Cons, Nil};

    let list = Cons(1,
        Box::new(Cons(2,
            Box::new(Cons(3,
                Box::new(Nil))))));
}

保存实现了某个 trait 的类型(trait 对象)

trait Draw {
    fn draw(&self);
}

struct Button {
    label: String,
}

impl Draw for Button {
    fn draw(&self) {
        println!("[Button] {}", self.label);
    }
}

struct TextBox {
    content: String,
}

impl Draw for TextBox {
    fn draw(&self) {
        println!("[TextBox] {}", self.content);
    }
}

fn main() {
    // Box<dyn Draw> 可以存储任何实现了 Draw 的类型
    let components: Vec<Box<dyn Draw>> = vec![
        Box::new(Button { label: "OK".into() }),
        Box::new(TextBox { content: "Hello".into() }),
    ];

    for component in components {
        component.draw();
    }
}

Rc<T>——引用计数(单线程)

Rc<T> 允许多个所有者共享同一份数据。只在单线程中使用:

use std::rc::Rc;

fn main() {
    let a = Rc::new(vec![1, 2, 3]);
    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 after creating c = {}", Rc::strong_count(&a));  // 3
    }  // c 离开作用域,计数减 1

    println!("count after c goes out = {}", Rc::strong_count(&a));  // 2

    println!("a: {:?}, b: {:?}", a, b);

    // Rc 只提供不可变访问。要和 RefCell 配合才能修改数据
}
Rc<T> 不是线程安全的——它使用非原子计数来提升性能。跨线程共享请用 Arc<T>。编译器会拒绝你在线程中使用 Rc<T>,因为 Rc<T> 没有实现 Send

Arc<T>——原子引用计数(多线程)

Arc<T>Rc<T> 的线程安全版本(A = Atomic)。前面并发章节已经用过:

use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3, 4, 5]);
    let mut handles = vec![];

    for i in 0..3 {
        let data = Arc::clone(&data);
        let handle = thread::spawn(move || {
            println!("线程 {i} 看到了: {:?}", data);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

RefCell<T>——内部可变性

RefCell<T> 允许在拥有不可变引用时修改内部数据——将借用规则的检查从编译期推迟到运行时

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    {
        let mut v = data.borrow_mut();  // 可变借用
        *v += 1;
    }  // 可变借用在此结束

    println!("data = {}", data.borrow());  // 不可变借用 → 6
}

违反借用规则会在运行时 panic:

fn main() {
    let data = RefCell::new(5);

    let r1 = data.borrow();       // 不可变借用
    let r2 = data.borrow_mut();   // ❌ 运行时 panic!已经有一个不可变借用

    println!("{r1}");
}

组合使用

常见组合模式:

组合用途
Rc<RefCell<T>>单线程下多个所有者的可变数据
Arc<Mutex<T>>多线程下安全的可变共享
Arc<RwLock<T>>多读少写场景(读写锁)
use std::rc::Rc;
use std::cell::RefCell;

// 单线程场景:多个所有者 + 可修改
#[derive(Debug)]
struct Node {
    value: i32,
    children: Vec<Rc<RefCell<Node>>>,
}

fn main() {
    let leaf = Rc::new(RefCell::new(Node {
        value: 3,
        children: vec![],
    }));

    let branch = Rc::new(RefCell::new(Node {
        value: 5,
        children: vec![Rc::clone(&leaf)],
    }));

    // 修改 leaf 的 value
    leaf.borrow_mut().value = 10;

    println!("branch: {:?}", branch);
}

Deref 和 Drop Trait

智能指针通过 Deref trait 实现了 * 解引用操作,让智能指针可以像普通引用一样使用:

fn main() {
    let x = Box::new(5);
    // *x 能工作是因为 Box<T> 实现了 Deref<Target=T>
    println!("{}", *x);  // 5

    // 隐式 Deref 强制转换
    let s = Box::new(String::from("hello"));
    // &s → &Box<String> → &String → &str(自动转换链)
    print_str(&s);
}

fn print_str(s: &str) {
    println!("{s}");
}

Drop trait 定义了值离开作用域时的清理行为:

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer { data: String::from("my stuff") };
    let d = CustomSmartPointer { data: String::from("other stuff") };
    println!("CustomSmartPointers created.");
}  // d 先 drop,c 后 drop(栈的逆序)

一句话小结

  • Box<T>:堆分配,trait 对象,递归类型
  • Rc<T>:单线程引用计数,多个所有者
  • Arc<T>:多线程引用计数(配合 Mutex 使用)
  • RefCell<T>:运行时借用检查,内部可变性
  • Deref:让智能指针像引用一样使用
  • Drop:离开作用域时自动清理

Rust 进阶系列到此完结。从基础语法到所有权系统,从 Web 框架到并发异步,你已经掌握了 Rust 的核心能力栈。回到 Rust 基础 复习,或者开始用 Rust 写你的下一个项目吧。

练习

  1. 实现一个简单的二叉树,节点用 Box<Node> 表示左右子树。
  2. Rc<RefCell<i32>> 实现两个变量共享同一份计数器数据,分别递增后打印。
参考答案
// 1. 二叉树
#[derive(Debug)]
struct TreeNode {
    value: i32,
    left: Option<Box<TreeNode>>,
    right: Option<Box<TreeNode>>,
}

impl TreeNode {
    fn new(value: i32) -> Self {
        TreeNode { value, left: None, right: None }
    }

    fn insert(&mut self, value: i32) {
        if value < self.value {
            match &mut self.left {
                Some(node) => node.insert(value),
                None => self.left = Some(Box::new(TreeNode::new(value))),
            }
        } else {
            match &mut self.right {
                Some(node) => node.insert(value),
                None => self.right = Some(Box::new(TreeNode::new(value))),
            }
        }
    }
}

// 2. 共享计数器
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let counter = Rc::new(RefCell::new(0));

    let c1 = Rc::clone(&counter);
    let c2 = Rc::clone(&counter);

    *c1.borrow_mut() += 10;
    *c2.borrow_mut() += 20;

    println!("counter = {}", counter.borrow());  // 30
}
最后更新于