1pub enum Cow<'a, B>
2where
3 B: 'a + ToOwned + ?Sized,
4{
5 Borrowed(&'a B),
6 Owned(<B as ToOwned>::Owned),
7}
它要求开发人员理解所有权和生命周期,以及另一个神秘的Borrow和ToOwned特征。因此,程序员尽量避免使用Cow,这通常会发生额外的内存分配,导致效率较低。
让我们从Cow类型最常见、最直接的用例开始。这很好地说明了大多数开发人员(包括我!)第一次遇到Cow的情况。
1fn remove_whitespaces(s: &str) -> String {
2 s.to_string().replace(' ', "")
3}
4
5fn main() {
6 let value = remove_whitespaces("Hello world!");
7 println!("{}", value);
8}
正如你所看到的,它什么也不做,只是从字符串中删除所有空格。
在这种情况下,我们可以避免调用to_string()创建一个不必要的字符串副本。然而,如果我们要实现这样的逻辑,我们既不能使用String也不能使用&str类型:String强制内存分配,&str是不可变的。
1use std::borrow::Cow;
2
3fn remove_whitespaces(s: &str) -> Cow<str> {
4 if s.contains(' ') {
5 Cow::Owned(s.to_string().replace(' ', ""))
6 } else {
7 Cow::Borrowed(s)
8 }
9}
10
11fn main() {
12 let value = remove_whitespaces("Hello world!");
13 println!("{}", value);
14}
1struct User<'a> {
2 first_name: &'a str,
3 last_name: &'a str,
4}
如果能够创建一个具有静态生命周期的用户user <'static>拥有自己的数据不是很好吗?通过这种方式,我们可以实现do_something_with_user(user)方法,无论数据是克隆还是借用,都接受相同的结构。
1use std::borrow::Cow;
2
3struct User<'a> {
4 first_name: Cow<'a, str>,
5 last_name: Cow<'a, str>,
6}
1impl<'a> User<'a> {
2
3 pub fn new_owned(first_name: String, last_name: String) -> User<'static> {
4 User {
5 first_name: Cow::Owned(first_name),
6 last_name: Cow::Owned(last_name),
7 }
8 }
9
10 pub fn new_borrowed(first_name: &'a str, last_name: &'a str) -> Self {
11 Self {
12 first_name: Cow::Borrowed(first_name),
13 last_name: Cow::Borrowed(last_name),
14 }
15 }
16
17
18 pub fn first_name(&self) -> &str {
19 &self.first_name
20 }
21 pub fn last_name(&self) -> &str {
22 &self.last_name
23 }
24}
25
26
27fn main() {
28 // Static lifetime as it owns the data
29 let user: User<'static> = User::new_owned("James".to_owned(), "Bond".to_owned());
30 println!("Name: {} {}", user.first_name, user.last_name);
31
32 // Static lifetime as it borrows 'static data
33 let user: User<'static> = User::new_borrowed("Felix", "Leiter");
34 println!("Name: {} {}", user.first_name, user.last_name);
35
36 let first_name = "Eve".to_owned();
37 let last_name = "Moneypenny".to_owned();
38
39 // Non-static lifetime as it borrows the data
40 let user= User::new_borrowed(&first_name, &last_name);
41 println!("Name: {} {}", user.first_name, user.last_name);
42}
为什么它被命名为Cow呢?Cow代表抄写。上面的例子只说明了Cow的一面:表示借用或拥有状态的数据不在编译时计算,而在运行时计算。
Cow的真正力量来自于to_mut方法。如果Cow是owned的,它只是返回指向底层数据的指针,但是如果它是borrowed的,则将数据克隆。
它允许你基于结构实现接口,惰性地存储对数据的引用,并仅在(且是第一次)需要改变时克隆数据。
考虑以&[u8]形式接收数据缓冲区的代码。我们想要有条件地修改数据(例如追加几个字节)和消耗缓冲区&[u8]。与上面的例子类似,我们不能将缓冲区保持为&[u8],因为我们无法修改它,但将它转换为Vec将导致每次都进行复制。
1use std::borrow::Cow;
2
3struct LazyBuffer<'a> {
4 data: Cow<'a, [u8]>,
5}
6
7impl<'a> LazyBuffer<'a> {
8
9 pub fn new(data: &'a[u8]) -> Self {
10 Self {
11 data: Cow::Borrowed(data),
12 }
13 }
14
15 pub fn data(&self) -> &[u8] {
16 &self.data
17 }
18
19 pub fn append(&mut self, data: &[u8]) {
20 self.data.to_mut().extend(data)
21 }
22}
1fn main() {
2 let data = vec![0u8; 10];
3
4 // No memory copied yet
5 let mut buffer = LazyBuffer::new(&data);
6 println!("{:?}", buffer.data());
7
8 // The data is cloned
9 buffer.append(&[1, 2, 3]);
10 println!("{:?}", buffer.data());
11
12 // The data is not cloned on further attempts
13 buffer.append(&[4, 5, 6]);
14 println!("{:?}", buffer.data());
15}