你可以用 Rust Cow 做的六件事情 - 2

我們接着上一篇文章繼續完成剩下的事情。

四,在 Cow 中存儲你自己的類型

大多數情況下是使用 Cow 或 Cow<[u8]>,但在某些情況下,你可能希望將自己的類型存儲在其中。

爲了將 Cow 與用戶定義的類型一起使用,你需要實現 owned 和 borrowed 特性。owned 和 borrowed 特性必須通過以下特徵邊界連接在一起:

你有沒有想過爲什麼我們到處使用 & str 而幾乎從不使用 str?你在標準庫中找不到 str 類型的定義。由於 str 是一個動態大小的類型,它只能通過指針類型實例化,例如 & str。Trait 對象 dyn T 是動態大小類型的另一個例子。

假設你想實現自己版本的 String 和 str 類型:

use std::borrow::{Borrow, Cow};
use std::ops::Deref;

#[derive(Debug)]
struct MyString {
    dataString
}

#[derive(Debug)]
#[repr(transparent)]
struct MyStr {
    datastr,
}

因爲 str 是動態調整大小的,所以 MyStr 也是。你可以像 String 和 str 一樣綁定 MyString 和 MyStr:

impl Borrow<MyStr> for MyString {
    fn borrow(&self) -> &MyStr {
        unsafe { &*(self.data.as_str() as *const str as *const MyStr) }
    }
}

impl ToOwned for MyStr {
    type Owned = MyString;

    fn to_owned(&self) -> MyString {
        MyString {
            dataself.data.to_owned()
        }
    }
}

borrow 方法中不安全的指針可能已經引起了你的注意,雖然看起來很可怕,但它是標準庫中的常見模式,由於 MyStr 是一個帶有 #[repr(transparent)] 註釋的單一字段結構,因此它可以保證具有零成本的編譯時表示。這意味着我們可以安全地將指向 str 的有效指針轉換爲指向 MyStr 的指針,然後將其轉換爲引用。

爲了方便,我們也可以選擇實現 Deref 特性,並將 MyString 和 MyStr 存儲到 cow 中:

impl Deref for MyString {
    type Target = MyStr;

    fn deref(&self) -> &Self::Target {
        self.borrow()
    }
}


fn main()  {
    let data = MyString { data"Hello world".to_owned() };

    let borrowed_cowCow<'_, MyStr> = Cow::Borrowed(&data);
    println!("{:?}", borrowed_cow);

    let owned_cowCow<'_, MyStr> = Cow::Owned(data);
    println!("{:?}", owned_cow);
}

五,借用類型作爲 dyn Trait

如上所述,trait 對象是動態大小類型的另一個例子。我們可以用類似的方式使用 Cow 來實現動態調度,類似於 Box 和 Arc

use std::borrow::{Borrow, Cow};
use std::fmt::Debug;
use std::ops::Deref;

trait MyTraitDebug {
    fn data(&self) -> &str;
}

#[derive(Debug)]
struct MyString {
    dataString
}

impl MyTrait for MyString {
    fn data(&self) -> &str {
        &self.data
    }
}

MyString 實現了 MyTrait,我們可以借用 & MyString 作爲 & dyn MyTrait:

impl<'a> Borrow<dyn MyTrait + 'a> for MyString {
    fn borrow(&self) -> &(dyn MyTrait + 'a) {
        self
    }
}

我們還可以將任何 MyTrait 的實現轉換爲 MyString:

impl ToOwned for dyn MyTrait {
    type Owned = MyString;

    fn to_owned(&self) -> MyString {
        MyString {
            dataself.data().to_owned()
        }
    }
}

既然我們已經定義了 Borrow 和 ToOwned,我們現在可以把 MyString 放入 Cow

fn main()  {
    let data = MyString { data"Hello world".to_owned() };

    let borrowed_cowCow<'_, dyn MyTrait> = Cow::Borrowed(&data);
    println!("{:?}", borrowed_cow);

    let owned_cowCow<'_, dyn MyTrait> = Cow::Owned(data);
    println!("{:?}", owned_cow);
}

上面的實現可能是有用的,例如 trait 對象的可變向量:

fn main()  {
    let data = MyString { data"Hello world".to_owned() };
    let cow1Cow<'_, dyn MyTrait> = Cow::Borrowed(&data);

    let data = MyString { data"Hello world".to_owned() };
    let cow2Cow<'_, dyn MyTrait> = Cow::Owned(data);

    let mut vectorVec<Cow<'_, dyn MyTrait>> = vec![cow1, cow2];
}

