从 0 掌握 Rust Box 智能指针

53 阅读2分钟

欢迎关注公众号 猩猩程序员

wechat_2025-08-14_125617_342.png

1.什么是 Box?

  • Box 是“在堆上分配内存”的智能指针:值在堆上,栈上保存固定大小的指针。
  • 典型使用场景:
    • 递归类型(编译期大小无法确定)
    • 动态分发的 trait 对象(Box)
    • 将大对象放到堆上,降低移动时的栈拷贝成本
  • 内存与语义:
    • 实现 Drop:离开作用域自动释放堆内存
    • 实现 Deref:可像引用一样解引用访问内部值

2.基础用法:Box::new 与解引用

示例 1:在堆上分配并使用

fn main() {
    // 在堆上分配一个 i32 值 5
    let b = Box::new(5);

    // 自动解引用(Deref),像引用一样使用
    println!("b = {}", b);

    // 作用域结束时,Box 释放其堆上内存
}

3.递归类型:用 Box 打破无限大小

示例 2:链表(Cons List)

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    // 构建: 1 -> 2 -> 3 -> Nil
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
    // 递归位置使用 Box<List>(固定大小的指针),编译器可计算枚举大小
}

4.Trait 对象:Box 动态分发

示例 3:GUI 组件的 draw 行为

trait Draw {
    fn draw(&self);
}

struct Button { label: String }
struct SelectBox { options: Vec<String> }

impl Draw for Button {
    fn draw(&self) {
        println!("绘制按钮: {}", self.label);
    }
}
impl Draw for SelectBox {
    fn draw(&self) {
        println!("绘制选择框: {:?}", self.options);
    }
}

fn main() {
    // 不同具体类型,统一以 Box<dyn Draw> 保存
    let components: Vec<Box<dyn Draw>> = vec![
        Box::new(Button { label: "登录".into() }),
        Box::new(SelectBox { options: vec!["是".into(), "否".into()] }),
    ];

    for c in components {
        c.draw(); // 动态分发
    }
}

5.向下转型:Box::downcast

  • 适用于“装箱任意类型,再尝试恢复为具体类型”的场景。
  • 成功:Ok(Box);失败:Err(Box)。

示例 4:判定是否为 String

use std::any::Any;

fn print_if_string(value: Box<dyn Any>) {
    if let Ok(string) = value.downcast::<String>() {
        println!("是字符串: {}", string);
    } else {
        println!("不是字符串");
    }
}

fn main() {
    print_if_string(Box::new("Hello Rust".to_string()));
    print_if_string(Box::new(42i32));
}

6.与 Pin 结合:Box::pin

  • 构造 Pin<Box>;若 T: !Unpin,则值的内存位置被固定,不能再移动。

示例 5:固定一个不应被移动的类型

use std::pin::Pin;

struct NotUnpin {
    data: String,
}

fn main() {
    let pinned: Pin<Box<NotUnpin>> = Box::pin(NotUnpin { data: "fixed".into() });
    // 若 NotUnpin 不实现 Unpin,则 pinned 中的数据位置保持不变
}

7.延迟初始化:Box::::new_uninit

  • new_uninit 构造未初始化的 Box,需写入后再 assume_init 得到 Box。
  • 必须确保确实完成了正确初始化,否则为未定义行为(unsafe)。

示例 6:延迟初始化

fn main() {
    let mut boxed = Box::<u32>::new_uninit();
    // 延迟初始化
    boxed.write(5);
    let boxed: Box<u32> = unsafe { boxed.assume_init() };
    assert_eq!(*boxed, 5);
}

8.所有权、移动与克隆

  • 所有权:Box 遵循所有权语义,move 后旧变量不可再用。
  • 可变性:修改内部值需要可变借用(let mut 或 &mut)。
  • 克隆:Box 的 Clone 会分配新内存并克隆内部值(T: Clone),相当于“深拷贝”;若需共享所有权请用 Rc/Arc(不在本节展开)。

欢迎关注公众号 猩猩程序员