0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

rust语言基础学习: 智能指针之Cow

冬至子 来源:山川与湖水 作者:山川与湖水 2023-05-22 16:13 次阅读

Rust中与借用数据相关的三个trait: Borrow, BorrowMutToOwned。理解了这三个trait之后,再学习Rust中能够实现写时克隆智能指针Cow<'a B>。写时克隆(Copy on Write)技术是一种程序中的优化策略,多应用于读多写少的场景。主要思想是创建对象的时候不立即进行复制,而是先引用(借用)原有对象进行大量的读操作,只有进行到少量的写操作的时候,才进行复制操作,将原有对象复制后再写入。这样的好处是在读多写少的场景下,减少了复制操作,提高了性能。

1.Cow的定义

Cow是Rust提供的用于实现 ** 写时克隆 (Copy on Write)** 的智能指针。

定义如下:

pub enum Cow<'a, B> 
where
    B: 'a + ToOwned + ?Sized, 
 {    /// 用于包裹引用(通用引用)    Borrowed(&'a B),    /// 用于包裹所有者;    Owned(::Owned),
}

**
从Cow的定义看,它是一个enum,包含一个对类型B的只读引用,或者包含一个拥有类型B的所有权的数据。

可以看到Cow是一个枚举体,包括两个可选值,一个是“借用”(只读),一个是“所有”(可读写)。具体含义是:以不可变的方式访问借用内容,在需要可变借用或所有权的时候再克隆一份数据。

Cow trait的泛型参数约束比较复杂,下面详细介绍一下:

  • pub enum Cow<'a, B>中的'a是生命周期标注,表示Cow是一个包含引用的enum。泛型参数B需要满足'a + ToOwned + ?Sized。即当Cow内部类型B的生命周期为’a时,Cow自己的生命周期也是’a。
  • 泛型参数B除了生命周期注解’a外,还有ToOwned?Sized两个约束
  • ?Sized表示B是可变大小类型
  • ToOwned表示可以把借用的B数据复制出一个拥有所有权的数据
  • 这个enum里的Borrowed(&'a B)表示返回借用数据是B类型的引用,引用的生命周期为’a
  • 因为B满足ToOwned trait,所以Owned(::Owned)中的::Owned表示把B强制转换成ToOwned,并访问ToOwned内部的关联类型Owned

2.智能指针Cow

了解了Cow这个用于写时克隆的智能指针的定义,它在定义上是一个枚举类型,有两个可选值:

  • Borrowed用来包裹对象的引用
  • Owned用来包裹对象的所有者

Cow 在这里就是表示借用的和自有的,但只能出现其中的一种情况。

下面从智能指针的角度来学习Cow。先回顾一下智能指针的特征:

  • 大多数情况下智能指针具有它所指向数据的所有权
  • 智能指针是一种数据结构,一般使用结构体实现
  • 智能指针数据类型的显著特征是实现Deref和Drop trait

当然,上面智能指针的特征都不是强制的,我们来看一下Cow做为智能指针是否有上面的这些特征:

  • Cow枚举的Owned的可选值,可以返回一个拥有所有权的数据
  • Cow作为智能指针在定义上是使用枚举类型实现的
  • Cow实现的Deref trait,Cow没有实现Drop trait

我们知道,如果一个类型实现了Deref trait,那么就可以将类型当做常规引用类型使用。

下面是Cow对Deref trait的实现:

impl

**
在实现上很简单,match表达式中根据self是Borrowed还是Owned,分别取其内容,然后生成引用:

  • 对于Borrowed选项,其内容就是引用
  • 对于Owned选项,其内容是泛型参数B实现ToOwned中的关联类型Owned,而Owned是实现Borrow trait的,所以owned.borrow()可以获得引用

Cow<'a, B>通过对Deref trait的实现,就变得很厉害了,因为智能指针通过Deref的实现就可以获得常规引用的使用体验。对Cow<'a, B>的使用,在体验上和直接&B基本上时一致的。

通过函数或方法传参时Deref强制转换(Deref coercion)功能,可以使用Cow<'a, B>直接调用 B的不可变引用方法 (&self)。

例1:

use std::borrow::Cow;

fn main() {
    let hello = "hello world";
    let c = Cow::Borrowed(hello);
    println!("{}", c.starts_with("hello"));
}

例1中变量c使用Cow包裹了一个&str引用,随后直接调用了str的start_with方法。

3.Cow的方法

接下来看一下智能指针Cow都提供了哪些方法供我们使用。

2个关键函数:

  • to_mut(): 就是返回数据的可变引用,如果没有数据的所有权,则复制拥有后再返回可变引用;
  • into_owned(): 获取一个拥有所有权的对象(区别与引用),如果当前是借用,则发生复制,创建新的所有权对象,如果已拥有所有权,则转移至新对象。
impl

pub fn into_owned(self) -> ::Owned: into_owned方法用于抽取Cow所包裹类型B的所有者权的数据,如果它还没有所有权数据将会克隆一份。在一个Cow::Borrowed上调用into_owned,会克隆底层数据并成为Cow::Owned。在一个Cow::Owned上调用into_owned不会发生克隆操作。

**
例2:

use std::borrow::Cow;

fn main() {
    let s = "Hello world!";

    // 在一个`Cow::Borrowed`上调用`into_owned`,会克隆底层数据并成为`Cow::Owned`。
    let cow1 = Cow::Borrowed(s);

    assert_eq!(cow1.into_owned(), String::from(s));

    // 在一个`Cow::Owned`上调用into_owned不会发生克隆操作。
    let cow2: Cow<str> = Cow::Owned(String::from(s));

    assert_eq!(cow2.into_owned(), String::from(s));
}

pub fn to_mut(&mut self) -> &mut ::Owned: 从Cow所包裹类型B的所有者权的数据获得一个可变引用,如果它还没有所有权数据将会克隆一份再返回其可变引用。

**
例3:

use std::borrow::Cow;

fn main() {
    let mut cow = Cow::Borrowed("foo");
    cow.to_mut().make_ascii_uppercase();

    assert_eq!(cow, Cow::Owned(String::from("FOO")) as Cow<str>);
}

4.Cow的使用场景

使用Cow主要用来减少内存的分配和复制,因为绝大多数的场景都是读多写少。使用Cow可以在需要些的时候才做一次内存复制,这样就很大程度减少了内存复制次数。

先来看官方文档中的例子。

例4:

use std::borrow::Cow;

fn main() {
    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);
}

