rust漫遊 – 寫時拷貝 Cow<'_, B>
rust漫遊 – 寫時拷貝 Cow<‘_, B>
Cow 是一個寫時複製功能的智能指針,在數據需要修改或者所有權發生變化時使用,多用於讀多寫少的場景。
pub enum Cow<'a, B: ?Sized + 'a>
where
B: ToOwned,
{
/// Borrowed data.
Borrowed(&'a B),
/// Owned data.
Owned(<B as ToOwned>::Owned),
}
數據在寫入的情況下 Cow 才有存在的意義。當借用的數據被修改時,在不破壞原有數據的情況下,克隆一個副本並且在副本上進行修改;這是一種惰性的策略,在真正需要修改時才產生克隆的操作,而並不預先克隆。
關鍵函數
- to_mut(), 獲取所有權數據的可變引用,無所有權時從借用數據中克隆
- into_owned(), 提取所有權數據。
使用
官方示例
官方描述了三種情況
- 借用數據,但是未調用 to_mut(),故不存在 clone 操作
- 借用數據,調用 to_mut(), 發生 clone 操作
- 所有權數據,調用 to_mut(), 不存在 clone 操作,因為具有該數據的所有權
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);
觀察所有權的變化
寫了個地址打印函數,以此來觀察所有權的變化。
fn print_addr(s: &str) {
println!("{}", s);
let mut p = s.as_ptr();
for ch in s.chars() {
println!("\t{:p}\t{}", p, ch);
p = p.wrapping_add(ch.len_utf8());
}
}
借用修改取出所有權
對借用的數據進行修改操作(有可能不會修改,見下文),操作完成後取出所有權 是最常見的用法
以下是一般的借用數據從修改至獲取所有權數據的過程,通過新產生的地址可以看出來存在 clone 操作
{
let s = String::from("AB");
print_addr(&s);
let mut cow = Cow::Borrowed(&s);
cow.to_mut().insert_str(1, "cd");
let sr = cow.into_owned();
print_addr(&sr);
}
// AB
// 0x7fb694c05af0 A
// 0x7fb694c05af1 B
// AcdB
// 0x7fb694c05b00 A
// 0x7fb694c05b01 c
// 0x7fb694c05b02 d
// 0x7fb694c05b03 B
上面的代碼注釋 to_mut() 行後,相當於不會有獲取所有權的需求,這個時候是不應該做修改的,into_owned() 應該棄用轉而使用 as_str() 這類沒有所有權變化的操作.
{
let s = String::from("AB");
print_addr(&s);
let mut cow = Cow::Borrowed(&s);
// cow.to_mut().insert_str(1, "cd"); // 這一行是運行時決定的
let sr = cow.as_str(); // 看後續的使用,若是後續也只是讀操作可以使用 as_str()
print_addr(&sr);
}
在所有權數據上進行修改
在對已具有所有權數據上操作時,字符串的地址未發生改變,未發生 clone 操作
insert_str 為兩個 memcpy 操作,故首地址不會發生變化
{
let s1 = String::from("cd");
print_addr(&s1);
let mut cow1: Cow<'_, String> = Cow::Owned(s1);
cow1.to_mut().insert_str(0, "AB");
let sr1 = cow1.into_owned();
print_addr(&sr1);
}
// cd
// 0x7fb694c05b10 c
// 0x7fb694c05b11 d
// ABcd
// 0x7fb694c05b10 A
// 0x7fb694c05b11 B
// 0x7fb694c05b12 c
// 0x7fb694c05b13 d
實現
Cow 是一個枚舉值,包含了一個借用和所有。可以使用 Cow 的類型需要實現了 ToOwned
trait。
ToOwned
trait 同樣包含了所有權或借用的操作。
- 需要實現 Borrow 借用 trait
- 可以從借用的數據中創建所有權數據或者克隆
相關 trait
pub trait ToOwned {
type Owned: Borrow<Self>; // 需要實現 Borrow triat
pub fn to_owned(&self) -> Self::Owned; // 所有權創建
pub fn clone_into(&self, target: &mut Self::Owned) { ... }
}
pub trait Borrow<Borrowed> where
Borrowed: ?Sized, {
pub fn borrow(&self) -> &Borrowed;
}
Borrow
借用 triat 泛型實現
impl<T: ?Sized> Borrow<T> for T {
fn borrow(&self) -> &T {
self
}
}
to_owned
創建所有權的泛型實現如下,取決該類型是否實現 Clone
tait
impl<T> ToOwned for T
where
T: Clone,
{
type Owned = T;
fn to_owned(&self) -> T {
self.clone()
}
}
方法
to_mut()
獲取所有權的可變引用
- 已獲取所有權直接返回引用
- 借用數據的情況先調用 to_owned() 獲取克隆副本的所有權,並且做一個檢查
ref
關鍵字指示模式匹配為借用而不是移動。
<B as ToOwned>::Owned
表示 B 類型實現了 ToOwned
trait,現使用該 trait 中的 Owned 類型,本質就是B類型本身,但是限制了實現 trait
pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned {
match *self {
Borrowed(borrowed) => {
*self = Owned(borrowed.to_owned());
match *self {
Borrowed(..) => unreachable!(),
Owned(ref mut owned) => owned,
}
}
Owned(ref mut owned) => owned,
}
}
into_owned()
取出 Cow 中的所有權數據,當為獲取所有權時,進行 clone 操作
pub fn into_owned(self) -> <B as ToOwned>::Owned {
match self {
Borrowed(borrowed) => borrowed.to_owned(),
Owned(owned) => owned,
}
}
deref
由於 Cow 也實現了 Deref
trait, 支持解引用強制多態,實現如下。
impl<B: ?Sized + ToOwned> Deref for Cow<'_, B> {
type Target = B;
fn deref(&self) -> &B {
match *self {
Borrowed(borrowed) => borrowed,
Owned(ref owned) => owned.borrow(),
}
}
}
上面代碼的 print_addr 的參數既可以是 &str, 也可以為 &Cow.