Rust 中 Deref Coercion讲解

 更新时间:2022年10月26日 10:50:14   作者:aggresss  
Rust 的设计理念一向是显式比隐式好,也就是说所有的行为尽量在代码中表现出来,这篇文章主要介绍了Rust 中 Deref Coercion 介绍,需要的朋友可以参考下

0x00 前言

写这个文档的初衷是因为在中文社区看到一个非常不负责任的翻译,将 “implicit deref coercion” 翻译成 “隐式 deref 强制”,于是觉得有必要记录一下,以防止后来的新手在这里翻车。
首先需要解释一下 “coercion” 在编程语言文档中尤其是涉及到类型转换时的中文意思。
类型转换 (type conversion) 包括显式指定被转换到的类型的显式转换 (explicit conversion) 或称 cast,以及与之相对的隐式转换 (implicit conversion) 或称 coercion。因为翻译不准等原因,这两者之间经常被混淆。
cast 和 coercion 同时出现的时候,中文把前者翻译成 “显式类型转换”,后者翻译成 “隐式类型转换”。

Rust 的设计理念一向是显式比隐式好,也就是说所有的行为尽量在代码中表现出来。但也是有例外的,例如接下来我们要讨论的两个话题。

0x01 Deref Trait

Rust 中有一对运算符 &*,分别表示对一个变量的引用和解引用。
例如下面的代码:

fn main() {
    let x = 5;
    let y = &x;

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

如果按照 C 语言的思维方式,这两个操作符是互补的,即互为逆操作,但在 Rust 中并不适用。当一个类型实现了 Deref 这个 Trait 后,对该类型显式执行 *y 操作时,Rust 将隐式执行 *(y.deref())
如果没有实现 Deref trait,只有引用类型可以被解引用,而实现了 Deref trait 后,编译器将隐式执行 deref 决定返回怎样的引用类型。

Deref Trait 的定义如下:

pub trait Deref {
    type Target: ?Sized;

    fn deref(&self) -> &Self::Target;
}

有一个很有趣的点,虽然是 Deref 操作,但是返回类型却是一个引用类型。这个在官方文档中给出了解释:

The reason the deref method returns a reference to a value, and that the plain dereference outside the parentheses in *(y.deref()) is still necessary, is to do with the ownership system. If the deref method returned the value directly instead of a reference to the value, the value would be moved out of self. We don’t want to take ownership of the inner value inside MyBox in this case or in most cases where we use the dereference operator.

主要原因是为了与大多数场景下的 ownership 机制适配。

通过了解 Rust 的 Deref Trait 后,我们可以得出结论:在 Rust 中 *&T&*T 不一定是同一个类型,例如下面示例:

use std::ops::Deref;

#[derive(Copy, Clone, Debug)]
struct MyBox {
    alpha: i32,
}

impl Deref for MyBox {
    type Target = i32;

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

fn main() {
    let beta = MyBox { alpha: 32 };
    let gamma = &*beta; // &i32
    let delta = *β // Mybox
    println!("{:?}, {:?}", gamma, delta);
}

0x02 Implicit Deref Coercion

Deref coercion converts a reference to a type that implements the Deref trait into a reference to another type.

最初接触到 Deref coercion 的场景通常是使用 &str 作为参数的函数,传入 &String 是可以编译通过的,习惯了 Rust 大爷苛刻的编译器后觉得这不可思议,深入了解后才知道,Rust 内部实现了 String 类型的 Deref trait,并且 type Target = &str
当引用作为函数或方法的参数时,Rust 编译器将隐式执行 deref coercion,以找到适配的类型。这样省去了 *& 的繁琐操作,但讲真对新手确实不友好。下面是一个示例:

fn hello(name: &str) {
    println!("Hello, {name}!");
}

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
    }
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m);
}

再看一个有意思的示例:

fn main() {
    let s = "Hello, Rust!";
    println!("length: {}", s.len());
    println!("length: {}", (&s).len());
    println!("length: {}", (&&&&&&&&&&&&&s).len());
}

上面的示例是可以编译通过的,我们如果使用 &&&&&&&&&&str 类型来调用成员方法,也是可以的。原因是 Rust 编译器的 deref coercion 是递归的,当它找不到这个成员方法的时候会自动尝试使用 deref() 方法后再找该方法,一直递归下去。编译器在&&&str 类型里面找不到 len 方法,就尝试将它 deref(),变成 &&str 类型,再寻找 len 方法,还是没找到,那么继续 deref(),变成 &str,现在找到 len 方法了,于是就调用这个方法。

0x03 Option 中的 as_deref()

