今天学习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的定义如下:

1
2
3
4
5
6
pub trait Borrow<Borrowed> 
where
    Borrowed: ?Sized, 
{
    fn borrow(&self) -> &Borrowed;
}

如果一个类型实现了Borrow<T>,那么这个类型的borrow方法可以从其借用一个&T。看到这里你会问这和前面学过的AsRef trait有什么区别呢?

AsRef trait的定义如下:

1
2
3
4
5
6
pub trait AsRef<T> 
where
    T: ?Sized, 
{
    fn as_ref(&self) -> &T;
}

可以看出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的定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::borrow::Borrow;
use std:#️⃣:Hash;

pub struct HashMap<K, V> {
    // fields omitted
}

impl<K, V> HashMap<K, V> {
    pub fn insert(&self, key: K, value: V) -> Option<V>
    where K: Hash + Eq
    {
        // ...
    }

    pub fn get<Q>(&self, k: &Q) -> Option<&V>
    where
        K: Borrow<Q>,
        Q: Hash + Eq + ?Sized
    {
        // ...
    }
}

可以看到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的定义如下:

1
2
3
4
5
6
pub trait BorrowMut<Borrowed>: Borrow<Borrowed> 
where
    Borrowed: ?Sized, 
{
    fn borrow_mut(&mut self) -> &mut Borrowed;
}

BorrowMut trait类似于Borrow,用于可变借用。BorrowMut trait继承自Borrowed trait。因此,一个类型如果实现了BorrowMut trait,则它也实现了Borrowed trait。

Vec<T>实现了BorrowMut<[T]>,所以可以&[T]可以作为Vec<T>借用。

例1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::borrow::BorrowMut;

fn check<T: BorrowMut<[i32]>>(mut v: T) {
    assert_eq!(&mut [1, 2, 3], v.borrow_mut());
}

fn main() {
    let mut v = vec![1, 2, 3];

    assert_eq!(
        &mut [1, 2, 3],
        <Vec<i32> as BorrowMut<[i32]>>::borrow_mut(&mut v)
    );

    check(v);
}

3. Borrow和BorrowMut的blanket implement

对于Borrow<T>BorrowMut<T>,Rust为泛型T和&T自动实现了这两个trait。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Borrow<T> for T {
    #[rustc_diagnostic_item = "noop_method_borrow"]
    fn borrow(&self) -> &T {
        self
    }
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> BorrowMut<T> for T {
    fn borrow_mut(&mut self) -> &mut T {
        self
    }
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Borrow<T> for &T {
    fn borrow(&self) -> &T {
        &**self
    }
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Borrow<T> for &mut T {
    fn borrow(&self) -> &T {
        &**self
    }
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> BorrowMut<T> for &mut T {
    fn borrow_mut(&mut self) -> &mut T {
        &mut **self
    }
}

4. 为什么Borrow和BorrowMut被定义为泛型trait

上面学习了Borrow和BorrowMut,为什么它们被定义为泛型的trait呢?

被定义为泛型trait,这样就可以让同一个类型同时有多个实现Borrow和BorrowMut trait的实现, 这样这个类型就可以同时让多个不同的引用类型作为它的借用。

例2:

1
2
3
4
5
6
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可以让一个类型被借用成不同的引用

5. ToOwned trait

理解了Borrow和BorrowMut trait,就可以学习ToOwned trait。

ToOwned trait的定义如下:

1
2
3
4
5
6
pub trait ToOwned {
    type Owned: Borrow<Self>;
    fn to_owned(&self) -> Self::Owned;

    fn clone_into(&self, target: &mut Self::Owned) { ... }
}

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的实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
impl ToOwned for str {
    type Owned = String;
    #[inline]
    fn to_owned(&self) -> String {
        unsafe { String::from_utf8_unchecked(self.as_bytes().to_owned()) }
    }

    fn clone_into(&self, target: &mut String) {
        let mut b = mem::take(target).into_bytes();
        self.as_bytes().clone_into(&mut b);
        *target = unsafe { String::from_utf8_unchecked(b) }
    }
}

可以看到str实现ToOwned,关联类型Owned为String,根据要求String实现了Borrow<str>。

参考