除了你列出的这些,Rust 标准库中还有一些常用或重要的指针/封装类型,它们常与智能指针一起出现,或在特定场景下替代智能指针的功能:
- 裸指针
*const T/*mut T:无所有权、无生命周期管理的原始指针,用于与 C 交互、手写数据结构、避免借用检查器限制等场合。通常在unsafe块中使用。 -
NonNull<T>:保证非空的裸指针封装,用于实现更安全的集合和容器,很多标准库结构内部使用它。 MaybeUninit<T>:用于延迟初始化或处理未初始化内存,配合Box::new_uninit或手动初始化,常用于写 unsafe 数据结构或减少初始化开销。UnsafeCell<T>:所有内部可变性的基础原语,Cell、RefCell、Mutex等底层都依赖它,一般不直接使用,除非实现自定义同步原语。OnceCell<T>/OnceLock<T>:一次性初始化单元格,写入后只能读取,OnceLock是线程安全版本(类似Mutex但只写一次),常用于延迟初始化全局配置或常量。LazyCell<T>/LazyLock<T>:在OnceCell基础上封装了闭包,首次访问时自动计算并缓存值,LazyLock用于多线程,适合静态的延迟初始化复杂对象。ManuallyDrop<T>:阻止编译器自动调用Drop,用于需要手动控制资源释放的场景,或实现更复杂的析构逻辑。
这些类型与已列出的智能指针经常组合使用,形成适应不同所有权和并发需求的模式。下面是常见组合方式及适用环境:
| 组合模式 | 适用环境 | 典型场景 |
|---|---|---|
Rc<RefCell<T>> |
单线程,多个所有者需要共享可变数据 | 图、树结构,GUI 中的共享状态,事件处理 |
Arc<Mutex<T>> |
多线程,共享可变数据,互斥访问 | 线程间共享计数器、缓存、任务队列 |
Arc<RwLock<T>> |
多线程,读多写少 | 缓存、配置、无锁数据结构 |
Rc<Vec<RefCell<T>>> 或 Rc<RefCell<Vec<...>>> |
单线程,共享可变集合 | 树的多子节点、链表节点管理 |
Weak<T> 配合 Rc/Arc |
打破循环引用,避免内存泄漏 | 树结构中父节点指向子节点用 Rc,子节点指回父节点用 Weak;观察者模式 |
Pin<Box<T>> 或 Pin<Arc<T>> |
需要固定内存地址,不可移动 | 异步任务(Future)、自引用结构体 |
Cow<'_, T> |
读多写少,需要避免不必要的克隆 | 字符串处理、配置解析、序列化 |
Box<dyn Trait> |
运行时多态,类型擦除 | 插件系统、异构集合、动态分发 |
Cell<T> 用于简单 Copy 类型 |
单线程,只需要整体替换值 | 计数器、标志位、简单状态 |
OnceLock<T> 或 LazyLock<T> 配合 Arc |
线程安全的全局延迟初始化 | 静态配置、全局缓存、单例 |
MaybeUninit<T> 与 Box 组合 |
延迟初始化堆上数据,避免重复初始化 | 实现 Vec 等容器,或需要分批填充的大数组 |
ManuallyDrop<T> 与 Box 组合 |
手动控制析构时机 | 共享内存、外部资源管理、实现自定义 Drop |
选择建议:
- 单线程只读共享 →
Rc<T> - 单线程共享可变 →
Rc<RefCell<T>> - 多线程只读共享 →
Arc<T> - 多线程共享可变 →
Arc<Mutex<T>>(需要互斥)或Arc<RwLock<T>>(读多写少) - 需要固定地址 → 结合
Pin - 需要打破循环引用 → 引入
Weak - 需要写时复制 →
Cow - 需要一次性初始化 →
OnceCell/OnceLock/LazyLock
这些组合覆盖了 Rust 中绝大多数所有权、可变性和并发场景,你可以根据实际需求灵活选用。