在Rust標準庫中,存在很多常用的工具類特型,它們能幫助我們寫出更具有Rust風格的代碼。
你可以通過在你的類型上實現(xiàn)std::ops::Deref
和std::ops::DerefMut
特型來自定義解引用操作例如*
操作符和.
操作符的行為。像Box<T>
和Rc<T>
實現(xiàn)了這兩個特型,所以他們的行為就像Rust內(nèi)置指針類型一樣。例如,如果你有一個Box<Complex>
類型的值b
,此時,*b
代表的是b
指向的那個復數(shù)值。b.re
代表該復數(shù)的實部.
如果對引用賦值或者借借出一個可變引用,那么Rust使用DerefMut
特型。否則,只讀就足夠了,此時會使用Deref
特型。
這兩個特型的定義類似如下:
trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
deref
和deref_mut
函數(shù)都使用&self
引用作為參數(shù)并且返回一個&Self::Target
的引用。 Target是該類型所擁有的,包含的或者指向的一個具體變量類型(顯然,它不可能把它不知道的東西借出去,例如另一個結(jié)構(gòu)體中的字段)。例如對Box<Complex>
來說,這里的Target
就是Complex
。注意DerefMut
拓展了Deref
,這時顯而易見的,如果你能解引用并修改它,那么你肯定能借出一個共享的引用而不修改。因為函數(shù)返回的引用的生命周期和&Self
相同(生命周期三原則),只要函數(shù)返回的引用一直存在,那么Self
本身就一直被借用。其實這里說的是只要是&Self
有效,那么函數(shù)得到的引用就一直有效。
Deref
和DerefMut
特型同時還扮演了其它角色。由于deref
函數(shù)拿走了一個&Self
引用但是返回了一個&Self::Target
引用,Rust可以使用它來進行自動引用轉(zhuǎn)換,將&Self
轉(zhuǎn)換成&Self::Target
。換句話說,如果插入一個deref
函數(shù)能消除類型不匹配的語法錯誤,Rust會自動為你插入。DerefMut
也可以作相應的轉(zhuǎn)換,只不過是可變引用。這個功能叫著deref coericoins
(強制解引用),一個類型被強制表現(xiàn)為另一種類型。
盡管你也可以自己寫一個類似強制轉(zhuǎn)引用的功能,但是它們很方便:
-
如果你有一個
Rc<String>
類型的值r
,你想在它上面應用String::find
函數(shù),你可以簡單的寫成r.find(?)
,而不是(*r).find(?)
。這是因為這個find
函數(shù)調(diào)用隱式的借用r
, 而Rc<T>
實現(xiàn)了Deref<Target=T>
,因此&Rc<String>
可以被解引用為&String
.其實這里就是智能指針的應用(可以把智能指針當成普通內(nèi)置指針使用) -
你可以在
String
上使用split_at
函數(shù),雖然該函數(shù)是定義在str
字符串切片類型上的,因為String
實現(xiàn)了Deref<Target=str>
。String
并不需要重新實現(xiàn)split_at
函數(shù),因為你可以從&String
強制解引用得到&str
。 這里其實是Rust中的一個便利性,我們可以在非常底層的數(shù)據(jù)結(jié)構(gòu)上定義函數(shù),然后其它結(jié)構(gòu)可以直接使用。最常見的就是Vec<T>
,其絕大部分函數(shù)是定義在切片[T]
上的,但是因為強制解引用,你可以非常簡單的直接在向量上使用這些函數(shù)。我們看一下代碼直觀就更明顯:impl str { pub const fn len(&self) -> usize { self.as_bytes().len() } pub const fn is_empty(&self) -> bool { self.len() == 0 } }
我們可以看到,len及is_empty函數(shù)都是定義在
str
上的,其參數(shù)為&self
,因此調(diào)用發(fā)生時,其實相當于是(&str).len
。雖然我們這里是(&String).len()
,但是由于自動解引用的存在,Rust會為我們自動插入deref
函數(shù),將&String
轉(zhuǎn)換成&str
。 -
如果你有一個字節(jié)向量v(類型為
Vec<u8>
),而你又想將它傳遞為參數(shù)為字節(jié)切片&[u8]
的函數(shù),你可以直接傳遞&v
即可。因為Vec<T>
實現(xiàn)了Deref<Target=[T]>
。這里和我上面剛說的定義在切片上的函數(shù)還有些不同,那個是解引用操作的,這里是函數(shù)參數(shù)傳遞,場景不一樣。
綜上所述,強制解引用主要應用在三個場合:
- 智能指針,讓智能指針變得和內(nèi)置指針一樣
- 共用函數(shù)定義,在底層結(jié)構(gòu)上定義函數(shù)從而讓其它類型直接使用。
- 函數(shù)多態(tài),例如函數(shù)參數(shù)為
&[T]
,那么任何實現(xiàn)了Deref<Target=[T]>
的類型都可以直接將引用傳遞給參數(shù)。
如果需要,Rust會執(zhí)行多重鏈式強制解引用 。例如你在Rc<String>
上調(diào)用split_at
函數(shù),首先,將它&Rc<String>
解引用為&String
,接下來再將&String
解引用為&str
,這正是split_at
函數(shù)的擁有類型。
例如,如果你的類型如下所示:
struct Selector<T> {
elements: Vec<T>,
current: usize
}
雖然這里的類型涉及到了泛型,但相當簡單。你的結(jié)構(gòu)體包含了一個類型T的向量和一個記錄當前位置的索引(current)。而這個結(jié)構(gòu)體的功能就是實現(xiàn)一個指向當前元素的指針行為。
為了實現(xiàn)這個需求,Selector
類型需要實現(xiàn)Deref
和DerefMut
特型。
use std::ops::{Deref, DerefMut};
impl<T> Deref for Selector<T> {
type Target = T;
fn deref(&self) -> &T {
&self.elements[self.current]
}
}
impl<T> DerefMut for Selector<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.elements[self.current]
}
}
代碼相當簡單了,我們就不說了。那怎么應用它呢?
let mut s = Selector { elements: vec!['x', 'y', 'z'],current: 2 };
*s = 'w';
assert_eq!(s.elements, ['x', 'y', 'w']);
這里s
定義為mut
的,這是因為我們要借用一個&mut T
并修改它的值。
Deref
和DerefMut
特型被設計用來實現(xiàn)實現(xiàn)智能指針類型,例如Box
,Rc
,Arc
等,它還用于某些特定類型。這些類型擁有其它類型的值,但是需要頻繁通過引用來使用它內(nèi)部擁有的其它類型的值。例如Vec<T>
和String
分別擁有[T]
和str
。當你只有讓Target
的方法自動出現(xiàn)在你的類型上這么一個目的時(正如c++中基類的方法自動出現(xiàn)在子類中),你不應該為它設計實現(xiàn)Deref
和DerefMut
特型。它有可能并是總是像你期望的那樣工作,并且濫用也容易導致困惑。
具體困惑是什么呢?下面提到了一點。
自動解引用功能有時會隨著警告,這或許會讓你感到不解。因為Rust雖然使用自動解引用來解決類型沖突,但是并不包含類型參數(shù)的條件綁定。例如,下面的代碼工作的很好:
let s = Selector { elements: vec!["good", "bad", "ugly"],
current: 2 };
fn show_it(thing: &str) { println!("{}", thing); }
show_it(&s);
這是因為我們的Selector
實現(xiàn)了Deref<Target=T>
,而在上面使用場景的第三點函數(shù)多態(tài)時我們知道,傳入&s
會自動執(zhí)行deref
函數(shù)從而轉(zhuǎn)換成為&str
.這里稍微有一點混亂。(這里是書中寫的,稍微有一點誤導)。
真實的事情是這樣的:
首先,s
的類型為Selector<&str>
(書中也是這么寫的),那么這里的T
應該是&str
而不是str
。所以它實現(xiàn)的是Deref<Target=&str>
而不是Deref<Target=str>
(書中是這樣寫的,未知原因)。所以書中寫的相當于show_it(s.deref())
這里deref
函數(shù)返回的類型為&&str
。但是傳入函數(shù)是沒有問題的,原因呢?估計是源碼中會再進行自動解引用 ,將&&T
解成&T
。
經(jīng)過查詢相關資料,Rust Course《Rust語言圣經(jīng)》上是這樣說的,Rust會 引用 歸一化,也就是把&&&v當成&v。所以這里&&str
實際上轉(zhuǎn)成了&str
,因此傳入函數(shù)是沒有問題的。實際上,我的猜想是對的。看下面標準庫源碼,
impl<T: ?Sized> Deref for &T {
type Target = T;
fn deref(&self) -> &T {
*self
}
}
它為&T實現(xiàn)了Deref<Target=T>
,雖然這里不擁有T,但是指向了T。(見前面特型定義)。所以&&T
會自動解引用為&T
。這么一來,s.deref()
返回的&&str
又被解引用成為了&str
,最終滿足函數(shù)參數(shù)的類型。
所以這里書中講的稍微不對,漏了一點東西。而我們一定不放過,要有打破沙鍋問到底的精神。
接下來講,如果你把show_it
改成一個泛型函數(shù),而類型參數(shù)T的限定為<T:Display>
,那么問題來了,
use std::fmt::Display;
fn show_it_generic<T: Display>(thing: T) { println!("{}", thing);
}
show_it_generic(&s);
編譯器會告訴你Selector<&str>
沒有實現(xiàn)std::fmt::Display
,雖然&str
實現(xiàn)了。
這里就是讓人困惑的地方,Selector<&str>
不強制解引用為&str
了么?怎么還會有錯誤?
實際上,你傳遞的參數(shù)類型為&Selector<&str>
,而函數(shù)的參數(shù)為&T
,所以T必須是Selector<&str>
。其實這里是這樣的,T的類型是
Selector<&str>
沒有錯的??赡苓@里需要涉及到Display
的相關內(nèi)容。這里暫時略過。
Rust會檢查T是否滿足Display
約束,因為在檢查約束時并不會應用強制解引用,顯然是無法通過檢查的。
解決辦法也很簡單,一是使用as
操作符來手動解引用, show_it_generic(&s as &str);
;另一種是按編輯器所建議,使用
show_it_generic(&*s);
,這里因為有了*
操作,所以會進行自動解引用操作得到&str
,然后再加上前面的&
就形成了&str
,從而滿足條件T:Display
.
這里有一點點不解的是,明明我們傳遞的是&s
,為什么T的類型為Selector<&str>
呢?
經(jīng)過研究,個人認為:其實T的類型仍然為&Selector<&str>
,我們它把簡化為&U:Display 而,&U:Display的條件為 U:Display,因此給出的錯誤提示為Selector<&str>
未實現(xiàn)Display
,這里我們仔細看這句話就會明白:
help: the trait
std::fmt::Display
is not implemented forSelector<&str>
, which is required by&Selector<&str>: std::fmt::Display
我們的函數(shù)參數(shù)檢查 后面半句,但是后面半句需要的條件為前面半句,因為所以書中講的還是有一些誤導(或者簡化)。
我們可以在函數(shù)體中增加如下代碼:println!("type is {}", std::any::type_name::<T>())
,它用來打印類型名稱,當我們修改代碼使用&s as &str
時,打印出來的類型為&str
而不是str
,這進一步印證了我的猜想。
這里還發(fā)現(xiàn)了上面表述的一點不對,如果我們使用&*s
,打印出來的T
的類型為&&str
,奇怪吧。因為*
號操作符相當于調(diào)用deref
函數(shù)(不是普通引用的解引用時的*,那里的操作是獲取引用指向的值),所以*s
得到的是&str
,你不會得到str
,因為str
是無固定大小類型的,編譯通不過。*s
再加一個&
就得到了&&str
。它是否滿足Display條件呢,Rust會移除&&
直接檢查str
,發(fā)現(xiàn)它是滿足的。標準庫有代碼如下:
impl Display for str {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.pad(self)
}
}
注意,這里沒有使用歸一化,它沒有插入deref
函數(shù)將&&str
變成&str
,因為它只是檢查條件。而Rust又自動實現(xiàn)了文章來源:http://www.zghlxwxcb.cn/news/detail-858473.html
impl<T> Display for &T where T:Display + ?Sized
。所以只要str
實現(xiàn)了Display
,不管前面加多少個&&
都是實現(xiàn)了的。這也正是前面 the trait std::fmt::Display
is not implemented for Selector<&str>
, which is required by &Selector<&str>: std::fmt::Display
的原因,我們可以直接略去前面所有的&&而直接檢查T是否實現(xiàn)了。那么有沒有&T實現(xiàn)了Display而T沒有實現(xiàn)的呢?當然沒有,絕對不可能,看上面的實現(xiàn):``impl Display for &T where T:Display + ?Sized
,很顯然。文章來源地址http://www.zghlxwxcb.cn/news/detail-858473.html
到了這里,關于Rust常用特型之Deref和DerefMut特型的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!