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()
舉例
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()
舉例
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);
綜合舉例
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
在函數返回值上的應用實例
題目:寫一個函數,過濾掉輸入的字符串中的所有空格字符,並返回過濾後的字符串。
對這個簡單的問題,不用思考,我們都可以很快寫出代碼:
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
君這時出馬了。奶牛君很快寫出瞭如下代碼:
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
的特性使用就好了。