Rust-解引用与Deref trait

220 阅读5分钟

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

Deref trait

*的意义

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

*(y.deref())

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

Deref trait

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

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

自定义智能指针

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

// 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 中,自动解引用(Auto-dereferencing)是一种编译器特性,允许智能指针或实现了 Deref trait 的类型在特定场景下自动转换为底层类型的引用。这一机制极大地简化了代码,让开发者可以像操作原始类型一样操作智能指针,同时保持类型安全。

核心机制:Deref 和 DerefMut traits

自动解引用的基础是 Deref 和 DerefMut traits:

  • Deref:用于不可变解引用,需实现 deref() 方法。
  • DerefMut:用于可变解引用,需实现 deref_mut() 方法。
use std::ops::{Deref, DerefMut};

struct MyBox<T>(T);

impl<T> Deref for MyBox<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target { &self.0 }
}

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

fn main() {
    let mut x = MyBox(5);
    *x += 10; // 自动解引用:MyBox<i32> -> i32
    println!("{}", *x); // 输出: 15
}

 触发自动解引用的场景

 方法调用

当调用对象的方法时,Rust 会自动解引用智能指针:

struct Person {
    name: String,
}

impl Person {
    fn say_hello(&self) {
        println!("Hello, my name is {}", self.name);
    }
}

fn main() {
    let p = Box::new(Person { name: "Alice".to_string() });
    p.say_hello(); // 自动解引用:Box<Person> -> Person
    // 等价于:(&*p).say_hello();
}
函数参数传递

当实参与函数参数类型不匹配时,自动解引用会尝试转换:

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

fn main() {
    let s = Box::new(String::from("hello"));
    print_str(&s); // 自动解引用:&Box<String> -> &String -> &str
}
 运算符重载

在使用运算符(如 *+)时,自动解引用会触发:

fn main() {
    let b = Box::new(5);
    println!("{}", *b + 10); // 自动解引用:Box<i32> -> i32
}

 解引用链与多重转换

Rust 会递归应用 Deref 直到类型匹配:

Box<String> -> String -> &str
Rc<Vec<i32>> -> Vec<i32> -> &[i32]

例如:

use std::rc::Rc;

fn main() {
    let s = Rc::new(String::from("hello"));
    let len = s.len(); // Rc<String> -> String -> &str -> len()
    println!("Length: {}", len);
}

 自动解引用与借用规则

自动解引用严格遵循 Rust 的借用规则:

  • 不可变引用:只能解引用为不可变引用。
  • 可变引用:可以解引用为可变或不可变引用,但同一作用域内不能同时存在可变和不可变借用。
fn main() {
    let mut s = Box::new(String::from("hello"));
    let r1 = &s;      // 不可变借用
    // let r2 = &mut s; // 错误:不能在不可变借用后可变借用
    let len = r1.len(); // 自动解引用:&Box<String> -> &String
}

引用归一化

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

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

看一个例子

    fn foo(s: &str) {}

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

    // 因此下面的函数可以正常运行:
    foo(&owned);
    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);
    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
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 可以把可变引用隐式的转换成不可变引用,但反之则不行