大多数情况下是使用Cow<str>或Cow<[u8]>,但在某些情况下,你可能希望将自己的类型存储在其中。
owned应该实现Borrow trait来生成对借用类型的引用
borrowed应该实现ToOwned trait来产生所有权类型
你有没有想过为什么我们到处使用&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}
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的指针,然后将其转换为引用。
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}
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}
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}
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}
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}
上面的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}
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