相关概念

  • 指针:一个变量在内存中包含的是一个地址(指向其他数据)
  • Rust中最常用的指针就是引用(&

智能指针

智能指针是一种数据结构

  • 行为和指针相似
  • 但是有额外的元数据和功能

与引用的不同

引用 智能指针
只借用数据 很多时候都拥有他所指向的数据

例子

  • StringVec<T>
  • 都拥有一片内存区域,且允许用户对其操作
  • 还拥有元数据(例如容量等)
  • 提供额外的功能保障(string会保障所有字符都符合utf-8编码规范)

实现

智能指针通常使用struct实现,并且实现了DerefDrop 这两个trait

  • Deref trait:允许智能指针struct的实例像引用一样使用
  • Drop trait:允许自定义当智能指针实例走出作用域时的代码

标准库中的智能指针

  • Box<T>: 在heap中分配值
  • Rc<T>:启用多重所有权的引用计数类型
  • Ref<T>RefMut<T>,通过RefCell<T>访问: 在运行时而不是编译时强制借用规则的类型

Box

Box是最简单的智能指针:

  • 允许在heap中存储数据(而不是stack
  • stack上是指向heap数据的指针
  • 没有额外的功能和性能开销

    实现了Deref traitDrop trait

image.png

使用场景

  1. 在编译时,某类型的大小无法确定。但使用该类型时,上下文却需要知道它的确切大小
  2. 当你有大量数据,想移交所有权,但需要确保在操作时数据不会被复制
  3. 使用某个值时,你只关心他是否实现了特定的trait,而不关心它的具体类型

    使用方法

    1. fn main() {
    2. let val = Box::new(12);
    3. println!("{}", val);
    4. }

    使用Box赋能递归类型

  • 在编译时,Rust需要知道一个类型所占的空间大小
  • 而递归的类型大小无法在编译时确定

image.png
上面这幅图就是一个递归的示例,当Cons无限递归下去的时候,每个i32类型的数据是确定的,但是由于不知道需要递归多少层,所以就会导致无法确定这个递归类型需要多少的内存空间。

这时就需要Box来赋能这种递归类型。

  1. use crate::List::{Cons, Nil};
  2. fn main() {
  3. let list = Cons(1,
  4. Box::new(Cons(2,
  5. Box::new(Cons(3,
  6. Box::new(Nil)
  7. )
  8. )
  9. )
  10. ));
  11. println!("【 list 】==> {:?}", list);
  12. }
  13. #[derive(Debug)]
  14. enum List {
  15. Cons(i32, Box<List>),
  16. Nil,
  17. }

Box

  • 只提供了间接存储和heap分配的功能
  • 没有性能开销,也没有额外的性能损耗

Deref Trait

如果实现了Deref Trait那么就可以自定义解引用(*)的行为。
通过实现Deref Trait智能指针就可以像普通引用一样来处理。

解引用运算符*

  • 常规的指针也是一种引用。 ```rust let x = 5; let y = &x;

assert_eq!(x, 5); assert_eq!(x, *y); // y是x的引用,当我们想知道y的值是多少的时候,就需要解引用

  1. 当我们不进行解引用时,代码就会painc
  2. ```rust
  3. assert_eq!(x, y);

image.png

意思就是没有实现 整型到引用整型的一个方法

而Box是默认实现了Deref Trait的,所以我们把上面代码改造一下,也是可以的。

let x = 5;
let y = Box::new(x);

assert_eq!(x, 5);
assert_eq!(x, *y); // y是x的引用,当我们想知道y的值是多少的时候,就需要解引用

这里需要知道的关键点就是:因为**Box<T>**实现了**Deref Trait**所以智能指针可以像普通指针一样来解引用.

实现自定义智能指针

fn main() {
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(x, 5);
    // 此时这里会报错,因为我们的自定义智能指针没有实现Deref
    assert_eq!(x, *y);
}

// 实现自定义智能指针
struct MyBox<T>(T); // 定义MyBox的元组

// 为MyBox实现构造方法
impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

image.png
我们来实现一个Deref Trait

use std::ops::Deref;

fn main() {
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(x, 5);
    assert_eq!(x, *y);
}

// 实现自定义智能指针
struct MyBox<T>(T); // 定义MyBox的元组

// 为MyBox实现构造方法
impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

// 实现Deref Trait
impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

这里主要是为了加强 Deref Trait的印象,就是为了让智能指针能够像普通的指针一样可以进行解引用*. 还有一点就死: Box 其实就一个元组结构体**tuple struct**

从上面的例子中我们还可以了解到一个关键点就是:
解引用操作***y**其实就相当于 ***( y.deref() )**

函数和方法的隐式解引用转化(Deref Coercion)

  • 隐式解引用转化是为函数和方法提供的一些便捷特性

假设T实现了Deref Trait

  • Deref Coercion可以把T的引用 ====> T经过Deref操作之后生成的引用

假设我们有这样一种场景: 当我们传递给函数一个参数x,这个x是某个类型的引用。 但是呢这个参数x不符合函数定义时的参数类型。 这时Deref Coercion就会自动发生 编译器会对deref进行一系列调用,最终转化为所需要的参数类型(这个过程在编译时完成,所以不会对运行时产生额外的开销)

( 那么会出现无法转换的情况吗?)

use std::ops::Deref;

fn hello(s: &str) {
    println!("【 s 】==> {:?}", s);
}

fn main() {
    let my_box = MyBox::new(String::from("hello"));


    // 这里我们定义的方法的参数类型为&str
    // 但是我们传入的是 &MyBox<String>

    // 因为MyBox实现了Deref
    // 所以 deref &MyBox<String> => String;
    // 因为String也实现了Deref,返回的是一个切片类型
    // 所以 deref String => &str

    hello(&my_box);
}

// 实现自定义智能指针
struct MyBox<T>(T); // 定义MyBox的元组

// 为MyBox实现构造方法
impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

// 实现Deref Trait
impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

**Deref Coercion**:编译器会对**deref**进行一系列调用,最终转化为所需要的参数类型(这个过程在编译时完成,所以不会对运行时产生额外的开销)
image.png

Drop Trait