rust漫游 - 写时拷贝 Cow<'_, B>

Cow 是一个写时复制功能的智能指针,在数据需要修改或者所有权发生变化时使用,多用于读多写少的场景。

pub enum Cow<'a, B: ?Sized + 'a>
where
B: ToOwned,
{
/// Borrowed data.
Borrowed(&'a B), /// Owned data.
Owned(<B as ToOwned>::Owned),
}

数据在写入的情况下 Cow 才有存在的意义。当借用的数据被修改时,在不破坏原有数据的情况下,克隆一个副本并且在副本上进行修改;这是一种惰性的策略,在真正需要修改时才产生克隆的操作,而并不预先克隆。

关键函数

  • to_mut(), 获取所有权数据的可变引用,无所有权时从借用数据中克隆
  • into_owned(), 提取所有权数据。

使用

官方示例

官方描述了三种情况

  1. 借用数据,但是未调用 to_mut(),故不存在 clone 操作
  2. 借用数据,调用 to_mut(), 发生 clone 操作
  3. 所有权数据,调用 to_mut(), 不存在 clone 操作,因为具有该数据的所有权
use std::borrow::Cow;

fn abs_all(input: &mut Cow<[i32]>) {
for i in 0..input.len() {
let v = input[i];
if v < 0 {
// Clones into a vector if not already owned.
input.to_mut()[i] = -v;
}
}
} // No clone occurs because `input` doesn't need to be mutated.
let slice = [0, 1, 2];
let mut input = Cow::from(&slice[..]);
abs_all(&mut input); // Clone occurs because `input` needs to be mutated.
let slice = [-1, 0, 1];
let mut input = Cow::from(&slice[..]);
abs_all(&mut input); // No clone occurs because `input` is already owned.
let mut input = Cow::from(vec![-1, 0, 1]);
abs_all(&mut input);

观察所有权的变化

写了个地址打印函数,以此来观察所有权的变化。

fn print_addr(s: &str) {
println!("{}", s);
let mut p = s.as_ptr();
for ch in s.chars() {
println!("\t{:p}\t{}", p, ch);
p = p.wrapping_add(ch.len_utf8());
}
}

借用修改取出所有权

对借用的数据进行修改操作(有可能不会修改,见下文),操作完成后取出所有权 是最常见的用法

以下是一般的借用数据从修改至获取所有权数据的过程,通过新产生的地址可以看出来存在 clone 操作

{
let s = String::from("AB");
print_addr(&s);
let mut cow = Cow::Borrowed(&s);
cow.to_mut().insert_str(1, "cd");
let sr = cow.into_owned();
print_addr(&sr);
} // AB
// 0x7fb694c05af0 A
// 0x7fb694c05af1 B
// AcdB
// 0x7fb694c05b00 A
// 0x7fb694c05b01 c
// 0x7fb694c05b02 d
// 0x7fb694c05b03 B

上面的代码注释 to_mut() 行后,相当于不会有获取所有权的需求,这个时候是不应该做修改的,into_owned() 应该弃用转而使用 as_str() 这类没有所有权变化的操作.

{
let s = String::from("AB");
print_addr(&s);
let mut cow = Cow::Borrowed(&s);
// cow.to_mut().insert_str(1, "cd"); // 这一行是运行时决定的
let sr = cow.as_str(); // 看后续的使用,若是后续也只是读操作可以使用 as_str()
print_addr(&sr);
}

在所有权数据上进行修改

在对已具有所有权数据上操作时,字符串的地址未发生改变,未发生 clone 操作

insert_str 为两个 memcpy 操作,故首地址不会发生变化

{
let s1 = String::from("cd");
print_addr(&s1);
let mut cow1: Cow<'_, String> = Cow::Owned(s1);
cow1.to_mut().insert_str(0, "AB");
let sr1 = cow1.into_owned();
print_addr(&sr1);
} // cd
// 0x7fb694c05b10 c
// 0x7fb694c05b11 d
// ABcd
// 0x7fb694c05b10 A
// 0x7fb694c05b11 B
// 0x7fb694c05b12 c
// 0x7fb694c05b13 d

实现

Cow 是一个枚举值,包含了一个借用和所有。可以使用 Cow 的类型需要实现了 ToOwned trait。

ToOwned trait 同样包含了所有权或借用的操作。

  1. 需要实现 Borrow 借用 trait
  2. 可以从借用的数据中创建所有权数据或者克隆

相关 trait

pub trait ToOwned {
type Owned: Borrow<Self>; // 需要实现 Borrow triat pub fn to_owned(&self) -> Self::Owned; // 所有权创建
pub fn clone_into(&self, target: &mut Self::Owned) { ... }
} pub trait Borrow<Borrowed> where
Borrowed: ?Sized, {
pub fn borrow(&self) -> &Borrowed;
}

Borrow 借用 triat 泛型实现

impl<T: ?Sized> Borrow<T> for T {
fn borrow(&self) -> &T {
self
}
}

to_owned 创建所有权的泛型实现如下,取决该类型是否实现 Clone tait

impl<T> ToOwned for T
where
T: Clone,
{
type Owned = T;
fn to_owned(&self) -> T {
self.clone()
}
}

方法

to_mut()

获取所有权的可变引用

  1. 已获取所有权直接返回引用
  2. 借用数据的情况先调用 to_owned() 获取克隆副本的所有权,并且做一个检查

ref 关键字指示模式匹配为借用而不是移动。

<B as ToOwned>::Owned 表示 B 类型实现了 ToOwned trait,现使用该 trait 中的 Owned 类型,本质就是B类型本身,但是限制了实现 trait

pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned {
match *self {
Borrowed(borrowed) => {
*self = Owned(borrowed.to_owned());
match *self {
Borrowed(..) => unreachable!(),
Owned(ref mut owned) => owned,
}
}
Owned(ref mut owned) => owned,
}
}

into_owned()

取出 Cow 中的所有权数据,当为获取所有权时,进行 clone 操作

pub fn into_owned(self) -> <B as ToOwned>::Owned {
match self {
Borrowed(borrowed) => borrowed.to_owned(),
Owned(owned) => owned,
}
}

deref

由于 Cow 也实现了 Deref trait, 支持解引用强制多态,实现如下。

impl<B: ?Sized + ToOwned> Deref for Cow<'_, B> {
type Target = B; fn deref(&self) -> &B {
match *self {
Borrowed(borrowed) => borrowed,
Owned(ref owned) => owned.borrow(),
}
}
}

