Deref 和 DerefMut
Deref
是讓你用 *
來對某些東西取值(dereference)的特徵。我們之前在使用元組結構體來做出新的型別時見過 Deref
這個字,現在是時候學會它了。
我們知道,參考和值是不一樣的:
// ⚠️ fn main() { let value = 7; // 這是個 i32 let reference = &7; // 這是個 &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
的簡單東西有時被稱為"智慧指標(smart pointer)"。一個智慧指標可以指向它的元素,有它的資訊,並且可以使用它的方法。因為現在我們可以新增 u8
的 my_number.0
,但我們不能用 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; // 記得, 這是"關聯型別(associated type)": 型別會一起寫在這. // 你必須要使用正確的 type Target = (你想回傳的型別) fn deref(&self) -> &Self::Target { // 當你使用 * 時 Rust 會呼叫 .deref(). 我們只定義 Target 為 u8 所以這很容易理解 &self.0 // 我們選擇 &self.0 因為這是元組結構體. 在具名結構體中它就會是像 "&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)); // 這是來自 u8 的方法 my_number.prints_the_number_times_two(); // 這是我們自己的方法 }
印出:
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 { // 這裡你不需要 type Target = u8; 這要感謝 Deref 因為它已經知道了 fn deref_mut(&mut self) -> &mut Self::Target { // 除了到處用 mut 以外,其它一切都一樣 &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)在一個大向量裡面。也許我們也會把怪物級資料也放進去,並存放在一起。由於 hit_points
是 i8
,我們實作了 Deref
,來讓我們可以對它進行各式各樣的數學計算。但是現在看看我們的 main()
函式有多麼奇怪:
use std::ops::Deref; // 直到例舉 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 { // 給 Character 實作 Deref. 現在我們可以任意做整數計算! 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); // 建立兩個角色, billy 和 brandy let brandy = Character::new("Brandy".to_string(), 9, 8, 7, 10, 19, 19, 5, Alignment::Good); let mut hit_points_vec = vec![]; // 把我們的生命值資料放在這裡 hit_points_vec.push(*billy); // 推入 *billy? hit_points_vec.push(*brandy); // 推入 *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
雖然提供了強大的力量,但最好確保程式碼的邏輯性。