Rust?中?Deref?Coercion講解
0x00 前言
寫這個(gè)文檔的初衷是因?yàn)樵谥形纳鐓^(qū)看到一個(gè)非常不負(fù)責(zé)任的翻譯,將 “implicit deref coercion” 翻譯成 “隱式 deref 強(qiáng)制”,于是覺(jué)得有必要記錄一下,以防止后來(lái)的新手在這里翻車。
首先需要解釋一下 “coercion” 在編程語(yǔ)言文檔中尤其是涉及到類型轉(zhuǎn)換時(shí)的中文意思。
類型轉(zhuǎn)換 (type conversion) 包括顯式指定被轉(zhuǎn)換到的類型的顯式轉(zhuǎn)換 (explicit conversion) 或稱 cast,以及與之相對(duì)的隱式轉(zhuǎn)換 (implicit conversion) 或稱 coercion。因?yàn)榉g不準(zhǔn)等原因,這兩者之間經(jīng)常被混淆。
cast 和 coercion 同時(shí)出現(xiàn)的時(shí)候,中文把前者翻譯成 “顯式類型轉(zhuǎn)換”,后者翻譯成 “隱式類型轉(zhuǎn)換”。
Rust 的設(shè)計(jì)理念一向是顯式比隱式好,也就是說(shuō)所有的行為盡量在代碼中表現(xiàn)出來(lái)。但也是有例外的,例如接下來(lái)我們要討論的兩個(gè)話題。
0x01 Deref Trait
Rust 中有一對(duì)運(yùn)算符 & 和 *,分別表示對(duì)一個(gè)變量的引用和解引用。
例如下面的代碼:
fn main() {
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y);
}
如果按照 C 語(yǔ)言的思維方式,這兩個(gè)操作符是互補(bǔ)的,即互為逆操作,但在 Rust 中并不適用。當(dāng)一個(gè)類型實(shí)現(xiàn)了 Deref 這個(gè) Trait 后,對(duì)該類型顯式執(zhí)行 *y 操作時(shí),Rust 將隱式執(zhí)行 *(y.deref()) 。
如果沒(méi)有實(shí)現(xiàn) Deref trait,只有引用類型可以被解引用,而實(shí)現(xiàn)了 Deref trait 后,編譯器將隱式執(zhí)行 deref 決定返回怎樣的引用類型。
Deref Trait 的定義如下:
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
有一個(gè)很有趣的點(diǎn),雖然是 Deref 操作,但是返回類型卻是一個(gè)引用類型。這個(gè)在官方文檔中給出了解釋:
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ù)場(chǎng)景下的 ownership 機(jī)制適配。
通過(guò)了解 Rust 的 Deref Trait 后,我們可以得出結(jié)論:在 Rust 中 *&T 和 &*T 不一定是同一個(gè)類型,例如下面示例:
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 的場(chǎng)景通常是使用 &str 作為參數(shù)的函數(shù),傳入 &String 是可以編譯通過(guò)的,習(xí)慣了 Rust 大爺苛刻的編譯器后覺(jué)得這不可思議,深入了解后才知道,Rust 內(nèi)部實(shí)現(xiàn)了 String 類型的 Deref trait,并且 type Target = &str 。
當(dāng)引用作為函數(shù)或方法的參數(shù)時(shí),Rust 編譯器將隱式執(zhí)行 deref coercion,以找到適配的類型。這樣省去了 * 和 & 的繁瑣操作,但講真對(duì)新手確實(shí)不友好。下面是一個(gè)示例:
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);
}
再看一個(gè)有意思的示例:
fn main() {
let s = "Hello, Rust!";
println!("length: {}", s.len());
println!("length: {}", (&s).len());
println!("length: {}", (&&&&&&&&&&&&&s).len());
}
上面的示例是可以編譯通過(guò)的,我們?nèi)绻褂?&&&&&&&&&&str 類型來(lái)調(diào)用成員方法,也是可以的。原因是 Rust 編譯器的 deref coercion 是遞歸的,當(dāng)它找不到這個(gè)成員方法的時(shí)候會(huì)自動(dòng)嘗試使用 deref() 方法后再找該方法,一直遞歸下去。編譯器在&&&str 類型里面找不到 len 方法,就嘗試將它 deref(),變成 &&str 類型,再尋找 len 方法,還是沒(méi)找到,那么繼續(xù) deref(),變成 &str,現(xiàn)在找到 len 方法了,于是就調(diào)用這個(gè)方法。
0x03 Option 中的 as_deref()
熟練的使用 Option 可以在 Rust 開(kāi)發(fā)中節(jié)省很多頭發(fā)。當(dāng)需要將 Option<T> 轉(zhuǎn)化為 Option<&T> 時(shí),我們通常使用 Option.as_ref() 操作,再結(jié)合 map() 方法可以在不轉(zhuǎn)移所有權(quán)的情況下使用 Option 中的變量。當(dāng)看到 Option.as_deref() 操作時(shí),我們首先會(huì)望文生義的認(rèn)為是將 Option<&T> 轉(zhuǎn)化為 Option<T>,但理解了前兩節(jié)的內(nèi)容后會(huì)懂得 Option.as_deref() 的操作其實(shí)是 Option.as_ref().map(|s| s.deref()) 的簡(jiǎn)寫形式,返回的仍然是一個(gè)引用,目的是為了使用 Rust 中的 deref coercion 機(jī)制。
參考文檔
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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用cargo install安裝Rust二進(jìn)制工具過(guò)程
cargoinstall是一個(gè)用于安裝包含可執(zhí)行目標(biāo)的Rust包的命令行工具,類似于系統(tǒng)軟件包管理器,但它為Rust開(kāi)發(fā)者提供了一種簡(jiǎn)潔的方式來(lái)安裝和管理命令行工具,安裝后,二進(jìn)制文件會(huì)存儲(chǔ)在$HOME/.cargo/bin目錄中,需要將該目錄添加到$PATH環(huán)境變量中才能在命令行中直接運(yùn)行2025-02-02
Rust調(diào)用函數(shù)操作符?.?和?::?的區(qū)別詳解
在Rust中,.和::操作符都可以用來(lái)調(diào)用方法,但它們的用法有所不同,所以本文就將詳細(xì)的給大家介紹一下.和::操作符的區(qū)別,感興趣的同學(xué)跟著小編一起來(lái)學(xué)習(xí)吧2023-07-07
詳解在Rust語(yǔ)言中如何聲明可變的static類型變量
在Rust中,可以使用lazy_static宏來(lái)聲明可變的靜態(tài)變量,lazy_static是一個(gè)用于聲明延遲求值靜態(tài)變量的宏,本文將通過(guò)一個(gè)簡(jiǎn)單的例子,演示如何使用?lazy_static?宏來(lái)聲明一個(gè)可變的靜態(tài)變量,需要的朋友可以參考下2023-08-08
Rust動(dòng)態(tài)調(diào)用字符串定義的Rhai函數(shù)方式
Rust中使用Rhai動(dòng)態(tài)調(diào)用字符串定義的函數(shù),通過(guò)eval_expression_with_scope實(shí)現(xiàn),但參數(shù)傳遞和函數(shù)名處理有局限性,使用FnCall功能更健壯,但更復(fù)雜,總結(jié)提供了更通用的方法,但需要處理更多錯(cuò)誤情況2025-02-02
Rust循環(huán)控制結(jié)構(gòu)用法詳解
Rust提供了多種形式的循環(huán)結(jié)構(gòu),每種都適用于不同的場(chǎng)景,在Rust中,循環(huán)有三種主要的形式:loop、while和for,本文將介紹Rust中的這三種循環(huán),并通過(guò)實(shí)例展示它們的用法和靈活性,感興趣的朋友一起看看吧2024-02-02
教你使用RustDesk?搭建一個(gè)自己的遠(yuǎn)程桌面中繼服務(wù)器
這篇文章主要介紹了RustDesk?搭建一個(gè)自己的遠(yuǎn)程桌面中繼服務(wù)器,主要包括服務(wù)端安裝和客戶端配置方法,配置好相關(guān)操作輸入控制碼即可發(fā)起遠(yuǎn)程或文件傳輸,本文通過(guò)圖文給大家講解的非常詳細(xì),需要的朋友可以參考下2022-08-08

