你可以用 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_nameCow<'a, str>,
    last_nameCow<'a, str>,
}

通過這種方式,我們可以構造 User 結構的所有權版本和借用版本:

impl<'a> User<'a> {

    pub fn new_owned(first_nameString, last_nameString) -> User<'static> {
        User {
            first_nameCow::Owned(first_name),
            last_nameCow::Owned(last_name),
        }
    }

    pub fn new_borrowed(first_name&'a str, last_name&'a str) -> Self {
        Self {
            first_nameCow::Borrowed(first_name),
            last_nameCow::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 userUser<'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 userUser<'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> {
    dataCow<'a, [u8]>,
}

impl<'a> LazyBuffer<'a> {

    pub fn new(data&'a[u8]) -> Self {
        Self {
            dataCow::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