0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

rust語言基礎(chǔ)學(xué)習(xí): 智能指針之Cow

冬至子 ? 來源:山川與湖水 ? 作者:山川與湖水 ? 2023-05-22 16:13 ? 次閱讀

Rust中與借用數(shù)據(jù)相關(guān)的三個(gè)trait: Borrow, BorrowMutToOwned。理解了這三個(gè)trait之后,再學(xué)習(xí)Rust中能夠?qū)崿F(xiàn)寫時(shí)克隆智能指針Cow<'a B>。寫時(shí)克?。–opy on Write)技術(shù)是一種程序中的優(yōu)化策略,多應(yīng)用于讀多寫少的場景。主要思想是創(chuàng)建對象的時(shí)候不立即進(jìn)行復(fù)制,而是先引用(借用)原有對象進(jìn)行大量的讀操作,只有進(jìn)行到少量的寫操作的時(shí)候,才進(jìn)行復(fù)制操作,將原有對象復(fù)制后再寫入。這樣的好處是在讀多寫少的場景下,減少了復(fù)制操作,提高了性能。

1.Cow的定義

Cow是Rust提供的用于實(shí)現(xiàn) ** 寫時(shí)克隆 (Copy on Write)** 的智能指針。

定義如下:

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

**
從Cow的定義看,它是一個(gè)enum,包含一個(gè)對類型B的只讀引用,或者包含一個(gè)擁有類型B的所有權(quán)的數(shù)據(jù)。

可以看到Cow是一個(gè)枚舉體,包括兩個(gè)可選值,一個(gè)是“借用”(只讀),一個(gè)是“所有”(可讀寫)。具體含義是:以不可變的方式訪問借用內(nèi)容,在需要可變借用或所有權(quán)的時(shí)候再克隆一份數(shù)據(jù)。

Cow trait的泛型參數(shù)約束比較復(fù)雜,下面詳細(xì)介紹一下:

  • pub enum Cow<'a, B>中的'a是生命周期標(biāo)注,表示Cow是一個(gè)包含引用的enum。泛型參數(shù)B需要滿足'a + ToOwned + ?Sized。即當(dāng)Cow內(nèi)部類型B的生命周期為’a時(shí),Cow自己的生命周期也是’a。
  • 泛型參數(shù)B除了生命周期注解’a外,還有ToOwned?Sized兩個(gè)約束
  • ?Sized表示B是可變大小類型
  • ToOwned表示可以把借用的B數(shù)據(jù)復(fù)制出一個(gè)擁有所有權(quán)的數(shù)據(jù)
  • 這個(gè)enum里的Borrowed(&'a B)表示返回借用數(shù)據(jù)是B類型的引用,引用的生命周期為’a
  • 因?yàn)锽滿足ToOwned trait,所以Owned(::Owned)中的::Owned表示把B強(qiáng)制轉(zhuǎn)換成ToOwned,并訪問ToOwned內(nèi)部的關(guān)聯(lián)類型Owned

2.智能指針Cow

了解了Cow這個(gè)用于寫時(shí)克隆的智能指針的定義,它在定義上是一個(gè)枚舉類型,有兩個(gè)可選值:

  • Borrowed用來包裹對象的引用
  • Owned用來包裹對象的所有者

Cow 在這里就是表示借用的和自有的,但只能出現(xiàn)其中的一種情況。

下面從智能指針的角度來學(xué)習(xí)Cow。先回顧一下智能指針的特征:

  • 大多數(shù)情況下智能指針具有它所指向數(shù)據(jù)的所有權(quán)
  • 智能指針是一種數(shù)據(jù)結(jié)構(gòu),一般使用結(jié)構(gòu)體實(shí)現(xiàn)
  • 智能指針數(shù)據(jù)類型的顯著特征是實(shí)現(xiàn)Deref和Drop trait

