侧边栏壁纸
博主头像
王一川博主等级

努力成为一个不会前端的全栈工程师

  • 累计撰写 70 篇文章
  • 累计创建 20 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录

Rust智能指针

王一川
2022-12-12 / 0 评论 / 0 点赞 / 1,115 阅读 / 1,576 字
温馨提示:
本文最后更新于 2022-12-12,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

为什么会有这篇文章?

在跟着官方文档学习Rust智能指针的时候,第一次感受到困惑不得甚解。例如:Smart pointers, on the other hand, are data structures that act like a pointer but also have additional metadata and capabilities,翻译过来就是:智能指针=指针+元数据和功能。在我翻阅无数资料后尝试去理解、总结一下。

从官方文档的一句话开始:Smart pointers are usually implemented using structs. Unlike an ordinary struct, smart pointers implement the Deref and Drop traits. 如果不理解文章开头的一句话那么这句话从技术层面定义什么是智能指针:就是实现DerefDrop的结构体。我们知道Rust中通过trait来赋予类型的行为,Deref赋予类型解引用的能力即:对于实现Deref的某个类型引用x,可以调用*xDrop赋予类型在离开作用域后执行的一些代码。这就区分了智能指针和普通指针的区别,具备一些特殊的行为。那么只需要再回答一下智能指针智能在哪里?

一、什么是智能指针

从技术角度来定义就是实现了DerefDrop的结构体,例如官方给出的最简单直接的智能指针Box<T>

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

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

Deref的实现如下:

#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_box", issue = "92521")]
impl<T: ?Sized, A: Allocator> const Deref for Box<T, A> {
    type Target = T;

    fn deref(&self) -> &T {
        &**self
    }
}

deref方法的意义在于当编译器遇到*x的解引用操作是会自动调用deref方法,稍后会举例验证。首先可能需要解释一下&**self是什么操作。图解如下

image-20221212144253459

注:Addr表示值在堆中的内存地址

下面来验证一下*x解引用时调用的是deref方法,自定义一个元组结构体MyBox<T>

// 定一个元组结构体
struct MyBox<T>(T);

// 定义构造方法
impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

测试用例

fn main() {
    let my_box = MyBox::new(5);
    let _x = *my_box;
}

build结果

wjun :: learn/toolchain/demo ‹master*› » cargo build
   Compiling demo v0.1.0 (/Users/wjun/Documents/Program/Rust/learn/toolchain/demo)
error[E0614]: type `MyBox<{integer}>` cannot be dereferenced
 --> demo/src/main.rs:3:14
  |
3 |     let _x = *my_box;
  |              ^^^^^^^

For more information about this error, try `rustc --explain E0614`.
error: could not compile `demo` due to previous error

当前类型不能被解引用,下面尝试实现Deref

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

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

注:返回值&Self::Target和Box源码中返回值&T是等价的,这里是编译器自动生成的

再次build没有报错。

二、智能在哪里

上一节我们实现了一个自定义智能指针MyBox<T>,下面来探讨智能在哪里;首先定义一个普通的结构体

struct User {
    name: &'static str,
}

impl User {
    fn name(&self) {
        println!("{}", &self.name)
    }
}

然后使用智能指针进行包装

fn where_smart_pointer() {
    let user = User { name: "王一川" };
    let my_box = MyBox::new(user);
    my_box.name();
}

这时候细心的小伙伴发现问题了,这能编译通过?MyBox哪来的name方法,name方法只在User中定义的。这就是智能指针智能的其中一点

🎉🎉可以自动解引用,提高开发效率🎉🎉

其调用逻辑是:当遇到.操作调用的时候,会触发自动解引用,上一节MyBox中deref逻辑是返回&T,即user的引用那么自然就可以调用name方法了。

注:这里是自动解引用,要区别于*操作是强制解引用,是自动和手动的区别

再看一个例子

fn print_str(line: &str) {
    println!("{}", line);
}

一个简单的逻辑,打印字符串字面量,相信大家学习Rust的时候一定做过这样子的调用

fn where_smart_pointer() {
    let line = String::from("王一川");
    print_str(&line);
}

要求&str但传入&String,之前的解释大多是str可以看成字符串切片,str等价于String[…],所以编译器可以识别。这种解释也不能算错,但官方在智能指针中说:we’ve already encountered a few smart pointers in this book, including String and Vec<T>,String和Vec也是智能指针,可以看到String中对deref方法的实现

#[stable(feature = "rust1", since = "1.0.0")]
impl ops::Deref for String {
    type Target = str;

    #[inline]
    fn deref(&self) -> &str {
        unsafe { str::from_utf8_unchecked(&self.vec) }
    }
}

也就是说如果对String类型解引用可以获得&str,那么这一切就解释通了,作为函数参数会自动触发解引用操作

同理Vec的deref方法的实现也是如此

#[stable(feature = "rust1", since = "1.0.0")]
impl<T, A: Allocator> ops::Deref for Vec<T, A> {
    type Target = [T];

    #[inline]
    fn deref(&self) -> &[T] {
        unsafe { slice::from_raw_parts(self.as_ptr(), self.len) }
    }
}

上述是Deref带来的智能,那么Drop带来的只能就是自动的内存管理,即

🎉🎉可以自动化管理内存,安全无忧🎉🎉

但是我们Rust不允许我们手动调用drop方法,因为这样就造成了double free,这块在官方文档中解释的比较详细就不做赘述了,相信到这里大家对智能指针的理解应该会清晰很多,这时候在回过头看官方文档就不会很懵。

总结一下:

  1. 什么是智能指针:Rust 的 trait 控制类型的行为,我们称实现了 Deref 和 Drop 任意一个的类型为智能指针
  2. 智能指针智能在何处
    1. 可以自动解引用,提高开发体验(Deref)
    2. 可以自动化管理内存,安全无忧(Drop)
0

评论区