熟练的使用 Option 可以在 Rust 开发中节省很多头发。当需要将 Option<T> 转化为 Option<&T> 时,我们通常使用 Option.as_ref() 操作,再结合 map() 方法可以在不转移所有权的情况下使用 Option 中的变量。当看到 Option.as_deref() 操作时,我们首先会望文生义的认为是将 Option<&T> 转化为 Option<T>,但理解了前两节的内容后会懂得 Option.as_deref() 的操作其实是 Option.as_ref().map(|s| s.deref()) 的简写形式,返回的仍然是一个引用,目的是为了使用 Rust 中的 deref coercion 机制。

参考文档

https://doc.rust-lang.org/std/ops/trait.Deref.html
https://doc.rust-lang.org/book/ch15-02-deref.html
https://stackoverflow.com/questions/8857763/what-is-the-difference-between-casting-and-coercing
https://mp.weixin.qq.com/s/G28XE1rfX0nT6zIi86ji0Q
https://blog.csdn.net/weixin_39702559/article/details/112276392
https://blog.csdn.net/JAN6055/article/details/125774473

到此这篇关于Rust 中 Deref Coercion 介绍的文章就介绍到这了,更多相关Rust  Deref Coercion内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一文掌握Rust编程中的生命周期

    一文掌握Rust编程中的生命周期

    在Rust语言中, 每一个引用都有其生命周期, 通俗讲就是每个引用在程序执行的过程中都有其自身的作用域, 一旦离开其作用域, 其生命周期也宣告结束, 值不再有效,这篇文章主要介绍了Rust编程中的生命周期,需要的朋友可以参考下
    2023-11-11
  • Rust中的引用与借用举例详解

    Rust中的引用与借用举例详解

    这篇文章主要给大家介绍了关于Rust中引用与借用的相关资料,rust中借用和引用的附带功效都一样,就是都有生命周期,文中通过代码介绍的非常详细,对大家学习或者使用Rust具有一定的参考价值,需要的朋友可以参考下
    2023-11-11
  • Rust use关键字妙用及模块内容拆分方法

    Rust use关键字妙用及模块内容拆分方法

    这篇文章主要介绍了Rust use关键字妙用|模块内容拆分,文中还给大家介绍use关键字的习惯用法,快速引用自定义模块内容或标准库,以此优化代码书写,需要的朋友可以参考下
    2022-09-09
  • Rust 编程语言中的所有权ownership详解

    Rust 编程语言中的所有权ownership详解

    这篇文章主要介绍了Rust 编程语言中的所有权ownership详解的相关资料,需要的朋友可以参考下
    2023-02-02
  • Rust使用kind进行异常处理(错误的分类与传递)

    Rust使用kind进行异常处理(错误的分类与传递)

    Rust 有一套独特的处理异常情况的机制,它并不像其它语言中的 try 机制那样简单,这篇文章主要介绍了Rust指南错误的分类与传递以及使用kind进行异常处理,需要的朋友可以参考下
    2022-09-09
  • Rust中的关联类型总结

    Rust中的关联类型总结

    关联类型是定义通用trait的一种机制。它允许在trait中定义一个或多个占位符类型,这些类型将在trait的实现中具体化。文中有详细示例代码供参考,需要的朋友可以阅读一下
    2023-05-05
  • Rust中的Trait与Trait Bounds详解

    Rust中的Trait与Trait Bounds详解

    Rust中的Trait与TraitBounds通过《西游记》的故事背景进行解释,Trait是一种接口定义机制,用于描述角色的能力;TraitBounds用于限制函数或结构体的参数类型必须实现某些trait;BlanketImplementations可以为所有实现了某类trait的类型提供默认的trait实现
    2025-02-02
  • Rust中泛型的学习笔记

    Rust中泛型的学习笔记

    在Rust语言中,泛型是一种强大的工具,它允许我们编写可复用且灵活的代码,本文主要介绍了Rust中泛型的学习笔记,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • 解析rust中的struct

    解析rust中的struct

    自定义的数据类型,为相关联的值命名,打包成有意义的组合,类似python的dict,但是赋值的时候可以不按顺序,本文给大家介绍下rust中的struct知识,感兴趣的朋友一起看看吧
    2022-10-10
  • rust 创建多线程web server的详细过程

    rust 创建多线程web server的详细过程

    web server 中主要的两个协议是 http 和 tcp,tcp 是底层协议,http 是构建在 tcp 之上的,本篇文章重点给大家介绍rust 创建多线程web server的详细过程,感兴趣的朋友跟随小编一起看看吧
    2023-11-11

最新评论