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. TypeScript 中限制对象键名的取值范围

    当我们使用 TypeScript 时,我们想利用它提供的类型系统限制代码的方方面面,对象的键值,也不例外. 譬如我们有个对象存储每个年级的人名,类型大概长这样: type Students = Rec ...

  2. FHD 4K 8K分辨率

    4K(2160P,即4096×2160的像素分辨率)和8K(4320P,即7,680 × 4,320的像素分辨率)属于UHDTV. FHD是FULL HD(Full High Definition)的 ...

  3. php中的一些碎的知识点

    PHP函数之可变函数,即可以通过变量的名字来调用函数,因为变量的值是可变的,所以可以通过改变一个变量来调用不同的函数 例如 function name(){     echo "name&q ...

  4. Node.js-Events 模块总结与源码解析

    Events 描述 大多数 Node.js API 采用异步事件驱动架构,这些对象都是EventEmitter类的实例(Emitter),通过触发命名事件(eventName or type)来调用函 ...

  5. CRM系统实现自动化的“三部曲”

    在了解CRM系统的自动化的时候,我们先来看一下CRM能干什么. 从上面的流程图我们就可以看出,CRM可以管理售前,售中和售后的整个客户生命周期. 为什么在复杂的客户生命周期中需要自动化呢? 当然是为了 ...

  6. too much recursion

    今天在火狐浏览器上调试swagger接口遇到一个浏览器报错: too much recursion 刚开始以为接口出问题了,但是调试之后发现,后台有数据返回,往下一拉,看到了差不多两千多条数据,一下子 ...

  7. 『动善时』JMeter基础 — 23、JMeter中使用“用户自定义变量”实现参数化

    目录 1.用户自定义变量介绍 2.使用"用户自定义变量"实现参数化 (1)测试计划内包含的元件 (2)数据文件内容 (3)测试计划界面内容 (4)线程组元件内容 (5)CSV数据文 ...

  8. 网速测试利器-iperf3

    网速测试利器-iperf3 使用工具   简介 iperf3是一个网络速度测试工具,支持IPv4与IPv6,支持TCP.UDP.SCTP传输协议,可在Windows.Mac OS X.Linux.Fr ...

  9. 什么是CPU缓存

    一.什么是CPU缓存 1. CPU缓存的来历 众所周知,CPU是计算机的大脑,它负责执行程序的指令,而内存负责存数据, 包括程序自身的数据.在很多年前,CPU的频率与内存总线的频率在同一层面上.内存的 ...

  10. rpm包名详解-rpm命令使用方法

    linux软件包管理-rpm mount # 挂载 1.将光盘镜像插入光驱 2.创建挂载目录 mkdir /guangqu 3.挂载到/guangqu [root@gong ~]# mount /de ...