rust语言基础学习: 与借用数据相关的三个trait Borrow, BorrowMut, ToOwned

rust语言基础学习: 与借用数据相关的三个trait Borrow, BorrowMut, ToOwned

2020-07-26
Rust

今天学习Rust中与借用数据相关的三个trait: Borrow, BorrowMutToOwned。 这三个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只适用于从&TT。 ToOwned trait可以从任意类型如&UT的转换。

下面从例子来理解一下。如果一个类型实现了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提供了一种更宽松的方式来将引用转换为拥有的值。

下面是strToOwned的实现:

 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>。

参考 #

© 2024 青蛙小白