你可以用 Rust Cow 做的六件事情
即使對於一些中級 Rust 開發人員來說,Cow 類型也是一個謎。儘管它被定義爲一個簡單的雙變量枚舉類型。
pub enum Cow<'a, B>
where
B: 'a + ToOwned + ?Sized,
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
它要求開發人員理解所有權和生命週期,以及另一個神祕的 Borrow 和 ToOwned 特徵。因此,程序員儘量避免使用 Cow,這通常會發生額外的內存分配,導致效率較低。
在什麼情況下可以考慮使用 Cow?爲什麼它有這麼奇怪的名字?今天就讓我們一起來尋找答案吧!
一,Cow 作爲函數的返回值
讓我們從 Cow 類型最常見、最直接的用例開始。這很好地說明了大多數開發人員 (包括我!) 第一次遇到 Cow 的情況。
考慮以下函數接受並修改借用的數據 (在本例中是 & str):
fn remove_whitespaces(s: &str) -> String {
s.to_string().replace(' ', "")
}
fn main() {
let value = remove_whitespaces("Hello world!");
println!("{}", value);
}
正如你所看到的,它什麼也不做,只是從字符串中刪除所有空格。
在這種情況下,我們可以避免調用 to_string() 創建一個不必要的字符串副本。然而,如果我們要實現這樣的邏輯,我們既不能使用 String 也不能使用 & str 類型:String 強制內存分配,&str 是不可變的。
該是 Cow 發揮作用的時刻了,當字符串被修改時,我們可以返回 Cow::Owned,否則返回 Cow:: borrow (s):
use std::borrow::Cow;
fn remove_whitespaces(s: &str) -> Cow<str> {
if s.contains(' ') {
Cow::Owned(s.to_string().replace(' ', ""))
} else {
Cow::Borrowed(s)
}
}
fn main() {
let value = remove_whitespaces("Hello world!");
println!("{}", value);
}
Cow 的好處是,它總是可以被解引用爲 & str,或者通過調用 into_owned 轉換爲 String。into_owned 只在字符串最初被借用的情況下分配內存。
二,Cow 作爲 Struct 的成員類型
我們經常需要在結構中存儲引用。因爲我們不想克隆不必要的數據。
struct User<'a> {
first_name: &'a str,
last_name: &'a str,
}
如果能夠創建一個具有靜態生命週期的用戶 user <'static> 擁有自己的數據不是很好嗎?通過這種方式,我們可以實現 do_something_with_user(user) 方法,無論數據是克隆還是借用,都接受相同的結構。
不幸的是,創建 User<'static> 的唯一方法是使用 &'static str。但如果我們有一個字符串呢?我們可以通過不存儲 &'a str,而是在結構體中存儲 Cow<'a, str > 來解決這個問題:
use std::borrow::Cow;
struct User<'a> {
first_name: Cow<'a, str>,
last_name: Cow<'a, str>,
}
通過這種方式,我們可以構造 User 結構的所有權版本和借用版本:
impl<'a> User<'a> {
pub fn new_owned(first_name: String, last_name: String) -> User<'static> {
User {
first_name: Cow::Owned(first_name),
last_name: Cow::Owned(last_name),
}
}
pub fn new_borrowed(first_name: &'a str, last_name: &'a str) -> Self {
Self {
first_name: Cow::Borrowed(first_name),
last_name: Cow::Borrowed(last_name),
}
}
pub fn first_name(&self) -> &str {
&self.first_name
}
pub fn last_name(&self) -> &str {
&self.last_name
}
}
fn main() {
// Static lifetime as it owns the data
let user: User<'static> = User::new_owned("James".to_owned(), "Bond".to_owned());
println!("Name: {} {}", user.first_name, user.last_name);
// Static lifetime as it borrows 'static data
let user: User<'static> = User::new_borrowed("Felix", "Leiter");
println!("Name: {} {}", user.first_name, user.last_name);
let first_name = "Eve".to_owned();
let last_name = "Moneypenny".to_owned();
// Non-static lifetime as it borrows the data
let user= User::new_borrowed(&first_name, &last_name);
println!("Name: {} {}", user.first_name, user.last_name);
}
三,Struct 寫時克隆
爲什麼它被命名爲 Cow 呢?Cow 代表抄寫。上面的例子只說明瞭 Cow 的一面:表示借用或擁有狀態的數據不在編譯時計算,而在運行時計算。
Cow 的真正力量來自於 to_mut 方法。如果 Cow 是 owned 的,它只是返回指向底層數據的指針,但是如果它是 borrowed 的,則將數據克隆。
它允許你基於結構實現接口,惰性地存儲對數據的引用,並僅在 (且是第一次) 需要改變時克隆數據。
考慮以 &[u8]形式接收數據緩衝區的代碼。我們想要有條件地修改數據 (例如追加幾個字節) 和消耗緩衝區 &[u8]。與上面的例子類似,我們不能將緩衝區保持爲 &[u8],因爲我們無法修改它,但將它轉換爲 Vec 將導致每次都進行復制。
我們可以通過將數據表示爲 Cow<[u8]> 來實現所需的行爲:
use std::borrow::Cow;
struct LazyBuffer<'a> {
data: Cow<'a, [u8]>,
}
impl<'a> LazyBuffer<'a> {
pub fn new(data: &'a[u8]) -> Self {
Self {
data: Cow::Borrowed(data),
}
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn append(&mut self, data: &[u8]) {
self.data.to_mut().extend(data)
}
}
通過這種方式,我們可以傳遞借來的數據,而無需克隆,直到我們需要修改它的時刻:
fn main() {
let data = vec![0u8; 10];
// No memory copied yet
let mut buffer = LazyBuffer::new(&data);
println!("{:?}", buffer.data());
// The data is cloned
buffer.append(&[1, 2, 3]);
println!("{:?}", buffer.data());
// The data is not cloned on further attempts
buffer.append(&[4, 5, 6]);
println!("{:?}", buffer.data());
}
本文翻譯自:
https://dev.to/kgrech/6-things-you-can-do-with-the-cow-in-rust-4l55
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/KwHNzZoSwdTzxqRpwsv1PQ