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