Rust中與借用數據相關的三個trait: Borrow
, BorrowMut
和ToOwned
。理解了這三個trait之后,再學習Rust中能夠實現寫時克隆的智能指針Cow<'a B>
。寫時克隆(Copy on Write)技術是一種程序中的優化策略,多應用于讀多寫少的場景。主要思想是創建對象的時候不立即進行復制,而是先引用(借用)原有對象進行大量的讀操作,只有進行到少量的寫操作的時候,才進行復制操作,將原有對象復制后再寫入。這樣的好處是在讀多寫少的場景下,減少了復制操作,提高了性能。
1.Cow的定義
Cow是Rust提供的用于實現 ** 寫時克隆 (Copy on Write)** 的智能指針。
定義如下:
pub enum Cow<'a, B>
where
B: 'a + ToOwned + ?Sized,
{ /// 用于包裹引用(通用引用) Borrowed(&'a B), /// 用于包裹所有者; Owned(::Owned),
}
**
從Cow的定義看,它是一個enum,包含一個對類型B的只讀引用,或者包含一個擁有類型B的所有權的數據。
可以看到Cow是一個枚舉體,包括兩個可選值,一個是“借用”(只讀),一個是“所有”(可讀寫)。具體含義是:以不可變的方式訪問借用內容,在需要可變借用或所有權的時候再克隆一份數據。
Cow trait的泛型參數約束比較復雜,下面詳細介紹一下:
pub enum Cow<'a, B>
中的'a
是生命周期標注,表示Cow是一個包含引用的enum。泛型參數B需要滿足'a + ToOwned + ?Sized
。即當Cow內部類型B的生命周期為’a時,Cow自己的生命周期也是’a。- 泛型參數B除了生命周期注解’a外,還有
ToOwned
和?Sized
兩個約束 ?Sized
表示B是可變大小類型ToOwned
表示可以把借用的B數據復制出一個擁有所有權的數據- 這個enum里的
Borrowed(&'a B)
表示返回借用數據是B類型的引用,引用的生命周期為’a - 因為B滿足ToOwned trait,所以
Owned(::Owned)
中的::Owned
表示把B強制轉換成ToOwned,并訪問ToOwned內部的關聯類型Owned
2.智能指針Cow
了解了Cow這個用于寫時克隆的智能指針的定義,它在定義上是一個枚舉類型,有兩個可選值:
Borrowed
用來包裹對象的引用Owned
用來包裹對象的所有者
Cow 在這里就是表示借用的和自有的,但只能出現其中的一種情況。
下面從智能指針的角度來學習Cow。先回顧一下智能指針的特征:
- 大多數情況下智能指針具有它所指向數據的所有權
- 智能指針是一種數據結構,一般使用結構體實現
- 智能指針數據類型的顯著特征是實現Deref和Drop trait
當然,上面智能指針的特征都不是強制的,我們來看一下Cow做為智能指針是否有上面的這些特征:
- Cow枚舉的Owned的可選值,可以返回一個擁有所有權的數據
- Cow作為智能指針在定義上是使用枚舉類型實現的
- Cow實現的Deref trait,Cow沒有實現Drop trait
我們知道,如果一個類型實現了Deref trait,那么就可以將類型當做常規引用類型使用。
下面是Cow對Deref trait的實現:
impl
**
在實現上很簡單,match表達式中根據self是Borrowed還是Owned,分別取其內容,然后生成引用:
- 對于Borrowed選項,其內容就是引用
- 對于Owned選項,其內容是泛型參數B實現ToOwned中的關聯類型Owned,而Owned是實現Borrow trait的,所以owned.borrow()可以獲得引用
Cow<'a, B>
通過對Deref trait的實現,就變得很厲害了,因為智能指針通過Deref的實現就可以獲得常規引用的使用體驗。對Cow<'a, B>
的使用,在體驗上和直接&B
基本上時一致的。
通過函數或方法傳參時Deref強制轉換(Deref coercion)功能,可以使用Cow<'a, B>
直接調用 B的不可變引用方法 (&self)。
例1:
use std::borrow::Cow;
fn main() {
let hello = "hello world";
let c = Cow::Borrowed(hello);
println!("{}", c.starts_with("hello"));
}
例1中變量c使用Cow包裹了一個&str
引用,隨后直接調用了str的start_with方法。
3.Cow的方法
接下來看一下智能指針Cow都提供了哪些方法供我們使用。
2個關鍵函數:
- to_mut(): 就是返回數據的可變引用,如果沒有數據的所有權,則復制擁有后再返回可變引用;
- into_owned(): 獲取一個擁有所有權的對象(區別與引用),如果當前是借用,則發生復制,創建新的所有權對象,如果已擁有所有權,則轉移至新對象。
impl
pub fn into_owned(self) -> ::Owned
: into_owned
方法用于抽取Cow所包裹類型B的所有者權的數據,如果它還沒有所有權數據將會克隆一份。在一個Cow::Borrowed
上調用into_owned
,會克隆底層數據并成為Cow::Owned
。在一個Cow::Owned
上調用into_owned不會發生克隆操作。
**
例2:
use std::borrow::Cow;
fn main() {
let s = "Hello world!";
// 在一個`Cow::Borrowed`上調用`into_owned`,會克隆底層數據并成為`Cow::Owned`。
let cow1 = Cow::Borrowed(s);
assert_eq!(cow1.into_owned(), String::from(s));
// 在一個`Cow::Owned`上調用into_owned不會發生克隆操作。
let cow2: Cow<str> = Cow::Owned(String::from(s));
assert_eq!(cow2.into_owned(), String::from(s));
}
pub fn to_mut(&mut self) -> &mut ::Owned
: 從Cow所包裹類型B的所有者權的數據獲得一個可變引用,如果它還沒有所有權數據將會克隆一份再返回其可變引用。
**
例3:
use std::borrow::Cow;
fn main() {
let mut cow = Cow::Borrowed("foo");
cow.to_mut().make_ascii_uppercase();
assert_eq!(cow, Cow::Owned(String::from("FOO")) as Cow<str>);
}
4.Cow的使用場景
使用Cow主要用來減少內存的分配和復制,因為絕大多數的場景都是讀多寫少。使用Cow可以在需要些的時候才做一次內存復制,這樣就很大程度減少了內存復制次數。
先來看官方文檔中的例子。
例4:
use std::borrow::Cow;
fn main() {
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);
}
最后再來看一下例子。
例5:
use std::borrow::Cow;
const SENSITIVE_WORD: &str = "bad";
fn remove_sensitive_word<'a>(words: &'a str) -> Cow<'a, str> {
if words.contains(SENSITIVE_WORD) {
Cow::Owned(words.replace(SENSITIVE_WORD, ""))
} else {
Cow::Borrowed(words)
}
}
fn remove_sensitive_word_old(words: &str) -> String {
if words.contains(SENSITIVE_WORD) {
words.replace(SENSITIVE_WORD, "")
} else {
words.to_owned()
}
}
fn main() {
let words = "I'm a bad boy.";
let new_words = remove_sensitive_word(words);
println!("{}", new_words);
let new_words = remove_sensitive_word_old(words);
println!("{}", new_words);
}
例5的需求是實現一個字符串敏感詞替換函數,從給定的字符串替換掉預制的敏感詞。
例子中給出了remove_sensitive_word
和remove_sensitive_word_old
兩種實現,前者的返回值使用了Cow,后者返回值使用的是String。仔細分析一下,很明顯前者的實現效率更高。因為如果輸入的字符串中沒有敏感詞時,前者Cow::Borrowed(words)
不會發生堆內存的分配和拷貝,后者words.to_owned()
會發生一次堆內存的分配和拷貝。
試想一下,如果例5的敏感詞替換場景,是大多數情況下都不會發生替換的,即讀多寫少的場景,remove_sensitive_word實現中使用Cow作為返回值就在很多程度上提高了系統的效率。
總結
Cow 的設計目的是提高性能(減少復制)同時增加靈活性,因為大部分情況下,多用于讀多寫少的場景。利用 Cow,可以用統一,規范的形式實現,需要寫的時候才做一次對象復制。
- 創建語義:Cow::Borrowed(v) 或者 Cow::Owned(v)
- 獲得本體:Cow::into_owned(),得到具有 所有權的值 ,如果之前Cow是Borrowed借用狀態,調用into_owned將會克隆,如果已經是Owned狀態,將不會克隆
- 可變借用:Cow::to_mut(),得到一個具有所有權的值的 可變引用 ,注意在已經具有所有權的情況下,也可以調用to_mut但不會產生新的克隆,多次調用to_mut只會產生一次克隆
-
COW
+關注
關注
0文章
4瀏覽量
8009 -
rust語言
+關注
關注
0文章
57瀏覽量
3009
發布評論請先 登錄
相關推薦
評論