[rust]Box智能指针

0 阅读4分钟

定义

  1. 智能指针是一类数据结构,它们的表现类似于指针,但拥有额外的元数据,比如,它们拥有一个引用计数器,该计数记录智能指针总共有多少个所有者,当没有任何所有者时清除数据
  2. 智能指针通常使用结构体实现,区别于常规结构体的点在于其实现了Defer traitDrop trait
  3. 普通引用只是借用数据的指针(没有所有权),而智能指针拥有它们指向的数据(有所有权)

Defer trait允许智能指针结构体实例表现得像引用一样,这样就可以编写即为引用,用于智能指针的代码。还允许我们自定义当智能指针离开作用域时执行的代码。

Box

fn main() {
    let b = Box::new(5); // b 存储在栈上,5 存储在堆上,b指向5所在的内存
    println!("{}",b)
}

Box 是最简单的智能指针,类型为Box<T>,允许将值放在堆上而非栈上,留在栈上的则是指向堆数据的指针。除了数据被存储在堆上,Box没有任何损失

Box适用场景如下:

  1. 当有一个在编译时未知大小的数据,而又需要在确切大小的上下文中使用这个类型值的时候
  2. 当有大量数据并希望在确保数据不被拷贝的基础上转移所有权的时候
  3. 当希望拥有一个值且只关心它的类型是否实现了特定trait而不是某个具体类型的时候

错误示例

enum List {
    Next(i32, List),
    Nil,
}

fn main() {
    use List::Next;
    // 定义一个链表: 1 -> 2 -> 3 -> Nil
    let list = Next(1, Next(2, Next(3, Nil)));
}

编译时报错:由于List定义存在递归,编译器无法确定其内存大小

error[E0072]: recursive type `List` has infinite size
  --> src/main.rs:66:1
   |
66 | enum List {
   | ^^^^^^^^^
67 |     Next(i32, List),
   |               ---- recursive without indirection
   |
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
   |
67 |     Next(i32, Box<List>),
   |               ++++    +

正确示例:Box<T>是一个指针,指针的内存大小是可以确定的

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

fn main() {
    use List::Next; // use还可以直接在函数中使用
    // 定义一个链表: 1 -> 2 -> 3 -> Nil
    let list = Next(1, Box::new(Next(2, Box::new(Next(3, Box::new(Nil))))));
}

Deref/DerefMut自动解引用

fn main() {
    let x = 5; // 内存分配在栈上
    let y = &x;
    assert_eq!(x,5);
    assert_eq!(*y,5); // 解引用,x类型必须实现Deref trait

    let z = Box::new(x); // x传入之后,内存在堆上还是栈上?
    assert_eq!(*z,5) 解引用
}

对于第7行代码:由于int32具有 copy trait,所以x的值被copy了一份到堆上

Rust提供了两个trait来支持智能指针的解引用

  • Derfer:允许将智能指针看作它指向的值
  • DeferMut:允许将可变智能指针看作指向它的值

下文以Deref为例进行说明

重载解引用运算符

Deref trait允许我们重载解引用运算符,主要用于*操作符的自动解引用

// 自己实现一个Box
struct MyBox<T>(T); // 元组结构体的定义方式

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

// 实现Deref特征,自定义解引用的标准使用
impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0 // 元组属性的访问方式:第一个元素,所以序号为0,如果有第二个元素,则序号为1
    }
}

fn main() {
    let x = 5;
    let y = MyBox(x);
    assert_eq!(5, x);
    assert_eq!(5, *y);
}

自动解引用

承接上文

fn main() {
    let my_box = MyBox::new(String::from("rust"));
    let x: &str = &my_box;
    hello(x); // hello, rust
}

fn hello(name: &str) {
    println!("hello, {}", name) // hello, rust
}

解引用多态与可变性交互方式:当函数需要某类型的引用时,Rust会自动区分可变和不可变性,从而应用DerferDerferMut,使实际传入的参数类型和所需的类型匹配

自动解引用的过程详解:

  1. 创建了一个MyBox实例,my_box指向一个MyBox实例,其中包含一个String类型的值
let my_box = MyBox::new(String::from("rust"));
  1. 尝试获取获取&str引用
let x: &str = &my_box;
  1. 应用Deref trait:Deref trait的deref方法将&MyBox自动解引用为&String
impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
  1. 再次应用Deref trait:现在我们有了一个&String。但是,我们需要一个&str。幸运的是,标准库中String类型也实现了Deref trait,其中Targetstr,因此,Rust会再次应用Deref trait,将&String自动解引用为&str
impl Deref for String {
    type Target = str;

    fn deref(&self) -> &str {
        &self[..]
    }
}

小结,整个自动解引用的过程如下:

  1. &MyBox通过MyBoxDeref实现被自动转换为一个&String
  2. &String通过StringDeref实现被自动转换为一个&str

因为Rust允许对解引用的连锁应用,所以这些转换在一个步骤内完成,使得最终结果是将&MyBox转换为&str