rust语言基础学习: 与借用数据相关的三个trait Borrow, BorrowMut, ToOwned
2020-07-26
今天学习Rust中与借用数据相关的三个trait: Borrow
, BorrowMut
和ToOwned
。
这三个trait在Rust标准库module std::borrow
中。
std:borrow
是Rust标准库中用于处理借用数据的moudle。这个module中除了Borrow, BorrowMut, ToOwned三个trait
外,还有一个Cow
enum。不过今天我们先只学习这三个trait。
1. Borrow trait #
Borrow trait的定义如下:
1pub trait Borrow<Borrowed>
2where
3 Borrowed: ?Sized,
4{
5 fn borrow(&self) -> &Borrowed;
6}
如果一个类型实现了Borrow<T>
,那么这个类型的borrow
方法可以从其借用一个&T
。看到这里你会问这和前面学过的AsRef trait有什么区别呢?
AsRef trait的定义如下:
1pub trait AsRef<T>
2where
3 T: ?Sized,
4{
5 fn as_ref(&self) -> &T;
6}
可以看出AsRef的定义和Borrow的定义十分相像,那么既然有了AsRef trait,为啥还有Borrow trait的存在呢?
从Borrow trait的文档中看,它对类型实现借用数据强加了更多的限制:
如果类型U实现了Borrow<T>,在为U实现额外的trait(特别是实现Eq, Ord, Hash)的时候应该实现与T相同的行为。
这句话可以从例子理解,例如String
实现了Borrow<str>
,那么在为String实现Eq, Ord, Hash等trait时,实现的行为应该与str实现相同。
Borrow trait的文档中给了一个HashMap的例子,HashMap利用了String实现Borrow<str>时,String和str对Eq, Hash的实现是相同的这一点,可以让我们可以使用&str
作为Key来访问一个HashMap<String, _>。
HashMap的定义如下:
1use std::borrow::Borrow;
2use std::hash::Hash;
3
4pub struct HashMap<K, V> {
5 // fields omitted
6}
7
8impl<K, V> HashMap<K, V> {
9 pub fn insert(&self, key: K, value: V) -> Option<V>
10 where K: Hash + Eq
11 {
12 // ...
13 }
14
15 pub fn get<Q>(&self, k: &Q) -> Option<&V>
16 where
17 K: Borrow<Q>,
18 Q: Hash + Eq + ?Sized
19 {
20 // ...
21 }
22}
可以看到get方法的参数k类型是&Q
,而不是&K
。Q的trait bound是Hash + Eq + ?Sized
,而K的trait bound是Borrow<Q>
。
这里K实现了Borrow<Q>, Hash, Eq等作为额外的trait,Borrow trait约定的限制是K和Q对这些额外trait的实现行为是相同的。
上面例子说明在写通用的代码时,如果依赖了Hash, Eq等这些额外的trait的相同的行为,会使用Borrow trait。这些trait作为trait bounds出现。
2. BorrowMut trait #
BorrowMut trait的定义如下:
1pub trait BorrowMut<Borrowed>: Borrow<Borrowed>
2where
3 Borrowed: ?Sized,
4{
5 fn borrow_mut(&mut self) -> &mut Borrowed;
6}
BorrowMut trait类似于Borrow,用于可变借用。BorrowMut trait继承自Borrowed trait。因此,一个类型如果实现了BorrowMut trait,则它也实现了Borrowed trait。
Vec<T>
实现了BorrowMut<[T]>
,所以可以&[T]
可以作为Vec<T>
借用。
例1:
1use std::borrow::BorrowMut;
2
3fn check<T: BorrowMut<[i32]>>(mut v: T) {
4 assert_eq!(&mut [1, 2, 3], v.borrow_mut());
5}
6
7fn main() {
8 let mut v = vec![1, 2, 3];
9
10 assert_eq!(
11 &mut [1, 2, 3],
12 <Vec<i32> as BorrowMut<[i32]>>::borrow_mut(&mut v)
13 );
14
15 check(v);
16}
3. Borrow和BorrowMut的blanket implement #
对于Borrow<T>
和BorrowMut<T>
,Rust为泛型T和&T自动实现了这两个trait。
1#[stable(feature = "rust1", since = "1.0.0")]
2impl<T: ?Sized> Borrow<T> for T {
3 #[rustc_diagnostic_item = "noop_method_borrow"]
4 fn borrow(&self) -> &T {
5 self
6 }
7}
8
9#[stable(feature = "rust1", since = "1.0.0")]
10impl<T: ?Sized> BorrowMut<T> for T {
11 fn borrow_mut(&mut self) -> &mut T {
12 self
13 }
14}
15
16#[stable(feature = "rust1", since = "1.0.0")]
17impl<T: ?Sized> Borrow<T> for &T {
18 fn borrow(&self) -> &T {
19 &**self
20 }
21}
22
23#[stable(feature = "rust1", since = "1.0.0")]
24impl<T: ?Sized> Borrow<T> for &mut T {
25 fn borrow(&self) -> &T {
26 &**self
27 }
28}
29
30#[stable(feature = "rust1", since = "1.0.0")]
31impl<T: ?Sized> BorrowMut<T> for &mut T {
32 fn borrow_mut(&mut self) -> &mut T {
33 &mut **self
34 }
35}
4. 为什么Borrow和BorrowMut被定义为泛型trait #
上面学习了Borrow和BorrowMut,为什么它们被定义为泛型的trait呢?
被定义为泛型trait,这样就可以让同一个类型同时有多个实现Borrow和BorrowMut trait的实现, 这样这个类型就可以同时让多个不同的引用类型作为它的借用。
例2:
1fn main() {
2 let s = String::from("hello");
3 let s1: &str = s.borrow();
4 let s2: &String = s.borrow();
5 println!("s1: {s1:p}, s2: {s2:p}"); // s1: 0x7ff58ec05bc0, s2: 0x7ffee9169fe0
6}
例2中引用类型&str
和&String
都可以作为String
类型的借用。即通过实现Borrow trait可以让一个类型被借用成不同的引用。
5. ToOwned trait #
理解了Borrow和BorrowMut trait,就可以学习ToOwned trait。
ToOwned trait的定义如下:
1pub trait ToOwned {
2 type Owned: Borrow<Self>;
3 fn to_owned(&self) -> Self::Owned;
4
5 fn clone_into(&self, target: &mut Self::Owned) { ... }
6}
ToOwned是一个带有关联类型的trait,type Owned: Borrow<Self>;
中的Owned就是关联类型,需要ToOwned的实现者定义。
这里关联类型Owned不能是任意类型,它必须满足Borrow<Self>
trait。
也就是说一个类型实现了ToOwned trait,它的to_owned
方法将其从借用转换到Owned, Owned要求必须满足Borrow<Self>
trait。
Rust为什么要提供ToOwned trait呢。这是因为,一些类型可以从借用转换为拥有通常是通过实现Clone trait,但Clone只适用于从&T
到T
。
ToOwned trait可以从任意类型如&U
到T
的转换。
下面从例子来理解一下。如果一个类型实现了std::clone::Clone
trait,则从这个类型的引用,生成并拥有其所指向对象副本的通用方式是调用clone
。
例如我们可以从一个&String
clone得到String
,可以从一个&Vec<i32>
clone得到Vec<i32>
。
但是如果我们想clone一个&str
或者clone一个&[i32]
呢?
根据Clone trait的定义,克隆&T
必须返回T
。但str
和[i32]
都是?Sized
。而ToOwned trait提供了一种更宽松的方式来将引用转换为拥有的值。
下面是str
对ToOwned
的实现:
1impl ToOwned for str {
2 type Owned = String;
3 #[inline]
4 fn to_owned(&self) -> String {
5 unsafe { String::from_utf8_unchecked(self.as_bytes().to_owned()) }
6 }
7
8 fn clone_into(&self, target: &mut String) {
9 let mut b = mem::take(target).into_bytes();
10 self.as_bytes().clone_into(&mut b);
11 *target = unsafe { String::from_utf8_unchecked(b) }
12 }
13}
可以看到str实现ToOwned,关联类型Owned为String,根据要求String实现了Borrow<str>。