1 - Rust标准库中的借用介绍
https://doc.rust-lang.org/std/borrow/index.html
用于处理借用(borrow)数据的模块。
Cow 枚举
pub enum Cow<'a, B> where
B: 'a + ToOwned + ?Sized, {
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
clone-on-write 智能指针。
cow = Clone On Write
类型Cow是一个智能指针,提供了 clone-on-write 功能:它可以封装并提供对借用数据的不可变访问,当需要可变或所有权时,可以延迟克隆数据。该类型是通过 Borrow trait 来处理一般的借用数据。
Cow 实现了 Deref,这意味着你可以直接在它所封装的数据上调用非可变方法。如果需要改变,to_mut 将获得一个到拥有值的可变引用,必要时进行克隆。
use std::borrow::Cow;
fn abs_all(input: &mut Cow<[i32]>) {
for i in 0..input.len() {
let v = input[i];
if v < 0 {
// Clones into a vector if not already owned.
input.to_mut()[i] = -v;
}
}
}
// No clone occurs because `input` doesn't need to be mutated.
let slice = [0, 1, 2];
let mut input = Cow::from(&slice[..]);
abs_all(&mut input);
// Clone occurs because `input` needs to be mutated.
let slice = [-1, 0, 1];
let mut input = Cow::from(&slice[..]);
abs_all(&mut input);
// No clone occurs because `input` is already owned.
let mut input = Cow::from(vec![-1, 0, 1]);
abs_all(&mut input);
Borrow Trait
借用数据的特征。
在Rust中,常见的是为不同的用例提供不同的类型表示。例如,可以通过诸如Box
这些类型通过对该数据的类型的引用来提供对底层数据的访问。它们被说成是 “borrow/借用 “该类型。例如,Box
类型表示它们可以通过实现Borrow
此外,在为附加的特征提供实现时,需要考虑到它们是否应该与底层类型的行为完全相同,因为它们作为底层类型的表示方式。当依赖这些额外的特征实现的行为相同时,通用代码通常会使用Borrow
特别是Eq、Ord和Hash对于借用值和拥有值必须是等价的:x.borrow()==y.borrow()
应该给出与 x==y
相同的结果。
如果通用代码仅仅需要对所有能够提供相关类型T的引用的类型进行工作,那么通常最好使用AsRef
例子:
作为一个数据集合,HashMap<K, V>同时拥有键和值。如果键的实际数据被封装在某种管理类型中,但是,应该还是可以使用对键的数据引用来搜索值。例如,如果键是一个字符串,那么它很可能是用哈希图作为String存储的,而应该可以用&str来搜索。因此,insert需要对String进行操作,而get需要能够使用&str。
稍微简化一下,HashMap<K, V>的相关部分看起来像这样。
use std::borrow::Borrow;
use std::hash::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
{
// ...
}
}
整个hash map是通用于一个key类型K,由于这些key是和hash map一起存储的,所以这个类型必须拥有该密钥的数据。当插入一个键-值对时,map被赋予了这样一个K,需要找到正确的hash map,并根据这个K来检查这个键是否已经存在。
然而,当在地图中搜索一个值时,必须提供一个K的引用作为要搜索的键,这就需要始终创建这样一个拥有的值。对于字符串键,这就意味着需要创建一个String值来搜索只有str的情况。
相反,get方法是通用于底层键数据的类型,在上面的方法签名中称为Q。它通过要求K:Borrow来说明K借入为Q。通过额外要求Q:Hash + Eq,它表明要求K和Q都有Hash和Eq特征的实现,产生相同的结果。
get的实现特别依赖于Hash的相同实现,通过在Q值上调用Hash::hash来确定密钥的哈希桶,即使它是根据从K值计算出的哈希值插入了key。
因此,如果一个包裹Q值的K产生的散列值与Q不同,那么hash map就会被破坏。
pub struct CaseInsensitiveString(String);
impl PartialEq for CaseInsensitiveString {
fn eq(&self, : &Self) -> bool {
self.0.eq_ignore_ascii_case(&other.0)
}
}
impl Eq for CaseInsensitiveString { }
因为两个相等的值需要产生相同的散列值,所以Hash的实现也需要忽略ASCII 大小写:
impl Hash for CaseInsensitiveString {
fn hash<H: Hasher>(&self, state: &mut H) {
for c in self.0.as_bytes() {
c.to_ascii_lowercase().hash(state)
}
}
}
CaseInsensitiveString可以实现Borrow
BorrowMut Trait
用于可变地借用数据的特征。
作为 Borrow
ToOwned Trait
用于借用数据的Clone泛化。
一些类型使其可以从借用数据到拥有数据,通常是通过实现Clone特征。但Clone只适用于从&T到T。ToOwned特征将Clone泛化为从给定类型的任何借入数据中构造出拥有的数据。