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()
*billy
。Character
当然不仅仅是i8
的智能指针。
当然,写hit_points_vec.push(*billy)
并不违法,但这让代码看起来非常奇怪。也许一个简单的.get_hp()
方法会好得多,或者另一个存放角色的结构体。然后你可以迭代并推送每个角色的 hit_points
。Deref
提供了很多功能,但最好确保代码的逻辑性。