你可以用 Rust Cow 做的六件事情 - 2
我們接着上一篇文章繼續完成剩下的事情。
四,在 Cow 中存儲你自己的類型
大多數情況下是使用 Cow 或 Cow<[u8]>,但在某些情況下,你可能希望將自己的類型存儲在其中。
爲了將 Cow 與用戶定義的類型一起使用,你需要實現 owned 和 borrowed 特性。owned 和 borrowed 特性必須通過以下特徵邊界連接在一起:
-
owned 應該實現 Borrow trait 來生成對借用類型的引用
-
borrowed 應該實現 ToOwned trait 來產生所有權類型
你有沒有想過爲什麼我們到處使用 & str 而幾乎從不使用 str?你在標準庫中找不到 str 類型的定義。由於 str 是一個動態大小的類型,它只能通過指針類型實例化,例如 & str。Trait 對象 dyn T 是動態大小類型的另一個例子。
假設你想實現自己版本的 String 和 str 類型:
use std::borrow::{Borrow, Cow};
use std::ops::Deref;
#[derive(Debug)]
struct MyString {
data: String
}
#[derive(Debug)]
#[repr(transparent)]
struct MyStr {
data: str,
}
因爲 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 {
data: self.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_cow: Cow<'_, MyStr> = Cow::Borrowed(&data);
println!("{:?}", borrowed_cow);
let owned_cow: Cow<'_, 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 MyTrait: Debug {
fn data(&self) -> &str;
}
#[derive(Debug)]
struct MyString {
data: String
}
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 {
data: self.data().to_owned()
}
}
}
既然我們已經定義了 Borrow 和 ToOwned,我們現在可以把 MyString 放入 Cow:
fn main() {
let data = MyString { data: "Hello world".to_owned() };
let borrowed_cow: Cow<'_, dyn MyTrait> = Cow::Borrowed(&data);
println!("{:?}", borrowed_cow);
let owned_cow: Cow<'_, dyn MyTrait> = Cow::Owned(data);
println!("{:?}", owned_cow);
}
上面的實現可能是有用的,例如 trait 對象的可變向量:
fn main() {
let data = MyString { data: "Hello world".to_owned() };
let cow1: Cow<'_, dyn MyTrait> = Cow::Borrowed(&data);
let data = MyString { data: "Hello world".to_owned() };
let cow2: Cow<'_, dyn MyTrait> = Cow::Owned(data);
let mut vector: Vec<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 len: usize
}
這個結構體並不擁有它的數據,它從 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_data: Vec<u8>,
native_proxy: NativeBuffer,
}
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 {
ptr: owned_data.as_ptr(),
len: owned_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 {
ptr: self.owned_data.as_ptr(),
len: self.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