當(dāng)然,上面智能指針的特征都不是強(qiáng)制的,我們來看一下Cow做為智能指針是否有上面的這些特征:

  • Cow枚舉的Owned的可選值,可以返回一個(gè)擁有所有權(quán)的數(shù)據(jù)
  • Cow作為智能指針在定義上是使用枚舉類型實(shí)現(xiàn)的
  • Cow實(shí)現(xiàn)的Deref trait,Cow沒有實(shí)現(xiàn)Drop trait

我們知道,如果一個(gè)類型實(shí)現(xiàn)了Deref trait,那么就可以將類型當(dāng)做常規(guī)引用類型使用。

下面是Cow對Deref trait的實(shí)現(xiàn):

impl

**
在實(shí)現(xiàn)上很簡單,match表達(dá)式中根據(jù)self是Borrowed還是Owned,分別取其內(nèi)容,然后生成引用:

  • 對于Borrowed選項(xiàng),其內(nèi)容就是引用
  • 對于Owned選項(xiàng),其內(nèi)容是泛型參數(shù)B實(shí)現(xiàn)ToOwned中的關(guān)聯(lián)類型Owned,而Owned是實(shí)現(xiàn)Borrow trait的,所以owned.borrow()可以獲得引用

Cow<'a, B>通過對Deref trait的實(shí)現(xiàn),就變得很厲害了,因?yàn)橹悄苤羔樛ㄟ^Deref的實(shí)現(xiàn)就可以獲得常規(guī)引用的使用體驗(yàn)。對Cow<'a, B>的使用,在體驗(yàn)上和直接&B基本上時(shí)一致的。

通過函數(shù)或方法傳參時(shí)Deref強(qiáng)制轉(zhuǎn)換(Deref coercion)功能,可以使用Cow<'a, B>直接調(diào)用 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包裹了一個(gè)&str引用,隨后直接調(diào)用了str的start_with方法。

3.Cow的方法

接下來看一下智能指針Cow都提供了哪些方法供我們使用。

2個(gè)關(guān)鍵函數(shù):

  • to_mut(): 就是返回?cái)?shù)據(jù)的可變引用,如果沒有數(shù)據(jù)的所有權(quán),則復(fù)制擁有后再返回可變引用;
  • into_owned(): 獲取一個(gè)擁有所有權(quán)的對象(區(qū)別與引用),如果當(dāng)前是借用,則發(fā)生復(fù)制,創(chuàng)建新的所有權(quán)對象,如果已擁有所有權(quán),則轉(zhuǎn)移至新對象。
impl

pub fn into_owned(self) -> ::Owned: into_owned方法用于抽取Cow所包裹類型B的所有者權(quán)的數(shù)據(jù),如果它還沒有所有權(quán)數(shù)據(jù)將會克隆一份。在一個(gè)Cow::Borrowed上調(diào)用into_owned,會克隆底層數(shù)據(jù)并成為Cow::Owned。在一個(gè)Cow::Owned上調(diào)用into_owned不會發(fā)生克隆操作。

**
例2:

use std::borrow::Cow;

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

    // 在一個(gè)`Cow::Borrowed`上調(diào)用`into_owned`,會克隆底層數(shù)據(jù)并成為`Cow::Owned`。
    let cow1 = Cow::Borrowed(s);

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

    // 在一個(gè)`Cow::Owned`上調(diào)用into_owned不會發(fā)生克隆操作。
    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的所有者權(quán)的數(shù)據(jù)獲得一個(gè)可變引用,如果它還沒有所有權(quán)數(shù)據(jù)將會克隆一份再返回其可變引用。

**
例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主要用來減少內(nèi)存的分配和復(fù)制,因?yàn)榻^大多數(shù)的場景都是讀多寫少。使用Cow可以在需要些的時(shí)候才做一次內(nèi)存復(fù)制,這樣就很大程度減少了內(nèi)存復(fù)制次數(shù)。

先來看官方文檔中的例子。

例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的需求是實(shí)現(xiàn)一個(gè)字符串敏感詞替換函數(shù),從給定的字符串替換掉預(yù)制的敏感詞。

