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浏览量
7383 -
rust语言
+关注
关注
0文章
57浏览量
3009
发布评论请先 登录
相关推荐
评论