最后再来看一下例子。

例5:

use std::borrow::Cow;

const SENSITIVE_WORD: &str = "bad";

fn remove_sensitive_word<'a>(words: &'a str) -> Cow<'a, str> {
    if words.contains(SENSITIVE_WORD) {
        Cow::Owned(words.replace(SENSITIVE_WORD, ""))
    } else {
        Cow::Borrowed(words)
    }
}

fn remove_sensitive_word_old(words: &str) -> String {
    if words.contains(SENSITIVE_WORD) {
        words.replace(SENSITIVE_WORD, "")
    } else {
        words.to_owned()
    }
}

fn main() {
    let words = "I'm a bad boy.";
    let new_words = remove_sensitive_word(words);
    println!("{}", new_words);

    let new_words = remove_sensitive_word_old(words);
    println!("{}", new_words);
}

例5的需求是实现一个字符串敏感词替换函数,从给定的字符串替换掉预制的敏感词。

例子中给出了remove_sensitive_wordremove_sensitive_word_old两种实现,前者的返回值使用了Cow,后者返回值使用的是String。仔细分析一下,很明显前者的实现效率更高。因为如果输入的字符串中没有敏感词时,前者Cow::Borrowed(words)不会发生堆内存的分配和拷贝,后者words.to_owned()会发生一次堆内存的分配和拷贝。

试想一下,如果例5的敏感词替换场景,是大多数情况下都不会发生替换的,即读多写少的场景,remove_sensitive_word实现中使用Cow作为返回值就在很多程度上提高了系统的效率。

总结

Cow 的设计目的是提高性能(减少复制)同时增加灵活性,因为大部分情况下,多用于读多写少的场景。利用 Cow,可以用统一,规范的形式实现,需要写的时候才做一次对象复制。

  1. 创建语义:Cow::Borrowed(v) 或者 Cow::Owned(v)
  2. 获得本体:Cow::into_owned(),得到具有 所有权的值 ,如果之前Cow是Borrowed借用状态,调用into_owned将会克隆,如果已经是Owned状态,将不会克隆
  3. 可变借用:Cow::to_mut(),得到一个具有所有权的值的 可变引用 ,注意在已经具有所有权的情况下,也可以调用to_mut但不会产生新的克隆,多次调用to_mut只会产生一次克隆

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • COW
    COW
    +关注

    关注

    0

    文章

    4

    浏览量

    7978
  • rust语言
    +关注

    关注

    0

    文章

    58

    浏览量

    2958
