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

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

rust語言基礎學習: 智能指針之Cow

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

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

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,那么就可以將類型當做常規(guī)引用類型使用。

下面是Cow對Deref trait的實現:

impl

**
在實現上很簡單,match表達式中根據self是Borrowed還是Owned,分別取其內容,然后生成引用:

  • 對于Borrowed選項,其內容就是引用
  • 對于Owned選項,其內容是泛型參數B實現ToOwned中的關聯類型Owned,而Owned是實現Borrow trait的,所以owned.borrow()可以獲得引用

Cow<'a, B>通過對Deref trait的實現,就變得很厲害了,因為智能指針通過Deref的實現就可以獲得常規(guī)引用的使用體驗。對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(): 獲取一個擁有所有權的對象(區(qū)別與引用),如果當前是借用,則發(fā)生復制,創(chuàng)建新的所有權對象,如果已擁有所有權,則轉移至新對象。
impl

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

**
例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不會發(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的所有者權的數據獲得一個可變引用,如果它還沒有所有權數據將會克隆一份再返回其可變引用。

**
例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)不會發(fā)生堆內存的分配和拷貝,后者words.to_owned()會發(fā)生一次堆內存的分配和拷貝。

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

總結

Cow 的設計目的是提高性能(減少復制)同時增加靈活性,因為大部分情況下,多用于讀多寫少的場景。利用 Cow,可以用統(tǒng)一,規(guī)范的形式實現,需要寫的時候才做一次對象復制。

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

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

    關注

    0

    文章

    4

    瀏覽量

    7986
  • rust語言
    +關注

    關注

    0

    文章

    57

    瀏覽量

    2988
收藏 人收藏

    評論

    相關推薦

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

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

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

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

    基于Rust語言Hash特征的基礎用法和進階用法

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

    Rust語言如何與 InfluxDB 集成

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

    基于Rust語言中的生命周期

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

    Cow特征的使用方法和最佳實踐

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

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

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

    如何利用C語言去調用rust靜態(tài)庫呢

    語言的感覺,要做不少的對接工作。也用過Lua,感覺也差不多。評估學習評估Rust語言時,感覺性能和體積應該都不會有太大的問題。加上語言本身
    發(fā)表于 06-21 10:27

    C語言指針電子教程

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

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

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

    以調試Rust的方式來學習Rust

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

    CRust學習筆記:智能指針和內部可變性

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

    C語言入門結構體指針

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

    Rust的內部工作原理

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

    C++智能指針的底層實現原理

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