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. 简单说几个MySQL高频面试题

    前言: 在各类技术岗位面试中,似乎 MySQL 相关问题经常被问到.无论你面试开发岗位或运维岗位,总会问几道数据库问题.经常有小伙伴私信我,询问如何应对 MySQL 面试题.其实很多面试题都是大同小异 ...

  2. MySQL redo与undo日志解析

    前言: 前面文章讲述了 MySQL 系统中常见的几种日志,其实还有事务相关日志 redo log 和 undo log 没有介绍.相对于其他几种日志而言, redo log 和 undo log 是更 ...

  3. 普里姆算法(Prim)

    概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图(带权图)里搜索最小生成树.即此算法搜索到的边(Edge)子集所构成的树中,不但包括了连通图里的所有顶点(Vertex)且其所有边的权 ...

  4. Django(32)自定义过滤器

    前言 虽然DTL给我们内置了许多好用的过滤器.但是有些时候还是不能满足我们的需求.因此Django给我们提供了一个接口,可以让我们自定义过滤器,实现自己的需求. 自定义过滤器 首先在某个app中,创建 ...

  5. mysql基础之mysql双主(主主)架构

    一.概念 在企业中,数据库高可用一直是企业的重中之重,中小企业很多都是使用mysql主从方案,一主多从,读写分离等,但是单主存在单点故障,从库切换成主库需要作改动.因此,如果是双主或者多主,就会增加m ...

  6. JFlash ARM对stm32程序的读取和烧录-(转载)

    本篇文章主要是记录一下JFlash ARM 的相关使用和操作步骤,读取程序说不上破解,这只是在没有任何加密情况下对Flash的读写罢了!在我们装了JLINK驱动后再根目录下找到JFlash ARM , ...

  7. 9.2-3 pstree & pgrep

    9.2 pstree:显示进程状态树     pstree命令以树形结构显示进程和进程之间的关系.     如果不指定进程的PID号,或者不指定用户名称,则会以init进程为根进程,显示系统的所有进程 ...

  8. 西门子 S7200 以太网模块连接力控组态方法

    产品简介:北京华科远创科技有限研发的远创智控ETH-YC模块,以太网通讯模块型号有MPI-ETH-YC01和PPI-ETH-YC01,适用于西门子S7-200/S7-300/S7-400.SMART ...

  9. 数据库原理 第七章 数据库设计和ER模型

    第七章讲述一个E-R设计如何转换成一个关系模式的集合以及如何在该设计中找到某些约束. 1.概念设计定义了数据库中表示的实体.实体的属性.实体之间的联系,以及实体和联系上的约束 在设计一个数据库模型的时 ...

  10. node.js学习(1)全局对象

    1 计时器 1)三秒后执行 2) 每隔2秒执行一次 3)清除计时器 2 全局对象 1)当前文件所在目录 2)当前文件路径