Rust - 智能指针
Rust - 智能指针
Section titled “Rust - 智能指针”在 Rust 中,值默认分配在**栈(stack)上,这种方式快速高效。然而,有时我们需要将数据分配在堆(heap)**上。这对于编译时大小未知的数据、无需昂贵复制即可转移大量数据的所有权,或者创建递归数据结构是必需的。智能指针(Smart pointers)是一种行为类似于指针但还包含额外元数据和功能的数据结构,例如管理它们指向的内存。
所有智能指针都实现了 Deref 和 Drop trait(特性),这赋予了它们超越普通 struct(结构体)的特殊能力。
| Trait | 包 & 描述 |
|---|---|
| Deref | std::ops::Deref允许智能指针 struct 的实例像引用一样被对待。这使得 *my_box 这样的代码能够访问内部数据。 |
| Drop | std::ops::Drop允许你自定义当智能指针实例超出作用域时运行的代码。这是 Rust RAII(资源获取即初始化)模式的基础,确保自动内存释放和资源清理。 |
本章将探讨 Box<T>(最简单的智能指针),然后简要介绍其他关键指针,如 Rc<T> 和 RefCell<T>。
Box 智能指针
Section titled “Box 智能指针”Box<T> 智能指针,通常简称为“box”,是在堆上分配值最直接的方式。当你使用 Box<T> 时,指针本身存储在栈上,而它指向的数据 (T) 则位于堆上。Box<T> 除了初始的堆分配外,没有其他性能开销。
何时使用 Box
Section titled “何时使用 Box”- 递归类型:对于定义中引用自身的类型,如链表。编译器需要在编译时知道类型的大小,而
Box<T>通过将递归部分存储在堆上(指针具有已知固定大小)来打破无限递归。 - 大数据量:为了在不复制数据的情况下转移大量数据的所有权。在栈上移动
Box<T>的开销很小,因为只移动了指针。 - Trait 对象:在运行时确定其类型(动态分发)的情况下使用值,这将在高级主题中介绍。
示例:在堆上存储值
Section titled “示例:在堆上存储值”以下是如何使用 Box 在堆上存储一个 i32 类型的值。变量 b 位于栈上,并持有堆上值 5 的地址。
fn main() { // 这个值在栈上 let stack_var: i32 = 5;
// 这个值分配在堆上 // 'b' 是栈上的一个 Box<i32>,指向堆上的数据 let b = Box::new(stack_var);
println!("b = {}", b);
// 要访问 box 内部的数据,我们必须对其进行解引用 assert_eq!(5, *b); println!("The value inside the box is: {}", *b);}// 当 'b' 在 main 函数结束时超出作用域,// Drop trait 被调用,从堆中释放 box 的内存。b = 5The value inside the box is: 5实现自己的智能指针
Section titled “实现自己的智能指针”为了更好地理解 Box<T>,我们来创建自己的一个简单版本,名为 MyBox<T>。这将演示 Deref 和 Drop trait 是如何工作的。
实现 Deref Trait
Section titled “实现 Deref Trait”Deref trait 允许我们自定义解引用运算符 (*) 的行为。通过实现 Deref,我们可以编写 *y,让 Rust 将其转换为 *(y.deref())。
use std::ops::Deref;
// 一个名为 MyBox 的泛型元组结构体struct MyBox<T>(T);
impl<T> MyBox<T> { // 一个类似构造函数的功能,用于创建新实例 fn new(x: T) -> MyBox<T> { MyBox(x) }}
// 为 MyBox<T> 实现 Deref traitimpl<T> Deref for MyBox<T> { // 关联类型,指定 `*` 将返回一个什么类型的引用 type Target = T;
fn deref(&self) -> &Self::Target { // 我们返回元组结构体内部数据的引用 &self.0 }}
fn main() { let x = 5; let y = MyBox::new(x);
assert_eq!(5, x); // 在幕后,Rust 正在运行:*(y.deref()) assert_eq!(5, *y);
println!("Successfully dereferenced MyBox!");}实现 Drop Trait
Section titled “实现 Drop Trait”Drop trait 对于 RAII 至关重要。drop 方法中的代码会在值超出作用域时自动运行。你很少需要自己实现 Drop,因为 Rust 会为标准类型处理内存释放。然而,对于管理文件句柄或网络连接等其他资源,它至关重要。
struct CustomSmartPointer { data: String,}
// 实现 Drop traitimpl 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."); // 在 main 函数结束时,'d' 先被丢弃,然后是 'c'(按创建的相反顺序)。}CustomSmartPointers created.Dropping CustomSmartPointer with data `other stuff`!Dropping CustomSmartPointer with data `my stuff`!适用于不同场景的其他智能指针
Section titled “适用于不同场景的其他智能指针”Box<T> 用于单一所有权。Rust 的标准库为更复杂的场景提供了其他智能指针:
Rc<T>(引用计数): 当一个值需要有多个所有者时使用。Rc<T>跟踪对一个值的引用数量。只有当最后一个引用超出作用域时,数据才从堆中释放。它适用于单线程场景。RefCell<T>(内部可变性): 允许你在存在不可变引用的情况下修改数据。这种模式在运行时而非编译时强制执行 Rust 的借用规则。违反规则(例如,同时有两个可变借用)将导致panic!。
结合使用 Rc<T> 和 RefCell<T> 是一种常见模式,用于创建图或其他复杂数据结构,在这些结构中,一个值由多个其他值拥有,并且可能需要被修改。