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 可以把可变引用隐式的转换成不可变引用,但反之则不行

相关推荐
一个小坑货1 小时前
Cargo Rust 的包管理器
开发语言·后端·rust
bluebonnet271 小时前
【Rust练习】22.HashMap
开发语言·后端·rust
VertexGeek4 小时前
Rust学习(八):异常处理和宏编程:
学习·算法·rust
前端与小赵1 天前
什么是Sass,有什么特点
前端·rust·sass
一个小坑货1 天前
Rust基础
开发语言·后端·rust
Object~1 天前
【第九课】Rust中泛型和特质
开发语言·后端·rust
码农飞飞1 天前
详解Rust结构体struct用法
开发语言·数据结构·后端·rust·成员函数·方法·结构体
Dontla2 天前
Rust derive macro(Rust #[derive])Rust派生宏
开发语言·后端·rust
fqbqrr2 天前
2411rust,编译时自动检查配置
rust
梦想画家2 天前
用Rust中byteorder包高效处理字节序列
rust·序列化·byteorder·文件编码