Deref和DerefMut

Deref是让你用*来解引用某些东西的trait。我们知道,一个引用和一个值是不一样的。

// ⚠️
fn main() {
    let value = 7; // This is an i32
    let reference = &7; // This is a &i32
    println!("{}", value == reference);
}

而Rust连false都不给,因为它甚至不会比较两者。

error[E0277]: can't compare `{integer}` with `&{integer}`
 --> src\main.rs:4:26
  |
4 |     println!("{}", value == reference);
  |                          ^^ no implementation for `{integer} == &{integer}`

当然,这里的解法是使用*。所以这将打印出true

fn main() {
    let value = 7;
    let reference = &7;
    println!("{}", value == *reference);
}

现在让我们想象一下一个简单的类型,它只是容纳一个数字。它就像一个Box,我们有一些想法为它提供一些额外的功能。但如果我们只是给它一个数字, 它就不能做那么多了。

我们不能像使用Box那样使用*:

// ⚠️
struct HoldsANumber(u8);

fn main() {
    let my_number = HoldsANumber(20);
    println!("{}", *my_number + 20);
}

错误信息是:

error[E0614]: type `HoldsANumber` cannot be dereferenced
  --> src\main.rs:24:22
   |
24 |     println!("{:?}", *my_number + 20);

我们当然可以做到这一点。println!("{:?}", my_number.0 + 20);. 但是这样的话,我们就是在20的基础上再单独加一个u8。如果我们能把它们加在一起就更好了。cannot be dereferenced这个消息给了我们一个线索:我们需要实现Deref。实现Deref的简单东西有时被称为 "智能指针"。一个智能指针可以指向它的元素,有它的信息,并且可以使用它的方法。因为现在我们可以添加my_number.0,这是一个u8,但我们不能用HoldsANumber做其他的事情:到目前为止,它只有Debug

有趣的是:String其实是&str的智能指针,Vec是数组(或其他类型)的智能指针。所以我们其实从一开始就在使用智能指针。

实现Deref并不难,标准库中的例子也很简单。下面是标准库中的示例代码

use std::ops::Deref;

struct DerefExample<T> {
    value: T
}

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

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

fn main() {
    let x = DerefExample { value: 'a' };
    assert_eq!('a', *x);
}

所以我们按照这个来,现在我们的Deref是这样的。


#![allow(unused)]
fn main() {
// 🚧
impl Deref for HoldsANumber {
    type Target = u8; // Remember, this is the "associated type": the type that goes together.
                      // You have to use the right type Target = (the type you want to return)

    fn deref(&self) -> &Self::Target { // Rust calls .deref() when you use *. We just defined Target as a u8 so this is easy to understand
        &self.0   // We chose &self.0 because it's a tuple struct. In a named struct it would be something like "&self.number"
    }
}
}

所以现在我们可以用*来做:

use std::ops::Deref;
#[derive(Debug)]
struct HoldsANumber(u8);