收藏 人收藏

    评论

    相关推荐

    聊聊Rust与C语言交互的具体步骤

    rust FFI 是rust与其他语言互调的桥梁,通过FFI rust 可以有效继承 C 语言的历史资产。本期通过几个例子来聊聊
    发表于 07-06 11:15 1021次阅读

    如何使用Rust语言和paho-mqtt模块实现MQTT协议

    模块实现MQTT协议,并重点介绍LWT特征。 Rust是一种系统级编程语言,它的主要特点是安全、高效、并发。Rust编译器会在编译时进行内存安全检查,避免了很多常见的内存安全问题,如空指针
    的头像 发表于 09-19 14:41 1172次阅读

    基于Rust语言Hash特征的基础用法和进阶用法

    Rust语言是一种系统级编程语言,具有高性能、安全、并发等特点,是近年来备受关注的新兴编程语言。在Rust
    的头像 发表于 09-19 16:02 741次阅读

    Rust语言如何与 InfluxDB 集成

    Rust 是一种系统级编程语言,具有高性能和内存安全性。InfluxDB 是一个开源的时间序列数据库,用于存储、查询和可视化大规模数据集。Rust 语言可以与 InfluxDB 集成,
    的头像 发表于 09-30 16:45 618次阅读

    Cow特征的使用方法和最佳实践

    CowRust语言中的一个特殊类型,全称为Clone-On-Write,即在写入时进行克隆操作。Cow类型可以用来避免不必要的内存分配和复制操作,从而提高程序的性能和效率。
    的头像 发表于 09-20 11:11 624次阅读

    如何用 rust 语言开发 stm32

    本文介绍如何用 rust 语言开发 stm32。开发平台为 linux(gentoo)。硬件准备本文使用的芯片为 STM32F103C8T6。该芯片性价比较高,价格低廉,适合入门学习。需要
    发表于 11-26 06:20

    如何利用C语言去调用rust静态库呢

    语言的感觉,要做不少的对接工作。也用过Lua,感觉也差不多。评估学习评估Rust语言时,感觉性能和体积应该都不会有太大的问题。加上语言本身
    发表于 06-21 10:27

    D语言,Go语言,Rust语言优势分析

    编者按】本文是D语言来呢后创始人、架构师Andrei Alexandrescu在问答Quora回答在取代C语言的道路上,D、Go和Rust谁的前途最光明?为什么?的答案,从自己的角度谈及了D、Go
    发表于 10-13 11:11 0次下载

    C语言指针的详细学习资料(中文版)

    对C语言部分的指针写的很细,值得学习中文版免费下载
    发表于 06-06 08:00 0次下载

    嵌入式开发之C语言指针

    学习 C 语言指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行。
    的头像 发表于 11-06 17:09 2917次阅读
    嵌入式开发之C<b class='flag-5'>语言</b>的<b class='flag-5'>指针</b>

    以调试Rust的方式来学习Rust

    在我上一篇 关于 Rustup 的文章 中,我向你们展示了如何安装 Rust 工具链。但是,如果不能上手操作一下 Rust 的话下载工具链又有什么用?学习任何语言都包括阅读现有的代码和
    的头像 发表于 01-03 14:56 661次阅读

    CRust学习笔记:智能指针和内部可变性

    本系列文章是Jon Gjengset发布的CRust of Rust系列视频的学习笔记,CRust of Rust是一系列持续更新的Rust中级教程。
    的头像 发表于 01-29 14:58 558次阅读

    rust语言基础学习: rust中的错误处理

    错误是软件中不可避免的,所以 Rust 有一些处理出错情况的特性。在许多情况下,Rust 要求你承认错误的可能性,并在你的代码编译前采取一些行动。
    的头像 发表于 05-22 16:28 1404次阅读

    Rust的内部工作原理

    智能指针传递self时生成的汇编方式 Rust递归树生成的汇编代码 更多见原文链接 原文链接: https://www.eventhelix.com/rust/ libtracecmd-rs
    的头像 发表于 06-14 10:34 468次阅读
    <b class='flag-5'>Rust</b>的内部工作原理

    C++智能指针的底层实现原理

    C++智能指针的头文件: #include 1. shared_ptr: 智能指针从本质上来说是一个模板类,用类实现对指针对象的管理。 template class shared_ptr
    的头像 发表于 11-09 14:32 296次阅读
    C++<b class='flag-5'>智能指针</b>的底层实现原理