Rust 技巧:Borrow Trait

在這篇文章中,我們將探索 Rust trait Borrow,將看到它與類似的 AsRef trait 有何不同。

Borrow 和 AsRef 是什麼?

Borrow 和 AsRef 都是允許我們將其引用轉換爲其他的類型。例如,假設我們有一個 String 對象。可以使用 Borrow 或 AsRef 來獲取對 str 的引用。

let s = String::from("Hello");
let s_ref: &str = s.borrow(); // using Borrow
let s_ref: &str = s.as_ref(); // using AsRef

這是可能的,因爲 String 結構體同時實現了 Borrow 和 AsRef

看一下上面的函數原型,它們看起來是一樣的,都返回 & str 類型。事實上,它們也產生相同的值:

use std::borrow::Borrow;

fn main() {
    let s = String::from("Hello");
    let s1: &str = s.borrow(); // using Borrow
    let s2: &str = s.as_ref(); // using AsRef
    assert_eq!(s1, s2);
}

所以,問題是,它們有什麼不同,什麼時候使用 Borrow?

何時使用 Borrow?

Borrow 是一個 trait,用於作爲另一種類型的規範的借用方式。Borrow 的一個典型用例是,你有一個數據,它封裝了內部類型 T,用於增加功能。例如,String 爲 str 添加了可變性功能,因此就其特性而言,它是 str 的超集。換句話說,String 可以做所有 str 可以做的不可變操作。類似地,Vec 本質上是 [T] 的一個更靈活的版本,所以它同樣可以做 [T] 可以做的所有不可變的事情,以此類推。

實現 Borrow 的一個要求是,借用值的 Hash、Eq 和 Ord 與擁有值的 Hash、Eq 和 Ord 相等。這是與 AsRef trait 的關鍵區別。如果某些數據結構爲 T 實現了 Borrow trait,那麼它的行爲必須與 T 相同。根據這一要求,標準庫 HashMap 的 get() 和 get_mut() 方法使用了 Borrow trait。

注意 K 是鍵,它實現了 Q 的 Borrow trait。K 的典型類型是 String,而 Q 的典型類型是 str。這意味着我們可以傳遞任何可以作爲 K 借用的類型 Q。在我們的例子中,String 和 & str 都實現了 Borrow,所以它們可以用作鍵。

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    let key = String::from("key");
    map.insert(key.clone(), 42);

    // 我們可以使用&String或&str來獲取值
    assert_eq!(map.get("key"), Some(&42)); // &str
   assert_eq!(map.get(&key), Some(&42));  // &String
}

Borrow 和 AsRef 之間的第二個關鍵區別是——Borrow 對任何類型 T 都有一個全面的實現,也就是說,不需要爲 & T 實現 Borrow。思考下面的代碼:

struct MyStruct {
    val: i64,
}

impl AsRef<i64> for MyStruct {
    fn as_ref(&self) -> &i64 {
        &self.val
    }
}

fn print_i64(x: impl AsRef<i64>) {
    println!("{}", x.as_ref());    
}

fn main() {
    let x = MyStruct { val: 314 };
    print_i64(&1);
    print_i64(x);
}

我們希望能夠傳遞數字類型 i64 的直接引用,以及任何行爲類似於 i64 引用的類型。不幸的是,這段代碼無法編譯,因爲沒有爲 & i64 實現 AsRef

error[E0277]: the trait bound `{integer}: AsRef<i64>` is not satisfied
  --> src/main.rs:17:16
   |
17 |     print_i64(&1);
   |     ---------  ^ the trait `AsRef<i64>` is not implemented for `{integer}`
   |     |
   |     required by a bound introduced by this call
   |
   = note: required for `&{integer}` to implement `AsRef<i64>`
note: required by a bound in `print_i64`
  --> src/main.rs:11:22
   |
11 | fn print_i64(x: impl AsRef<i64>) {
   |                      ^^^^^^^^^^ required by this bound in `print_i64`

我們看看 Borrow Trait。另外,Borrow 可以開箱即用。

use std::borrow::Borrow;

struct MyStruct {
    val: i64,
}

impl Borrow<i64> for MyStruct {
    fn borrow(&self) -> &i64 {
        &self.val
    }
}

fn print_i64(x: impl Borrow<i64>) {
    println!("{}", x.borrow());
}

fn main() {
    let x = MyStruct { val: 314 };
    print_i64(&1);
    print_i64(x);
}

可以發現,使用 Borrow Trait 編譯通過。以上就是 Borrow Trait 與 AsRef Trait 的區別。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/98OwGZaEFCrQ2krHUbIGfg