上面代码的 print_addr 的参数既可以是 &str, 也可以为 &Cow.

参考

官方文档

rust漫游 - 写时拷贝 Cow<'_, B>的更多相关文章

  1. 写时拷贝COW(copy-on-write)

        写时拷贝技术是通过"引用计数"实现的,在分配空间的时候多分配4个字节,用来记录有多少个指针指向块空间,当有新的指针指向这块空间时,引用计数加一,当要释放这块空间时,引用计数 ...

  2. Linux写时拷贝技术(copy-on-write)

    COW技术初窥: 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内 ...

  3. 【转】Linux写时拷贝技术(copy-on-write)

    http://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html 源于网上资料 COW技术初窥: 在Linux程序中,fork()会 ...

  4. [转] Linux写时拷贝技术(copy-on-write)

    PS:http://blog.csdn.net/zxh821112/article/details/8969541 进程间是相互独立的,其实完全可以看成A.B两个进程各自有一份单独的liba.so和l ...

  5. copy-on-write(写时拷贝技术)

    今天看<Unix环境高级编程>的fork函数与vfork函数时,看见一个copy-on-write的名词,貌似以前也经常听见别人说过这个,但也一直不明白这究竟是什么东西.所以就好好在网上了 ...

  6. Linux写时拷贝技术【转】

    本文转载自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html COW技术初窥: 在Linux程序中,fork()会产 ...

  7. String写时拷贝实现

    头文件部分 1 /* 版权信息:狼 文件名称:String.h 文件标识: 摘 要:对于上版本简易的String进行优化跟进. 改进 1.(将小块内存问题与大块分别对待)小内存块每个对象都有,当内存需 ...

  8. 关于Linux平台malloc的写时拷贝(延迟分配)【转】

    Linux内核定义了“零页面”(内容全为0的一个物理页,且物理地址固定),应用层的内存分配请求,如栈扩展.堆分配.静态分配等,分配线性地址后,就将页表项条目指向“零页面”(指定初始值的情况除外),这样 ...

  9. 计算机程序的思维逻辑 (73) - 并发容器 - 写时拷贝的List和Set

    本节以及接下来的几节,我们探讨Java并发包中的容器类.本节先介绍两个简单的类CopyOnWriteArrayList和CopyOnWriteArraySet,讨论它们的用法和实现原理.它们的用法比较 ...

随机推荐

  1. 25.数据结构,LinkedList ,泛型,类型通配符

    3.数据结构 数据结构是计算机存储,组织数据的方式.是指相互之间存在的一种或多种特定关系的数据元素的集合 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率 ---------常见的数据结构 ...

  2. [Python] tkinter 之 Listbox & Combobox

    示例: 1 #用户界面 2 import os 3 os.chdir('F:\\spyder_workspace\\ColCal') 4 import Main 5 from tkinter impo ...

  3. python基础之包、模块、命名空间和作用域

    一.模块介绍 模块就是一组功能的集合体,我们的程序可以导入模块来复用模块里的功能. 模块的作用: (1)从文件级别组织程序,更方便管理:随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一 ...

  4. 11.20 yum:自动化RPM包管理工具

    yum(Yellow dog Updater Modified)是多个Linux发行版的软件包管理器,例如Redhat RHEL.CentOS和Fedora.yum主要用于自动安装.升级rpm软件包, ...

  5. fprintf函数

    描述 C 库函数 int fprintf(FILE *stream, const char *format, ...) 发送格式化输出到流 stream 中. 声明 下面是 fprintf() 函数的 ...

  6. IntelliJ IDEA配置tomcat 教程

    1.点击Run-Edit Configurations... 2.点击左侧"+",选择Tomcat Server--Local 3.在Tomcat Server -> Unn ...

  7. Python3.x 基础练习题100例(91-100)

    练习91: 题目: 时间函数举例1. 程序: if __name__ == '__main__': import time print (time.ctime(time.time())) print ...

  8. 爱心跳动效果 CSS实现

    爱心跳动效果 CSS实现 实现效果 砰砰砰 实现原理 通过动画改变每个元素的高度,从而实现每个元素高度变化的效果,为了使每个元素依次跳动,给每个元素添加一定的延时效果,使得从效果元素依次跳动 代码分析 ...

  9. python类属性和实例属性的访问

  10. C#基础之GetType 与 typeof的区别

      C#中GetType 与 typeof的区别 在实际开发中经常需要了解具体对象的类型,所以经常会使用GetType()和typeof().尽管可以得到相应的类型.但两者之间也存在一些差别,接下来我 ...