impl Deref for HoldsANumber {
    type Target = u8;

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

fn main() {
    let my_number = HoldsANumber(20);
    println!("{:?}", *my_number + 20);
}

所以,这样就可以打印出40,我们不需要写my_number.0。这意味着我们得到了 u8 的方法,我们可以为 HoldsANumber 写出我们自己的方法。我们将添加自己的简单方法,并使用我们从u8中得到的另一个方法,称为.checked_sub().checked_sub()方法是一个安全的减法,它能返回一个Option。如果它能做减法,那么它就会在Some里面给你,如果它不能做减法,那么它就会给出一个None。记住,u8不能是负数,所以还是.checked_sub()比较安全,这样就不会崩溃了。

use std::ops::Deref;

struct HoldsANumber(u8);

impl HoldsANumber {
    fn prints_the_number_times_two(&self) {
        println!("{}", self.0 * 2);
    }
}

impl Deref for HoldsANumber {
    type Target = u8;

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

fn main() {
    let my_number = HoldsANumber(20);
    println!("{:?}", my_number.checked_sub(100)); // This method comes from u8
    my_number.prints_the_number_times_two(); // This is our own method
}

这个打印:

None
40

我们也可以实现DerefMut,这样我们就可以通过*来改变数值。它看起来几乎是一样的。在实现DerefMut之前,你需要先实现Deref

use std::ops::{Deref, DerefMut};

struct HoldsANumber(u8);

impl HoldsANumber {
    fn prints_the_number_times_two(&self) {
        println!("{}", self.0 * 2);
    }
}

impl Deref for HoldsANumber {
    type Target = u8;

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

impl DerefMut for HoldsANumber { // You don't need type Target = u8; here because it already knows thanks to Deref
    fn deref_mut(&mut self) -> &mut Self::Target { // Everything else is the same except it says mut everywhere
        &mut self.0
    }
}

fn main() {
    let mut my_number = HoldsANumber(20);
    *my_number = 30; // DerefMut lets us do this
    println!("{:?}", my_number.checked_sub(100));
    my_number.prints_the_number_times_two();
}

所以你可以看到,Deref给你的类型提供了强大的力量。

这也是为什么标准库说:Deref should only be implemented for smart pointers to avoid confusion。这是因为对于一个复杂的类型,你可以用 Deref 做一些奇怪的事情。让我们想象一个非常混乱的例子来理解它们的含义。我们将从一个游戏的 Character 结构开始。一个新的Character需要一些数据,比如智力和力量。所以这里是我们的第一个角色。

struct Character {
    name: String,
    strength: u8,
    dexterity: u8,
    health: u8,
    intelligence: u8,
    wisdom: u8,
    charm: u8,
    hit_points: i8,
    alignment: Alignment,
}

impl Character {
    fn new(
        name: String,
        strength: u8,
        dexterity: u8,
        health: u8,
        intelligence: u8,
        wisdom: u8,
        charm: u8,
        hit_points: i8,
        alignment: Alignment,
    ) -> Self {
        Self {
            name,
            strength,
            dexterity,
            health,
            intelligence,
            wisdom,
            charm,
            hit_points,
            alignment,
        }
    }
}

enum Alignment {
    Good,
    Neutral,
    Evil,
}

fn main() {
    let billy = Character::new("Billy".to_string(), 9, 8, 7, 10, 19, 19, 5, Alignment::Good);
}

现在让我们想象一下,我们要把人物的hit points放在一个大的vec里。也许我们会把怪物数据也放进去,把它放在一起。由于 hit_points 是一个 i8,我们实现了 Deref,所以我们可以对它进行各种计算。但是看看现在我们的main()函数中,它看起来多么奇怪。

use std::ops::Deref;

// All the other code is the same until after the enum Alignment
struct Character {
    name: String,
    strength: u8,
    dexterity: u8,
    health: u8,
    intelligence: u8,
    wisdom: u8,
    charm: u8,
    hit_points: i8,
    alignment: Alignment,
}

impl Character {
    fn new(
        name: String,
        strength: u8,
        dexterity: u8,
        health: u8,
        intelligence: u8,
        wisdom: u8,
        charm: u8,
        hit_points: i8,
        alignment: Alignment,
    ) -> Self {
        Self {
            name,
            strength,
            dexterity,
            health,
            intelligence,
            wisdom,
            charm,
            hit_points,
            alignment,
        }
    }
}

enum Alignment {
    Good,
    Neutral,
    Evil,
}

impl Deref for Character { // impl Deref for Character. Now we can do any integer math we want!
    type Target = i8;

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



fn main() {
    let billy = Character::new("Billy".to_string(), 9, 8, 7, 10, 19, 19, 5, Alignment::Good); // Create two characters, billy and brandy
    let brandy = Character::new("Brandy".to_string(), 9, 8, 7, 10, 19, 19, 5, Alignment::Good);

    let mut hit_points_vec = vec![]; // Put our hit points data in here
    hit_points_vec.push(*billy);     // Push *billy?
    hit_points_vec.push(*brandy);    // Push *brandy?

    println!("{:?}", hit_points_vec);
}

这只打印了[5, 5]。我们的代码现在让人读起来感觉非常奇怪。我们可以在main()上面看到Deref,然后弄清楚*billy的意思是i8,但是如果有很多代码呢?可能我们的代码有2000行,突然要弄清楚为什么要.push() *billyCharacter当然不仅仅是i8的智能指针。

当然,写hit_points_vec.push(*billy)并不违法,但这让代码看起来非常奇怪。也许一个简单的.get_hp()方法会好得多,或者另一个存放角色的结构体。然后你可以迭代并推送每个角色的 hit_pointsDeref提供了很多功能,但最好确保代码的逻辑性。