为什么Borrow和BorrowMut被定义为泛型trait呢?

电子说

1.3w人已加入

描述

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

对于BorrowBorrowMut,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 可以将 BorrowBorrowMut视作 AsRefAsMut的严格版本,其返回的引用 &T 具有与 Self 相同的 Eq,Hash 和 Ord 的实现。

注:理解Borrow trait这类特性存在的意义,有助于我们揭开 HashSet,HashMap,BTreeSet 和 BTreeMap 中某些方法的实现的神秘面纱。但是在实际应用中,几乎没有什么地方需要我们去实现这样的特性,因为再难找到一个需要我们对一个值再创造一个“借用”版本的类型的场景了。对于某种类型 T ,&T 就能解决 99.9% 的问题了,且 T: Borrow 已经被一揽子泛型实现对 T 实现了,所以我们无需手动实现它,也无需去实现某种的对 U 有 T: Borrow 了。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分