例子中給出了remove_sensitive_wordremove_sensitive_word_old兩種實(shí)現(xiàn),前者的返回值使用了Cow,后者返回值使用的是String。仔細(xì)分析一下,很明顯前者的實(shí)現(xiàn)效率更高。因?yàn)槿绻斎氲淖址袥]有敏感詞時(shí),前者Cow::Borrowed(words)不會發(fā)生堆內(nèi)存的分配和拷貝,后者words.to_owned()會發(fā)生一次堆內(nèi)存的分配和拷貝。

試想一下,如果例5的敏感詞替換場景,是大多數(shù)情況下都不會發(fā)生替換的,即讀多寫少的場景,remove_sensitive_word實(shí)現(xiàn)中使用Cow作為返回值就在很多程度上提高了系統(tǒng)的效率。

總結(jié)

Cow 的設(shè)計(jì)目的是提高性能(減少復(fù)制)同時(shí)增加靈活性,因?yàn)榇蟛糠智闆r下,多用于讀多寫少的場景。利用 Cow,可以用統(tǒng)一,規(guī)范的形式實(shí)現(xiàn),需要寫的時(shí)候才做一次對象復(fù)制。

  1. 創(chuàng)建語義:Cow::Borrowed(v) 或者 Cow::Owned(v)
  2. 獲得本體:Cow::into_owned(),得到具有 所有權(quán)的值 ,如果之前Cow是Borrowed借用狀態(tài),調(diào)用into_owned將會克隆,如果已經(jīng)是Owned狀態(tài),將不會克隆
  3. 可變借用:Cow::to_mut(),得到一個(gè)具有所有權(quán)的值的 可變引用 ,注意在已經(jīng)具有所有權(quán)的情況下,也可以調(diào)用to_mut但不會產(chǎn)生新的克隆,多次調(diào)用to_mut只會產(chǎn)生一次克隆

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • COW
    COW
    +關(guān)注

    關(guān)注

    0

    文章

    4

    瀏覽量

    8016
  • rust語言
    +關(guān)注

    關(guān)注

    0

    文章

    57

    瀏覽量

    3009
