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

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

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

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

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

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

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

 1use std::borrow::{Borrow, Cow};
 2use std::ops::Deref;
 3
 4#[derive(Debug)]
 5struct MyString {
 6    data: String
 7}
 8
 9#[derive(Debug)]
10#[repr(transparent)]
11struct MyStr {
12    data: str,
13}

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

 1impl Borrow<MyStr> for MyString {
 2    fn borrow(&self) -> &MyStr {
 3        unsafe { &*(self.data.as_str() as *const str as *const MyStr) }
 4    }
 5}
 6
 7impl ToOwned for MyStr {
 8    type Owned = MyString;
 9
10    fn to_owned(&self) -> MyString {
11        MyString {
12            data: self.data.to_owned()
13        }
14    }
15}

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

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

 1impl Deref for MyString {
 2    type Target = MyStr;
 3
 4    fn deref(&self) -> &Self::Target {
 5        self.borrow()
 6    }
 7}
 8
 9
10fn main()  {
11    let data = MyString { data: "Hello world".to_owned() };
12
13    let borrowed_cow: Cow<'_, MyStr> = Cow::Borrowed(&data);
14    println!("{:?}", borrowed_cow);
15
16    let owned_cow: Cow<'_, MyStr> = Cow::Owned(data);
17    println!("{:?}", owned_cow);
18}

五,借用類型作爲 dyn Trait

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

 1use std::borrow::{Borrow, Cow};
 2use std::fmt::Debug;
 3use std::ops::Deref;
 4
 5trait MyTrait: Debug {
 6    fn data(&self) -> &str;
 7}
 8
 9#[derive(Debug)]
10struct MyString {
11    data: String
12}
13
14impl MyTrait for MyString {
15    fn data(&self) -> &str {
16        &self.data
17    }
18}

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

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

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

1impl ToOwned for dyn MyTrait {
2    type Owned = MyString;
3
4    fn to_owned(&self) -> MyString {
5        MyString {
6            data: self.data().to_owned()
7        }
8    }
9}

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

1fn main()  {
2    let data = MyString { data: "Hello world".to_owned() };
3
4    let borrowed_cow: Cow<'_, dyn MyTrait> = Cow::Borrowed(&data);
5    println!("{:?}", borrowed_cow);
6
7    let owned_cow: Cow<'_, dyn MyTrait> = Cow::Owned(data);
8    println!("{:?}", owned_cow);
9}

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

 1fn main()  {
 2    let data = MyString { data: "Hello world".to_owned() };
 3    let cow1: Cow<'_, dyn MyTrait> = Cow::Borrowed(&data);
 4
 5    let data = MyString { data: "Hello world".to_owned() };
 6    let cow2: Cow<'_, dyn MyTrait> = Cow::Owned(data);
 7
 8    let mut vector: Vec<Cow<'_, dyn MyTrait>> = vec![cow1, cow2];
 9}

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

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

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

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

1use std::borrow::{Borrow, Cow};
2use std::fmt::{Debug, Formatter};
3use std::ops::Deref;
4
5struct NativeBuffer {
6    pub ptr: *const u8,
7    pub len: usize
8}

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

 1impl Borrow<[u8]for NativeBuffer {
 2    fn borrow(&self) -> &[u8] {
 3        unsafe {
 4            std::slice::from_raw_parts(self.ptr, self.len)
 5        }
 6    }
 7}
 8
 9impl Deref for NativeBuffer {
10    type Target = [u8];
11
12    fn deref(&self) -> &Self::Target {
13        self.borrow()
14    }
15}
16
17impl Debug for NativeBuffer {
18    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
19        let data: &[u8] = self.borrow();
20        write!(f, "NativeBuffer {{ data: {:?}, len: {} }}", data, self.len)
21    }
22}

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

 1#[derive(Debug)]
 2struct OwnedBuffer {
 3    owned_data: Vec<u8>,
 4    native_proxy: NativeBuffer,
 5}
 6
 7impl ToOwned for NativeBuffer {
 8    type Owned = OwnedBuffer;
 9
10    fn to_owned(&self) -> OwnedBuffer {
11        let slice: &[u8] = self.borrow();
12        let owned_data = slice.to_vec();
13        let native_proxy = NativeBuffer {
14            ptr: owned_data.as_ptr(),
15            len: owned_data.len()
16        };
17        OwnedBuffer {
18            owned_data,
19            native_proxy,
20        }
21    }
22}

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

1impl Borrow<NativeBuffer> for OwnedBuffer {
2    fn borrow(&self) -> &NativeBuffer {
3        &self.native_proxy
4    }
5}

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

 1impl OwnedBuffer {
 2
 3    pub fn append(&mut self, data: &[u8]) {
 4        self.owned_data.extend(data);
 5        self.native_proxy = NativeBuffer {
 6            ptr: self.owned_data.as_ptr(),
 7            len: self.owned_data.len()
 8        };
 9    }
10}

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

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

 1fn main() {
 2    // Simulates the data coming across FFI (from C)
 3    let data = vec![1, 2, 3];
 4    let ptr = data.as_ptr();
 5    let len = data.len();
 6
 7    let native_buffer = NativeBuffer { ptr, len};
 8    let mut buffer = Cow::Borrowed(&native_buffer);
 9    // NativeBuffer { data: [1, 2, 3], len: 3 }
10    println!("{:?}", buffer);
11
12    // No data cloned
13    assert_eq!(buffer.ptr, ptr);
14    assert_eq!(buffer.len, len);
15
16    if buffer.len > 1 {
17        buffer.to_mut().append(&[4, 5, 6]);
18        // OwnedBuffer { owned_data: [1, 2, 3, 4, 5, 6], native_proxy: NativeBuffer { data: [1, 2, 3, 4, 5, 6], len: 6 } }
19        println!("{:?}", buffer);
20
21        // Data is cloned
22        assert_ne!(buffer.ptr, ptr);
23        assert_eq!(buffer.len, len + 3);
24    }
25
26    let slice: &[u8] = &buffer;
27    // [1, 2, 3, 4, 5, 6]
28    println!("{:?}", slice);
29}

只有當緩衝區的長度大於 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