Borrow和BorrowMut trait 是Rust標準庫std:borrow 模塊中用于處理借用數據的trait,通過實現Borrow 和BorrowMut trait可以讓一個類型被借用成不同的引用。
1、AsRef VS Borrow 轉換與借用
Borrow trait的定義如下:
pub trait Borrow
對比一下 AsRef:
pub trait AsRef
可以看出AsRef的定義和Borrow的定義十分相像,那么既然有了AsRef trait,為啥還有Borrow trait的存在呢?
AsRef trait用來表示**類型轉換, **Borrow trait用來表示 借用數據, 在Rust中,為不同的語義不同的使用情況提供不同的類型表示是很常見的。
一個類型通過實現 Borrow trait,在 borrow()方法中提供對 T 的引用/借用,表達的語義是可以作為某個類型 T被借用,而非轉換。一個類型可以自由地借用為幾個不同的類型,也可以用可變的方式借用。
Borrow trait這類特性存在的意義旨在于解決特定領域的問題,例如在 Hashset,HashMap,BTreeSet,BtreeMap 中使用 &str 查詢 String 類型的鍵。
所以 Borrow 和 AsRef 如何選呢?
- 當你想把不同類型的借用進行統一抽象,或者當你要建立一個數據結構,以同等方式處理自擁有值(ownered)和借用值(borrowed)時,例如散列(hash)和比較(compare)時,選擇Borrow。
- 當你想把某個類型直接轉換為引用,并且你正在編寫通用代碼時,選擇AsRef。比較簡單的情況。
2、Borrow 和 BorrowMut 實現借用數據
BorrowMut trait的定義如下:
pub trait BorrowMut
BorrowMut trait類似于Borrow,用于可變借用。BorrowMut trait繼承自Borrowed trait。因此,一個類型如果實現了BorrowMut trait,則它也實現了Borrowed trait。
從Borrow trait的文檔中看,它對類型實現借用數據強加了更多的限制:
如果一個類型U實現了Borrow,在為U實現額外的trait(特別是實現Eq, Ord, Hash)的時候應該實現與T相同的行為。
這句話可以從例子理解,例如String
實現了Borrow
,那么在為String實現Eq, Ord, Hash等trait時,實現的行為應該與str實現相同。
Borrow trait的文檔中給了一個HashMap的例子,HashMap利用了String實現Borrow時,String和str對Eq, Hash的實現是相同的這一點,可以讓我們可以使用&str
作為Key來訪問一個HashMap。HashMap的定義如下:
use std::borrow::Borrow;
use std:#:Hash;
pub struct HashMap
可以看到get方法的參數k類型是&Q
,而不是&K
。Q的trait bound是Hash + Eq + ?Sized
,而K的trait bound是Borrow
。這里K實現了Borrow, Hash, Eq等作為額外的trait,Borrow trait約定的限制是K和Q對這些額外trait的實現行為是相同的。
上面例子說明在寫通用的代碼時,如果依賴了Hash, Eq等這些額外的trait的相同的行為,會使用Borrow trait。這些trait作為trait bounds出現。
再看一個例子:
// 這個結構體能不能作為 HashMap 的 key?
pub struct CaseInsensitiveString(String);
// 它實現 Eq 沒有問題
impl PartialEq for CaseInsensitiveString {
fn eq(&self, other: &Self) -> bool {
// 但這里比較是要求忽略了 ascii 大小寫
self.0.eq_ignore_ascii_case(&other.0)
}
}
impl Eq for CaseInsensitiveString { }
// 實現 Hash 沒有問題
// 但因為 eq 忽略大小寫,那么 hash 計算也必須忽略大小寫
impl Hash for CaseInsensitiveString {
fn hash
但是 CaseInsensitiveString 可以實現 Borrow嗎?
很顯然,CaseInsensitiveString 和 str 對 Hash 的實現不同,str 是不會忽略大小寫的。因此,CaseInsensitiveString不能實現Borrow,所以 CaseInsensitiveString 不能作為 HashMap 的 key,編譯器就可以通過 Borrow trait 來識別這種情況了。但是 CaseInsensitiveString 完全可以實現 AsRef 。這就是 Borrow 和 AsRef 的區別,Borrow 更加嚴格一些,并且表示的語義和 AsRef 完全不同。
3、Borrow和BorrowMut的blanket implement
對于Borrow
和BorrowMut
,Rust為泛型T和&T自動實現了這兩個trait。
#[stable(feature = "rust1", since = "1.0.0")]
impl
4. 為什么Borrow和BorrowMut被定義為泛型trait
被定義為泛型trait,這樣就可以讓同一個類型同時有多個Borrow和BorrowMut trait的實現, 這樣這個類型就可以同時讓多個不同的引用類型作為它的借用。
例2:
fn main() {
let s = String::from("hello");
let s1: &str = s.borrow();
let s2: &String = s.borrow();
println!("s1: {s1:p}, s2: {s2:p}"); // s1: 0x7ff58ec05bc0, s2: 0x7ffee9169fe0
}
例2中引用類型&str
和&String
都可以作為String
類型的借用。即通過實現Borrow trait可以讓一個類型被借用成不同的引用。
總結
Borrow trait是用來表示 借用數據 ,一個類型通過實現 Borrow trait,在 borrow()方法中提供對 T 的引用/借用,表達的語義是可以作為某個類型 T被借用,而非轉換。一個類型可以自由地借用為幾個不同的類型,也可以用可變的方式借用。
1 如果一個類型實現了Borrow
,那么這個類型的borrow
方法可以從其借用一個&T
。
2 可以將 Borrow
和 BorrowMut
視作 AsRef
和 AsMut
的嚴格版本,其返回的引用 &T 具有與 Self 相同的 Eq,Hash 和 Ord 的實現。
注:理解Borrow trait這類特性存在的意義,有助于我們揭開 HashSet,HashMap,BTreeSet 和 BTreeMap 中某些方法的實現的神秘面紗。但是在實際應用中,幾乎沒有什么地方需要我們去實現這樣的特性,因為再難找到一個需要我們對一個值再創造一個“借用”版本的類型的場景了。對于某種類型 T ,&T 就能解決 99.9% 的問題了,且 T: Borrow 已經被一攬子泛型實現對 T 實現了,所以我們無需手動實現它,也無需去實現某種的對 U 有 T: Borrow 了。
-
Hash算法
+關注
關注
0文章
43瀏覽量
7382 -
rust語言
+關注
關注
0文章
57瀏覽量
3009
發布評論請先 登錄
相關推薦
評論