六,實現對 FFI 類型的安全包裝

上面的 MyString 例子令人興奮,但有些做作。讓我們考慮一下在 Cow 中存儲自己類型的實際例子。

假設你正在 rust 項目中使用 C 庫,從 C 代碼中收到一個數據緩衝區,其形式爲指針 * const u8 和長度 usize。假設你想在 rust 邏輯層傳遞數據,可能還要修改它 (是否考慮使用 Cow?)。最後,你可能希望以 &[u8] 的形式訪問 rust 中的數據(修改過的或未修改過的),或者將指針 * const u8 和長度 usize 傳遞給另一個 C 函數。

由於我們希望避免不必要的複製數據,我們將緩衝區表示爲以下結構:

use std::borrow::{Borrow, Cow};
use std::fmt::{Debug, Formatter};
use std::ops::Deref;

struct NativeBuffer {
    pub ptr*const u8,
    pub lenusize
}

這個結構體並不擁有它的數據,它從 C 指針借用了一個未知的生命週期。

impl Borrow<[u8]> for NativeBuffer {
    fn borrow(&self) -> &[u8] {
        unsafe {
            std::slice::from_raw_parts(self.ptr, self.len)
        }
    }
}

impl Deref for NativeBuffer {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        self.borrow()
    }
}

impl Debug for NativeBuffer {
    fn fmt(&self, f&mut Formatter<'_>) -> std::fmt::Result {
        let data&[u8] = self.borrow();
        write!(f, "NativeBuffer {{ data: {:?}, len: {} }}", data, self.len)
    }
}

爲了將 NativeBuffer 存儲在 Cow 中,我們首先需要定義它的所屬版本:

#[derive(Debug)]
struct OwnedBuffer {
    owned_dataVec<u8>,
    native_proxyNativeBuffer,
}

impl ToOwned for NativeBuffer {
    type Owned = OwnedBuffer;

    fn to_owned(&self) -> OwnedBuffer {
        let slice&[u8] = self.borrow();
        let owned_data = slice.to_vec();
        let native_proxy = NativeBuffer {
            ptrowned_data.as_ptr(),
            lenowned_data.len()
        };
        OwnedBuffer {
            owned_data,
            native_proxy,
        }
    }
}

技巧是借用數據作爲切片,並將其轉換爲 Vec。我們還需要將 NativeBuffer 存儲在 OwnedBuffer 中。它包含一個指針,指向向量內部的數據和它的長度,所以我們可以實現 Borrow 特性:

impl Borrow<NativeBuffer> for OwnedBuffer {
    fn borrow(&self) -> &NativeBuffer {
        &self.native_proxy
    }
}

我們現在可以定義方法來改變數據:

impl OwnedBuffer {

    pub fn append(&mut self, data&[u8]) {
        self.owned_data.extend(data);
        self.native_proxy = NativeBuffer {
            ptrself.owned_data.as_ptr(),
            lenself.owned_data.len()
        };
    }
}

確保本機緩衝區指針保持最新是很重要的。

最後,我們可以把借來的緩衝區放到 Cow 中,並實現條件邏輯改變,例如:

fn main() {
    // Simulates the data coming across FFI (from C)
    let data = vec![1, 2, 3];
    let ptr = data.as_ptr();
    let len = data.len();

    let native_buffer = NativeBuffer { ptr, len};
    let mut buffer = Cow::Borrowed(&native_buffer);
    // NativeBuffer { data: [1, 2, 3], len: 3 }
    println!("{:?}", buffer);

    // No data cloned
    assert_eq!(buffer.ptr, ptr);
    assert_eq!(buffer.len, len);

    if buffer.len > 1 {
        buffer.to_mut().append(&[4, 5, 6]);
        // OwnedBuffer { owned_data: [1, 2, 3, 4, 5, 6], native_proxy: NativeBuffer { data: [1, 2, 3, 4, 5, 6], len: 6 } }
        println!("{:?}", buffer);

        // Data is cloned
        assert_ne!(buffer.ptr, ptr);
        assert_eq!(buffer.len, len + 3);
    }

    let slice&[u8] = &buffer;
    // [1, 2, 3, 4, 5, 6]
    println!("{:?}", slice);
}

只有當緩衝區的長度大於 1 時,纔會克隆它。

本文翻譯自:

https://dev.to/kgrech/6-things-you-can-do-with-the-cow-in-rust-4l55

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/wWkmfUyyn1PQNAzzqzgT7A