收藏 人收藏

    評論

    相關(guān)推薦

    聊聊Rust與C語言交互的具體步驟

    rust FFI 是rust與其他語言互調(diào)的橋梁,通過FFI rust 可以有效繼承 C 語言的歷史資產(chǎn)。本期通過幾個(gè)例子來聊聊
    發(fā)表于 07-06 11:15 ?1710次閱讀

    如何使用Rust語言和paho-mqtt模塊實(shí)現(xiàn)MQTT協(xié)議

    模塊實(shí)現(xiàn)MQTT協(xié)議,并重點(diǎn)介紹LWT特征。 Rust是一種系統(tǒng)級編程語言,它的主要特點(diǎn)是安全、高效、并發(fā)。Rust編譯器會在編譯時(shí)進(jìn)行內(nèi)存安全檢查,避免了很多常見的內(nèi)存安全問題,如空指針
    的頭像 發(fā)表于 09-19 14:41 ?1982次閱讀

    基于Rust語言Hash特征的基礎(chǔ)用法和進(jìn)階用法

    Rust語言是一種系統(tǒng)級編程語言,具有高性能、安全、并發(fā)等特點(diǎn),是近年來備受關(guān)注的新興編程語言。在Rust
    的頭像 發(fā)表于 09-19 16:02 ?1466次閱讀

    Rust語言如何與 InfluxDB 集成

    Rust 是一種系統(tǒng)級編程語言,具有高性能和內(nèi)存安全性。InfluxDB 是一個(gè)開源的時(shí)間序列數(shù)據(jù)庫,用于存儲、查詢和可視化大規(guī)模數(shù)據(jù)集。Rust 語言可以與 InfluxDB 集成,
    的頭像 發(fā)表于 09-30 16:45 ?1172次閱讀

    基于Rust語言中的生命周期

    Rust是一門系統(tǒng)級編程語言具備高效、安和并發(fā)等特,而生命周期是這門語言中比較重要的概念之一。在這篇教程中,我們會了解什么是命周期、為什么需要生命周期、如何使用生命周期,同時(shí)我們依然會使用老朋友
    的頭像 發(fā)表于 09-19 17:03 ?907次閱讀

    Cow特征的使用方法和最佳實(shí)踐

    CowRust語言中的一個(gè)特殊類型,全稱為Clone-On-Write,即在寫入時(shí)進(jìn)行克隆操作。Cow類型可以用來避免不必要的內(nèi)存分配和復(fù)制操作,從而提高程序的性能和效率。
    的頭像 發(fā)表于 09-20 11:11 ?1096次閱讀

    如何用 rust 語言開發(fā) stm32

    本文介紹如何用 rust 語言開發(fā) stm32。開發(fā)平臺為 linux(gentoo)。硬件準(zhǔn)備本文使用的芯片為 STM32F103C8T6。該芯片性價(jià)比較高,價(jià)格低廉,適合入門學(xué)習(xí)。需要
    發(fā)表于 11-26 06:20

    C語言指針電子教程

    本資料是一份不錯(cuò)的關(guān)于C語言指針的電子教程,希望對大家有所幫助... 指針簡介 指針是C語言中廣泛使用的一種數(shù)據(jù)類型。 運(yùn)用
    發(fā)表于 07-30 16:00 ?77次下載

    嵌入式開發(fā)C語言指針

    學(xué)習(xí) C 語言指針既簡單又有趣。通過指針,可以簡化一些 C 編程任務(wù)的執(zhí)行。
    的頭像 發(fā)表于 11-06 17:09 ?3224次閱讀
    嵌入式開發(fā)<b class='flag-5'>之</b>C<b class='flag-5'>語言</b>的<b class='flag-5'>指針</b>

    以調(diào)試Rust的方式來學(xué)習(xí)Rust

    在我上一篇 關(guān)于 Rustup 的文章 中,我向你們展示了如何安裝 Rust 工具鏈。但是,如果不能上手操作一下 Rust 的話下載工具鏈又有什么用?學(xué)習(xí)任何語言都包括閱讀現(xiàn)有的代碼和
    的頭像 發(fā)表于 01-03 14:56 ?911次閱讀

    CRust學(xué)習(xí)筆記:智能指針和內(nèi)部可變性

    本系列文章是Jon Gjengset發(fā)布的CRust of Rust系列視頻的學(xué)習(xí)筆記,CRust of Rust是一系列持續(xù)更新的Rust中級教程。
    的頭像 發(fā)表于 01-29 14:58 ?828次閱讀

    C語言入門結(jié)構(gòu)體指針

    在C語言中,指向結(jié)構(gòu)體對象的指針變量既可以指向結(jié)構(gòu)體變量,也可指向結(jié)構(gòu)體數(shù)組中的元素。 指針變量的基類型必須與結(jié)構(gòu)體變量的類型相同。
    的頭像 發(fā)表于 03-24 14:59 ?1059次閱讀

    Rust的內(nèi)部工作原理

    智能指針傳遞self時(shí)生成的匯編方式 Rust遞歸樹生成的匯編代碼 更多見原文鏈接 原文鏈接:?https://www.eventhelix.com/rust/ libtracecmd-rs
    的頭像 發(fā)表于 06-14 10:34 ?798次閱讀
    <b class='flag-5'>Rust</b>的內(nèi)部工作原理

    C++智能指針的底層實(shí)現(xiàn)原理

    C++智能指針的頭文件: #include 1. shared_ptr: 智能指針從本質(zhì)上來說是一個(gè)模板類,用類實(shí)現(xiàn)對指針對象的管理。 template class shared_ptr
    的頭像 發(fā)表于 11-09 14:32 ?748次閱讀
    C++<b class='flag-5'>智能指針</b>的底層實(shí)現(xiàn)原理

    C語言指針學(xué)習(xí)筆記

    本文從底層內(nèi)存分析,徹底讓讀者明白C語言指針的本質(zhì)。
    的頭像 發(fā)表于 11-05 17:40 ?241次閱讀
    C<b class='flag-5'>語言</b><b class='flag-5'>指針</b><b class='flag-5'>學(xué)習(xí)</b>筆記