Rust-解引用与Deref trait

解引用想必大家都很熟悉,和CPP、Go等语言类似,都是用*操作符来进行解引用,既获取引用/指针所指向的值,在Rust中可以同实现Deref trait来重载*运算符

Deref trait

*的意义

在我们使用*对y进行解引用时,编译器实际帮我们做的是,

rust 复制代码
*(y.deref())

*运算符包含两个行为:一个朴素的解引用* + deref()。首先通过deref获得一个引用,再使用朴素的解引用方式取到值

Deref trait

由于在对某一个变量进行解引用时,实际编译器会自动帮我们调用deref()方法,而该方法正是属于Deref trait,其定义如下,deref方法返回的是目标类型的引用。

rust 复制代码
pub trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

自定义智能指针

当我们使用Box智能指针时,当对其进行解引用时,会返回其内部值,这就是通过自定义deref方法实现的

rust 复制代码
// MyBox定义
struct MyBox<T>(T);

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

// 重载*
use std::ops::Deref;

impl<T> Deref for MyBox<T> {
    type Target = T;

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

隐式Deref转换

对于函数和方法的传参,Rust 提供了一个极其有用的隐式转换:Deref 转换。若一个类型实现了 Deref 特征,那它的引用在传给函数或方法时,会根据参数签名来决定是否进行隐式的 Deref 转换

rust 复制代码
fn main() {
    let s = String::from("hello world");
    display(&s)
}

fn display(s: &str) {
    println!("{}",s);
}
  • String 实现了 Deref 特征,可以在需要时自动被转换为 &str 类型
  • &s 是一个 &String 类型,当它被传给 display 函数时,自动通过 Deref 转换成了 &str
  • 必须使用 &s 的方式来触发 Deref(仅引用类型的实参才会触发自动解引用)

连续隐式转换

Deref 可以支持连续的隐式转换,直到找到适合的形式为止:

rust 复制代码
fn main() {
    let s = MyBox::new(String::from("hello world"));
    display(&s)
}

fn display(s: &str) {
    println!("{}",s);
}

这里我们使用了之前自定义的智能指针 MyBox,并将其通过连续的隐式转换变成 &str 类型:首先 MyBoxDerefString 类型,结果并不能满足 display 函数参数的要求,编译器发现 String 还可以继续 Deref&str,最终成功的匹配了函数参数

引用归一化

Rust 会做隐式转换时自动把智能指针和 &&&&v 做引用归一化操作,转换成 &v 形式,最终再对 &v 进行解引用:

  • 把智能指针(比如在库中定义的,Box、Rc、Arc、Cow 等)从结构体脱壳为内部的引用类型,也就是转成结构体内部的 &v
  • 把多重&,例如 &&&&&&&v,归一成 &v

看一个例子

rust 复制代码
    fn foo(s: &str) {}

    // 由于 String 实现了 Deref<Target=str>
    let owned = "Hello".to_string();

    // 因此下面的函数可以正常运行:
    foo(&owned);
rust 复制代码
    use std::rc::Rc;

    fn foo(s: &str) {}

    // String 实现了 Deref<Target=str>
    let owned = "Hello".to_string();
    // 且 Rc 智能指针可以被自动脱壳为内部的 `owned` 引用: &String ,然后 &String 再自动解引用为 &str
    let counted = Rc::new(owned);

    // 因此下面的函数可以正常运行:
    foo(&counted);
rust 复制代码
    struct Foo;

    impl Foo {
        fn foo(&self) { println!("Foo"); }
    }

    let f = &&Foo;

    f.foo();
    (&f).foo();
    (&&f).foo();
    (&&&&&&&&f).foo();

三种Deref转换

在之前,我们讲的都是不可变的 Deref 转换,实际上 Rust 还支持将一个可变的引用转换成另一个可变的引用以及将一个可变引用转换成不可变的引用,规则如下:

  • T: Deref<Target=U>,可以将 &T 转换成 &U,也就是我们之前看到的例子
  • T: DerefMut<Target=U>,可以将 &mut T 转换成 &mut U
  • T: Deref<Target=U>,可以将 &mut T 转换成 &U
rust 复制代码
struct MyBox<T> {
    v: T,
}

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

use std::ops::Deref;

impl<T> Deref for MyBox<T> {
    type Target = T;

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

use std::ops::DerefMut;

impl<T> DerefMut for MyBox<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.v
    }
}

fn main() {
    let mut s = MyBox::new(String::from("hello, "));
    display(&mut s)
}

fn display(s: &mut String) {
    s.push_str("world");
    println!("{}", s);
}

以上代码有几点值得注意:

  • 要实现 DerefMut 必须要先实现 Deref 特征:pub trait DerefMut: Deref
  • T: DerefMut<Target=U> 解读:将 &mut T 类型通过 DerefMut 特征的方法转换为 &mut U 类型,对应上例中,就是将 &mut MyBox<String> 转换为 &mut String

对于上述三条规则中的第三条,它比另外两条稍微复杂了点:Rust 可以把可变引用隐式的转换成不可变引用,但反之则不行

相关推荐
对象存储与RustFS2 小时前
用 rclone 把现有 S3/MinIO 数据同步到 RustFS
后端·rust·开源
2501_915918414 小时前
Rust 程序抓包解密,rustls 不认系统证书的几种办法
开发语言·后端·网络协议·ios·adb·https·rust
学心理学的程序员4 小时前
anydoc:Firecrawl 出的 Rust 文档转 Markdown,4ms 转换、14 种格式干翻 MarkItDown
开发语言·后端·rust·开源·firecrawl·claude code·anydoc
Flynt18 小时前
OpenAI把Codex底层从TS重写成Rust,我拆开看了下它的三层架构
rust·openai·agent
第一程序员18 小时前
AI 辅助编程的 7 个误区:把模型当高级搜索引擎是对它的最大浪费
python·rust·github
NutShell Wang1 天前
Firecrawl anydoc 实战拆解:用一个 Rust 依赖吃下 14 种文档格式
性能优化·rust·vibe coding
DevHub1 天前
probe-rs 教程:一行命令替代 OpenOCD 烧写调试 Cortex-M
arm开发·嵌入式硬件·mcu·rust
程序员爱钓鱼1 天前
Rust const泛型详解:让常量也成为泛型参数
后端·面试·rust
k4m7v2pz1 天前
ESP32-S3 + I2S 麦克风实时音频流静音问题排查:从网络到AGC的谬误溯源
rust·音频·esp32·agc·实时监听·静音排查
SomeB1oody1 天前
【RustyML入门】7.4. 按需裁剪与模块化集成
开发语言·后端·机器学习·rust·教程