Rust?中?Deref?Coercion講解
0x00 前言
寫這個文檔的初衷是因為在中文社區(qū)看到一個非常不負責任的翻譯,將 “implicit deref coercion” 翻譯成 “隱式 deref 強制”,于是覺得有必要記錄一下,以防止后來的新手在這里翻車。
首先需要解釋一下 “coercion” 在編程語言文檔中尤其是涉及到類型轉(zhuǎn)換時的中文意思。
類型轉(zhuǎn)換 (type conversion) 包括顯式指定被轉(zhuǎn)換到的類型的顯式轉(zhuǎn)換 (explicit conversion) 或稱 cast,以及與之相對的隱式轉(zhuǎn)換 (implicit conversion) 或稱 coercion。因為翻譯不準等原因,這兩者之間經(jīng)常被混淆。
cast 和 coercion 同時出現(xiàn)的時候,中文把前者翻譯成 “顯式類型轉(zhuǎn)換”,后者翻譯成 “隱式類型轉(zhuǎn)換”。
Rust 的設(shè)計理念一向是顯式比隱式好,也就是說所有的行為盡量在代碼中表現(xiàn)出來。但也是有例外的,例如接下來我們要討論的兩個話題。
0x01 Deref Trait
Rust 中有一對運算符 &
和 *
,分別表示對一個變量的引用和解引用。
例如下面的代碼:
fn main() { let x = 5; let y = &x; assert_eq!(5, x); assert_eq!(5, *y); }
如果按照 C 語言的思維方式,這兩個操作符是互補的,即互為逆操作,但在 Rust 中并不適用。當一個類型實現(xiàn)了 Deref
這個 Trait 后,對該類型顯式執(zhí)行 *y
操作時,Rust 將隱式執(zhí)行 *(y.deref())
。
如果沒有實現(xiàn) Deref trait,只有引用類型可以被解引用,而實現(xiàn)了 Deref trait 后,編譯器將隱式執(zhí)行 deref 決定返回怎樣的引用類型。
Deref Trait 的定義如下:
pub trait Deref { type Target: ?Sized; fn deref(&self) -> &Self::Target; }
有一個很有趣的點,雖然是 Deref 操作,但是返回類型卻是一個引用類型。這個在官方文檔中給出了解釋:
The reason the deref method returns a reference to a value, and that the plain dereference outside the parentheses in *(y.deref()) is still necessary, is to do with the ownership system. If the deref method returned the value directly instead of a reference to the value, the value would be moved out of self. We don’t want to take ownership of the inner value inside MyBox in this case or in most cases where we use the dereference operator.
主要原因是為了與大多數(shù)場景下的 ownership 機制適配。
通過了解 Rust 的 Deref Trait 后,我們可以得出結(jié)論:在 Rust 中 *&T
和 &*T
不一定是同一個類型,例如下面示例:
use std::ops::Deref; #[derive(Copy, Clone, Debug)] struct MyBox { alpha: i32, } impl Deref for MyBox { type Target = i32; fn deref(&self) -> &Self::Target { &self.alpha } } fn main() { let beta = MyBox { alpha: 32 }; let gamma = &*beta; // &i32 let delta = *β // Mybox println!("{:?}, {:?}", gamma, delta); }
0x02 Implicit Deref Coercion
Deref coercion converts a reference to a type that implements the Deref trait into a reference to another type.
最初接觸到 Deref coercion 的場景通常是使用 &str
作為參數(shù)的函數(shù),傳入 &String
是可以編譯通過的,習慣了 Rust 大爺苛刻的編譯器后覺得這不可思議,深入了解后才知道,Rust 內(nèi)部實現(xiàn)了 String
類型的 Deref trait,并且 type Target = &str
。
當引用作為函數(shù)或方法的參數(shù)時,Rust 編譯器將隱式執(zhí)行 deref coercion,以找到適配的類型。這樣省去了 *
和 &
的繁瑣操作,但講真對新手確實不友好。下面是一個示例:
fn hello(name: &str) { println!("Hello, {name}!"); } 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 } } fn main() { let m = MyBox::new(String::from("Rust")); hello(&m); }
再看一個有意思的示例:
fn main() { let s = "Hello, Rust!"; println!("length: {}", s.len()); println!("length: {}", (&s).len()); println!("length: {}", (&&&&&&&&&&&&&s).len()); }
上面的示例是可以編譯通過的,我們?nèi)绻褂?&&&&&&&&&&str
類型來調(diào)用成員方法,也是可以的。原因是 Rust 編譯器的 deref coercion 是遞歸的,當它找不到這個成員方法的時候會自動嘗試使用 deref()
方法后再找該方法,一直遞歸下去。編譯器在&&&str
類型里面找不到 len
方法,就嘗試將它 deref()
,變成 &&str
類型,再尋找 len
方法,還是沒找到,那么繼續(xù) deref()
,變成 &str
,現(xiàn)在找到 len
方法了,于是就調(diào)用這個方法。
0x03 Option 中的 as_deref()
熟練的使用 Option 可以在 Rust 開發(fā)中節(jié)省很多頭發(fā)。當需要將 Option<T>
轉(zhuǎn)化為 Option<&T>
時,我們通常使用 Option.as_ref()
操作,再結(jié)合 map()
方法可以在不轉(zhuǎn)移所有權(quán)的情況下使用 Option 中的變量。當看到 Option.as_deref()
操作時,我們首先會望文生義的認為是將 Option<&T>
轉(zhuǎn)化為 Option<T>
,但理解了前兩節(jié)的內(nèi)容后會懂得 Option.as_deref()
的操作其實是 Option.as_ref().map(|s| s.deref())
的簡寫形式,返回的仍然是一個引用,目的是為了使用 Rust 中的 deref coercion 機制。
參考文檔
https://doc.rust-lang.org/std/ops/trait.Deref.html
https://doc.rust-lang.org/book/ch15-02-deref.html
https://stackoverflow.com/questions/8857763/what-is-the-difference-between-casting-and-coercing
https://mp.weixin.qq.com/s/G28XE1rfX0nT6zIi86ji0Q
https://blog.csdn.net/weixin_39702559/article/details/112276392
https://blog.csdn.net/JAN6055/article/details/125774473
到此這篇關(guān)于Rust 中 Deref Coercion 介紹的文章就介紹到這了,更多相關(guān)Rust Deref Coercion內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust調(diào)用函數(shù)操作符?.?和?::?的區(qū)別詳解
在Rust中,.和::操作符都可以用來調(diào)用方法,但它們的用法有所不同,所以本文就將詳細的給大家介紹一下.和::操作符的區(qū)別,感興趣的同學(xué)跟著小編一起來學(xué)習吧2023-07-07Rust動態(tài)調(diào)用字符串定義的Rhai函數(shù)方式
Rust中使用Rhai動態(tài)調(diào)用字符串定義的函數(shù),通過eval_expression_with_scope實現(xiàn),但參數(shù)傳遞和函數(shù)名處理有局限性,使用FnCall功能更健壯,但更復(fù)雜,總結(jié)提供了更通用的方法,但需要處理更多錯誤情況2025-02-02Rust循環(huán)控制結(jié)構(gòu)用法詳解
Rust提供了多種形式的循環(huán)結(jié)構(gòu),每種都適用于不同的場景,在Rust中,循環(huán)有三種主要的形式:loop、while和for,本文將介紹Rust中的這三種循環(huán),并通過實例展示它們的用法和靈活性,感興趣的朋友一起看看吧2024-02-02教你使用RustDesk?搭建一個自己的遠程桌面中繼服務(wù)器
這篇文章主要介紹了RustDesk?搭建一個自己的遠程桌面中繼服務(wù)器,主要包括服務(wù)端安裝和客戶端配置方法,配置好相關(guān)操作輸入控制碼即可發(fā)起遠程或文件傳輸,本文通過圖文給大家講解的非常詳細,需要的朋友可以參考下2022-08-08