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

rust語言基礎學習: 智能指針之Cow
評論