Cow
直譯為奶牛!開玩笑。
Cow 是一個枚舉類型,通過 use std::borrow::Cow; 引入。它的定義是 Clone-on-write,即寫時克隆。本質上是一個智能指針。
它有兩個可選值:
Borrowed,用於包裹對象的引用(通用引用);Owned,用於包裹對象的所有者;
Cow 提供
- 對此對象的不可變訪問(比如可直接調用此對象原有的不可變方法);
- 如果遇到需要修改此對象,或者需要獲得此對象的所有權的情況,
Cow提供方法做克隆處理,並避免多次重複克隆。
Cow 的設計目的是提高性能(減少複製)同時增加靈活性,因為大部分情況下,業務場景都是讀多寫少。利用 Cow,可以用統一,規範的形式實現,需要寫的時候才做一次對象複製。這樣就可能會大大減少複製的次數。
它有以下幾個要點需要掌握:
Cow<T>能直接調用T的不可變方法,因為Cow這個枚舉,實現了Deref;- 在需要寫
T的時候,可以使用.to_mut()方法得到一個具有所有權的值的可變借用;- 注意,調用
.to_mut()不一定會產生克隆; - 在已經具有所有權的情況下,調用
.to_mut()有效,但是不會產生新的克隆; - 多次調用
.to_mut()只會產生一次克隆。
- 注意,調用
- 在需要寫
T的時候,可以使用.into_owned()創建新的擁有所有權的對象,這個過程往往意味著內存拷貝並創建新對象;- 如果之前
Cow中的值是借用狀態,調用此操作將執行克隆; - 本方法,參數是
self類型,它會“吃掉”原先的那個對象,調用之後原先的對象的生命週期就截止了,在Cow上不能調用多次;
- 如果之前
舉例
.to_mut() 舉例
#![allow(unused)] fn main() { use std::borrow::Cow; let mut cow: Cow<[_]> = Cow::Owned(vec![1, 2, 3]); let hello = cow.to_mut(); assert_eq!(hello, &[1, 2, 3]); }
.into_owned() 舉例
#![allow(unused)] fn main() { use std::borrow::Cow; let cow: Cow<[_]> = Cow::Owned(vec![1, 2, 3]); let hello = cow.into_owned(); assert_eq!(vec![1, 2, 3], hello); }
綜合舉例
#![allow(unused)] fn main() { 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 the first time (if not already owned) input.to_mut()[i] = -v; } } } }
Cow 在函數返回值上的應用實例
題目:寫一個函數,過濾掉輸入的字符串中的所有空格字符,並返回過濾後的字符串。
對這個簡單的問題,不用思考,我們都可以很快寫出代碼:
#![allow(unused)] fn main() { fn remove_spaces(input: &str) -> String { let mut buf = String::with_capacity(input.len()); for c in input.chars() { if c != ' ' { buf.push(c); } } buf } }
設計函數輸入參數的時候,我們會停頓一下,這裡,用 &str 好呢,還是 String 好呢?思考一番,從性能上考慮,有如下結論:
- 如果使用
String, 則外部在調用此函數的時候,- 如果外部的字符串是
&str,那麼,它需要做一次克隆,才能調用此函數; - 如果外部的字符串是
String,那麼,它不需要做克隆,就可以調用此函數。但是,一旦調用後,外部那個字符串的所有權就被move到此函數中了,外部的後續代碼將無法再使用原字符串。
- 如果外部的字符串是
- 如果使用
&str,則不存在上述兩個問題。但可能會遇到生命週期的問題,需要注意。
繼續分析上面的例子,我們發現,在函數體內,做了一次新字符串對象的生成和拷貝。
讓我們來仔細分析一下業務需求。最壞的情況下,如果字符串中沒有空白字符,那最好是直接原樣返回。這種情況做這樣一次對象的拷貝,完全就是浪費了。
於是我們心想改進這個算法。很快,又遇到了另一個問題,返回值是 String 的嘛,我不論怎樣,要把 &str 轉換成 String 返回,始終都要經歷一次複製。於是我們快要放棄了。
好吧,Cow君這時出馬了。奶牛君很快寫出瞭如下代碼:
#![allow(unused)] fn main() { use std::borrow::Cow; fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> { if input.contains(' ') { let mut buf = String::with_capacity(input.len()); for c in input.chars() { if c != ' ' { buf.push(c); } } return Cow::Owned(buf); } return Cow::Borrowed(input); } }
完美解決了業務邏輯與返回值類型衝突的問題。本例可細細品味。
外部程序,拿到這個 Cow 返回值後,按照我們上文描述的 Cow 的特性使用就好了。