国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

rust 自動(dòng)化測(cè)試、迭代器與閉包、智能指針、無(wú)畏并發(fā)

這篇具有很好參考價(jià)值的文章主要介紹了rust 自動(dòng)化測(cè)試、迭代器與閉包、智能指針、無(wú)畏并發(fā)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

編寫測(cè)試可以讓我們的代碼在后續(xù)迭代過(guò)程中不出現(xiàn)功能性缺陷問(wèn)題;理解迭代器、閉包的函數(shù)式編程特性;Box<T>智能指針在堆上存儲(chǔ)數(shù)據(jù),Rc<T>智能指針開(kāi)啟多所有權(quán)模式等;理解并發(fā),如何安全的使用線程,共享數(shù)據(jù)。

自動(dòng)化測(cè)試

編寫測(cè)試以方便我們?cè)诤罄m(xù)的迭代過(guò)程中,不會(huì)改壞代碼。保證了程序的健壯性。

測(cè)試函數(shù)通常進(jìn)行如下操作:

  1. 設(shè)置需要的數(shù)據(jù)或狀態(tài)
  2. 運(yùn)行需要測(cè)試的代碼
  3. 斷言其結(jié)果是我們期望的

在 rust 中,通過(guò)test屬性、斷言宏和一些屬性設(shè)置來(lái)測(cè)試代碼。

$> cargo new ifun-grep --lib

創(chuàng)建項(xiàng)目時(shí),通過(guò)--lib表明創(chuàng)建一個(gè)庫(kù),會(huì)默認(rèn)生成一個(gè)測(cè)試示例,在src/lib.rs

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

進(jìn)入到項(xiàng)目中,執(zhí)行cargo test就會(huì)看到執(zhí)行完測(cè)試的詳細(xì)信息。包括了測(cè)試數(shù)量、通過(guò)測(cè)試數(shù)、失敗測(cè)試數(shù)等等維度

rust 自動(dòng)化測(cè)試、迭代器與閉包、智能指針、無(wú)畏并發(fā),rust,rust,開(kāi)發(fā)語(yǔ)言,前端,后端

首先使用mod tests定義了一個(gè) tests 模塊,內(nèi)部函數(shù)需要使用外部方法,在最頂部調(diào)用了use super::*;。這在包的一節(jié)里已有說(shuō)明。

#[cfg(test)]標(biāo)注測(cè)試模塊。它可以告訴 rust 在編譯時(shí)不需要包含該測(cè)試代碼。

#[test]表明是測(cè)試函數(shù),通過(guò) assert_eq!()斷言結(jié)果值是否相同。

可以手動(dòng)改動(dòng)一下斷言值assert_eq!(result, 5),再次執(zhí)行可以看到測(cè)試不通過(guò),并給出了結(jié)果的不同之處。

由 rust 標(biāo)準(zhǔn)庫(kù)提供的斷言測(cè)試宏,幫助我們處理結(jié)果值。結(jié)果與預(yù)期相同時(shí),則測(cè)試會(huì)通過(guò);不一樣時(shí),則會(huì)調(diào)用panic!宏,導(dǎo)致測(cè)試失敗。

  • assert!()一個(gè)必傳參數(shù),true是測(cè)試通過(guò);false測(cè)試失敗。
  • assert_eq!()兩個(gè)必傳參數(shù),比對(duì)它們是否相同。
  • assert_ne!兩個(gè)必傳參數(shù),比對(duì)它們是否不相同。

assert_eq!assert_ne斷言失敗時(shí),會(huì)打印出兩個(gè)值,便于觀察為什么失敗。因?yàn)闀?huì)打印輸出,所以兩個(gè)值必須實(shí)現(xiàn)PartialEqDebug trait可以被比較和輸出調(diào)試。

如果是我們自定義的結(jié)構(gòu)體或枚舉類型,則可以直接增加#[derive(PartialEq, Debug)]注解。如果是復(fù)雜的類型,則需要派生宏trait,這在后面的文章會(huì)講。

#[derive(PartialEq,Debug)]
struct User {
    name: String,
}

宏除了它們必須的參數(shù)之外,也可以傳遞更多的參數(shù),這些參數(shù)會(huì)被傳遞給format!()打印輸出。這樣我們可以增加一些輸出,方便解決斷言失敗的問(wèn)題

assert_eq!(result, 5, "hello rust!");

測(cè)試程序處理錯(cuò)誤

除了測(cè)試程序正常執(zhí)行邏輯的結(jié)果,也需要測(cè)試程序發(fā)生錯(cuò)誤時(shí),是否按照我們的錯(cuò)誤處理邏輯 處理了錯(cuò)誤。

假設(shè)我們的被測(cè)試函數(shù)接受的參數(shù)不能大于100,大于時(shí)panic錯(cuò)誤 信息

pub fn add(left: usize, right: usize) -> usize {
    if right > 100 {
        panic!("the value exceeds 100!");
    }
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 102);
        assert_eq!(result, 104);
    }
}

執(zhí)行測(cè)試cargo test,就算斷言結(jié)果時(shí)邏輯正確的,但是我們的函數(shù)限制了參數(shù)最大值,測(cè)試不通過(guò)。

增加測(cè)試用例來(lái)測(cè)試這種場(chǎng)景,通過(guò)增加#[should_panic]來(lái)處理程序確實(shí)有這種限制,并panic!。

#[test]
#[should_panic]
fn value_exceed_100() {
    add(5, 120);
}

執(zhí)行cargo test,可以看到測(cè)試示例通過(guò)了。如果我們符合參數(shù)要求,測(cè)試示例就會(huì)是失敗

rust 自動(dòng)化測(cè)試、迭代器與閉包、智能指針、無(wú)畏并發(fā),rust,rust,開(kāi)發(fā)語(yǔ)言,前端,后端

但如果我們代碼中有多個(gè)錯(cuò)誤panic!(),就會(huì)有同樣的多個(gè)測(cè)試示例不通過(guò),打印輸出并沒(méi)有給我們足夠的信息去找到問(wèn)題所在。
通過(guò)should_panic可選擇參數(shù)expected提供一個(gè)錯(cuò)誤描述信息,

pub fn add(left: usize, right: usize) -> usize {
    if right > 100 {
        panic!("the value exceeds 100!,got {}", right)
    } else if right < 50 {
        panic!("the value does not less than 50!,got {}", right)
    }
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic(expected = "exceeds 100!")]
    fn value_exceed_100() {
        add(5, 99);
    }
    #[test]
    #[should_panic(expected = "less than 50!")]
    fn value_not_less_50() {
        add(59, 59);
    }
}

也可以通過(guò)Result<T,E>編寫測(cè)試,在程序失敗時(shí),返回Err而不是panic;

#[test]
fn add_equal() -> Result<(), String> {
    if add(5, 105) == 111 {
        Ok(())
    } else {
        Err(String::from("Error in add"))
    }
}

此時(shí)不能使用#[should_panic()]注解。也不能使用表達(dá)式?

控制測(cè)試運(yùn)行

cargo test在測(cè)試模式下編譯代碼并發(fā)運(yùn)行生成的測(cè)試二進(jìn)制文件。

  1. 可以通過(guò)設(shè)置測(cè)試線程,單次只執(zhí)行一個(gè)測(cè)試示例
$> cargo test -- --test-threads=1

測(cè)試線程為 1,程序不會(huì)使用任何并行機(jī)制。

  1. 默認(rèn)的測(cè)試在測(cè)試示例通過(guò)時(shí),不會(huì)打印輸出。通過(guò)設(shè)置在測(cè)試成功時(shí)也輸出程序中的打印
$> cargo test -- --show-output
  1. 默認(rèn)的cargo test會(huì)運(yùn)行所有測(cè)試,通過(guò)指定名稱來(lái)運(yùn)行部分測(cè)試
$> cargot test add_equal

過(guò)濾運(yùn)行多個(gè)測(cè)試,可以通過(guò)指定測(cè)試名稱的一部分,只要匹配這個(gè)名稱的測(cè)試都會(huì)被運(yùn)行。

$> cargot test value

通過(guò)#[ignore]標(biāo)記忽略該測(cè)試。

#[test]
#[ignore]
fn add_equal() -> Result<(), String> {
    if add(5, 105) == 110 {
        Ok(())
    } else {
        Err(String::from("Error in add"))
    }
}

測(cè)試被忽略,但是可以通過(guò)cargot test -- --ignored來(lái)運(yùn)行被忽略的測(cè)試。

如果想運(yùn)行所有的測(cè)試,可以通過(guò)cargot test -- --include-ignored

集成測(cè)試

單元測(cè)試可以在指定的模塊中書寫測(cè)試實(shí)例,每次測(cè)試一個(gè)模塊,也可以測(cè)試私有接口。

集成測(cè)試對(duì)庫(kù)來(lái)說(shuō)是外部的,只能測(cè)試公有接口,可測(cè)試多個(gè)模塊。通過(guò)創(chuàng)建tests目錄編寫?yīng)毩⒌臏y(cè)試文件。

tests/lib.rs

use ifun_grep;

#[test]
#[should_panic(expected = "exceeds")]
fn value_exceed_100() {
    ifun_grep::add(5, 99);
}

隨著集成測(cè)試模塊的增多,我們需要更好的組織它們,可以根據(jù)測(cè)試的功能將測(cè)試分組。將一些測(cè)試公共模塊抽離出來(lái),作為其他測(cè)試功能組的測(cè)試函數(shù)調(diào)用

比如tests/common.rs

pub fn init(){
    // something init
}

再執(zhí)行cargo test,會(huì)看到運(yùn)行了tests/common.rs 運(yùn)行了 0 個(gè)測(cè)試。這顯然是我們不需要的,可以改寫文件目錄tests/common/mod.rs,這會(huì)告訴 rust 不要將common看作一個(gè)集成測(cè)試文件。

迭代器與閉包

rust 類似函數(shù)式編程語(yǔ)言的特性??梢詫⒑瘮?shù)作為參數(shù)值或返回值、將函數(shù)賦值給變量等。

閉包

可以儲(chǔ)存在變量里的類似函數(shù)的結(jié)構(gòu)。保存在一個(gè)變量中或作為參數(shù)傳遞給其他函數(shù)的匿名函數(shù)。

閉包允許捕獲被定義時(shí)所在作用域中的值。

#[derive(Debug)]
enum Name {
    Admin,
    Test,
}
#[derive(Debug)]
struct User {}

impl User {
    fn get_name(&self, name: Option<Name>) -> Name {
        name.unwrap_or_else(|| self.random_name())
    }

    fn random_name(&self) -> Name {
        Name::Admin
    }
}

fn main(){
    let user = User {};
    println!("{:?}", user.get_name(Some(Name::Test)));
    println!("{:?}", user.get_name(None));
}

unwrap_or_else方法接受一個(gè)閉包函數(shù),當(dāng)一個(gè)Some值存在時(shí)直接返回,如果不存在則執(zhí)行其傳入的閉包函數(shù)計(jì)算一個(gè)值返回。

閉包不需要在參數(shù)或返回值上注明類型。閉包通常只關(guān)聯(lián)小范圍的上下文而非任意情景,所以編譯器可以推導(dǎo)出參數(shù)和返回值類型。

也可以顯示定義閉包的參數(shù)和返回值的類型:

fn main(){
    let get_age = |age: i8| -> i8 { age };
    // let get_age = |age| age;

    println!("{}", get_age(32));
}

相對(duì)于增加參數(shù)或返回值類型使得書寫更加的繁瑣。而對(duì)于未標(biāo)注類型的閉包,在第一次調(diào)用后就確定其參數(shù)和返回值類型,再傳其他類型時(shí)就會(huì)報(bào)錯(cuò)。

fn main(){
    let get_age = |age| age;

    println!("{}", get_age(String::from("admin")));
    // 調(diào)用出錯(cuò),已經(jīng)確定了參數(shù)和返回值類型為String
    println!("{}", get_age(32));
}
捕獲引用或移動(dòng)所有權(quán)

在傳遞給閉包參數(shù)時(shí),需要考慮參數(shù)的傳遞方式:不可變借用、可變借用和獲取所有權(quán)。這是根據(jù)傳遞的值決定的。

對(duì)于不可變借用,變量可以在任何情形下被訪問(wèn)。

let str = String::from("hboot");
let print_str = || println!("{:?}", str);

println!("{str}");
print_str();
println!("{str}");

而對(duì)于可變借用,則只能在借用結(jié)束后調(diào)用.聲明的閉包函數(shù)也需要mut聲明

let mut str = String::from("hboot");
let mut print_str = || str.push_str("-rust");

// println!("{str}");
print_str();
println!("{str}");

通過(guò)move關(guān)鍵字將變量的所有權(quán)轉(zhuǎn)移閉包所在的環(huán)境中。

use std::thread;

fn main(){
    let mut str = String::from("hboot");
    println!("{str}");

    thread::spawn(move || {
        str.push_str("-rust");
        println!("{str}")
    })
    .join()
    .unwrap();
}

此時(shí),將變量str值的所有權(quán)轉(zhuǎn)移到了新線程中,主線程則不能再使用。

將被捕獲的值移出閉包和 Fn trait

在閉包環(huán)境中,捕獲和處理值的方式會(huì)影響閉包 trait 的實(shí)現(xiàn)。trait 是函數(shù)或結(jié)構(gòu)體指定它們可以使用什么類型的閉包。

從閉包如何任何處理值、閉包自動(dòng)、漸進(jìn)實(shí)現(xiàn)一個(gè)、多個(gè) Fntrait

  • FnOnce 適用于調(diào)用一次的閉包。所有閉包都是實(shí)現(xiàn)這個(gè) trait,它會(huì)將捕獲的值移除閉包。
  • FnMut 不會(huì)將捕獲的值移除閉包,可能會(huì)修改值。會(huì)被調(diào)用 多次。
  • Fn 不會(huì)移除捕獲的值,也不修改捕獲的值。會(huì)被調(diào)用多次而不改變其環(huán)境。

這是Option<T>unwrap_or_else()方法定義

impl<T> Option<T> {
    pub fn unwrap_or_else<F>(self, f: F) -> T
    where
        F: FnOnce() -> T
    {
        match self {
            Some(x) => x,
            None => f(),
        }
    }
}

F就是閉包指定的類型,T是返回值類型。FnOnce()->T表明了閉包會(huì)被調(diào)用一次,有值時(shí)Some,返回值;沒(méi)有值時(shí)None,f調(diào)用一次。

在使用閉包時(shí),如果我們不需要捕獲其環(huán)境中的值,則可以不使用閉包,而使用傳遞函數(shù)作為參數(shù)。

迭代器

迭代器是處理元素序列的方式。遍歷序列中的每一項(xiàng)以及決定序列何時(shí)結(jié)束的邏輯。

fn main(){
    let arr = [1, 2, 3, 4];

    for val in arr {
        println!("{}", val)
    }
}

迭代器都定義了Iteratortrait,并實(shí)現(xiàn)next方法。調(diào)用next返回迭代器的一個(gè)項(xiàng),封裝在Some中,結(jié)束后返回None

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

type ItemSelf::Item定義了 trait 的關(guān)聯(lián)類型。表明了迭代器返回值類型為Item

可以通過(guò)next()方法迭代獲取值:

fn main(){
    let arr = [1, 2, 3, 4];

    let mut iter = arr.iter();
    println!("{:?}", iter.next());
    println!("{:?}", iter.next());
}

iter()生成一個(gè)不可變引用的迭代器。對(duì)于迭代器實(shí)例iter必須是mut可變的。

  • into_ter()獲取到 arr 所有權(quán)的迭代器。
  • iter_mut()可以獲取到可變引用迭代器。
消費(fèi)適配器

調(diào)用next()方法的方法被稱為消費(fèi)適配器。

fn main() {
    let arr = [1, 2, 3, 4];

    let total: i8 = arr.iter().sum();
    println!("{}", total);
}

這些方法總是會(huì)獲取迭代器的所有權(quán)并反復(fù)調(diào)用 next來(lái)遍歷迭代器。sum()方法返回調(diào)用next方法獲取值,最終返回和值。

迭代器適配器

將當(dāng)前迭代器變?yōu)椴煌愋偷牡???梢枣準(zhǔn)秸{(diào)用多個(gè)迭代器適配器,但是每次調(diào)用都必須調(diào)用消費(fèi)適配器來(lái)獲取調(diào)用結(jié)果。

fn main(){
    let arr = [1, 2, 3, 4];
    let arr2: Vec<_> = arr.iter().map(|val| val + 1).collect();

    for val in arr2 {
        println!("{}", val)
    }
}

map()方法接受一個(gè)閉包函數(shù),可以在遍歷元素上執(zhí)行任何操作。進(jìn)行了一次迭代適配器操作,然后通過(guò)collect()方法獲取調(diào)用的結(jié)果值。

智能指針

指針是一個(gè)包含內(nèi)存地址的變量。智能指針是一類數(shù)據(jù)結(jié)構(gòu),表現(xiàn)同指針,并擁有額外的元數(shù)據(jù)和功能。

智能指針通常使用結(jié)構(gòu)體實(shí)現(xiàn),實(shí)現(xiàn)了DerefDroptrait。deref trait 允許智能指針結(jié)構(gòu)體實(shí)例表現(xiàn)的像引用一樣;drop trait 允許智能指針離開(kāi)作用域時(shí)自定義運(yùn)行代碼

標(biāo)準(zhǔn)庫(kù)中常用的智能指針:

  • Box<T> 用于在堆上分配值
  • Rc<T> 引用計(jì)數(shù)類型,其數(shù)據(jù)可以有多個(gè)所有者
  • Ref<T>、RefMut<T> 通過(guò)RefCell<T>訪問(wèn),這是一個(gè)在運(yùn)行時(shí)執(zhí)行借用規(guī)則的類型。

Box<T>

智能指針 box 允許將一個(gè)值放在堆上而不是棧上。留在棧上的則是指向堆數(shù)據(jù)的指針。

在以下情況下可以考慮使用:

  • 編譯時(shí)未知大小的類型,又想在確切大小的上下文中使用這個(gè)類型的值。
  • 當(dāng)有大量數(shù)據(jù)不被拷貝的情況下轉(zhuǎn)移所有權(quán)的時(shí)候
  • 當(dāng)有一個(gè)值只關(guān)心它的類型是否實(shí)現(xiàn)特定 trait,而不是具體類型的時(shí)候
fn main(){
    let b = Box::new(100);
    println!("{}", b);
}

直接聲明創(chuàng)建 box 類型變量,并分配了一個(gè)值100存儲(chǔ)在堆上, 可以直接訪問(wèn)變量訪問(wèn)值。

通過(guò)cons list 數(shù)據(jù)結(jié)構(gòu)定義遞歸數(shù)據(jù)類型

它是construct function的縮寫,利用兩個(gè)參數(shù)構(gòu)造一個(gè)新的列表.最后一項(xiàng)值包含了Nil值,標(biāo)識(shí)結(jié)束

enum List {
    Cons(i32, Box<List>),
    Nil,
}
use crate::List::{Cons, Nil};

fn main(){
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}

Cons可能會(huì)無(wú)限嵌套下去,為了保證 rust 編譯時(shí)計(jì)算需要的大小,只能通過(guò)Box來(lái)幫助 rust 計(jì)算出List需要的大小。

Dereftrait 重載解引用運(yùn)算符*

之前已經(jīng)使用過(guò)*解引用值,可以獲取到指針指向引用的值。

fn main(){
    let mut s = String::from("hboot");
    let s1 = &mut s;
    *s1 += " admin";

    println!("{}", s)
}

s1是 s 的可變引用,再通過(guò)*解引用后,可以修改存儲(chǔ)在堆上的數(shù)據(jù)。

也可以通過(guò)Box<T>代替引用,和*擁有相同的功能。

fn main(){
    let s = String::from("hboot");

    let mut s1 = Box::new(s);
    *s1 += " admin";

    println!("{:?}", s1);
}

Box會(huì)拷貝s在棧上的指針數(shù)據(jù),導(dǎo)致存儲(chǔ)在堆上的數(shù)據(jù)所有權(quán)被轉(zhuǎn)移,s在后續(xù)變的不可用。

自定義實(shí)現(xiàn)一個(gè)智能指針MyBox,它可以做到上面的解引用操作

#[derive(Debug)]
struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(val: T) -> MyBox<T> {
        MyBox(val)
    }
}

實(shí)現(xiàn)了一個(gè)元組結(jié)構(gòu)體,自定義實(shí)例new方法,接受一個(gè)參數(shù)進(jìn)行初始化操作。還需要實(shí)現(xiàn)解引用功能,Dereftrait 由標(biāo)準(zhǔn)庫(kù)提供,實(shí)現(xiàn) deref 方法

use std::ops::Deref;

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

上述的解引用例子,則可以由MyBox代替實(shí)現(xiàn)。type Target = T定義了 trait 的關(guān)聯(lián)類型,&self.0訪問(wèn)元組結(jié)構(gòu)體的第一個(gè)元素。

fn main(){
    let s = String::from("hboot");

    let s1 = MyBox::new(s);
    // *s1 += " admin";

    println!("{:?}", *s1);
}

因?yàn)閷?shí)現(xiàn)的是Deref所以不能修改,修改時(shí)需要實(shí)現(xiàn)DerefMuttrait。

實(shí)現(xiàn)了Dereftrait 的數(shù)據(jù)類型,在函數(shù)傳參時(shí),可做到隱式轉(zhuǎn)換,而不需要手動(dòng)去轉(zhuǎn)換為參數(shù)需要的類型。

fn print(val: &str) {
    println!("{}", val)
}

fn main(){
    // 輸出上面的示例 s1
    print(&s1);
}

對(duì)于數(shù)據(jù)的強(qiáng)制轉(zhuǎn)換,只能將可變引用轉(zhuǎn)為不可變引用;不能將不可變引用轉(zhuǎn)為可變引用。

Drop trait 運(yùn)行清理代碼

實(shí)現(xiàn)了Droptrait 的數(shù)據(jù),在離開(kāi)作用域時(shí),會(huì)調(diào)用其實(shí)現(xiàn)的drop方法,它獲取一個(gè)可變引用。

為上述的MyBox實(shí)現(xiàn)Drop,無(wú)需引入,Droptrait 是 prelude 的。

impl<T> Drop for MyBox<T> {
    fn drop(&mut self) {
        println!("mybox drop value");
    }
}

再次調(diào)用執(zhí)行,可以看到最終在程序執(zhí)行完畢后,打印輸出了mybox drop value. drop會(huì)自動(dòng)執(zhí)行,而無(wú)需手動(dòng)調(diào)用。

如果想要提前銷毀資源,則需要std::mem::drop,可以調(diào)用drop方法

fn main(){

    drop(s1);
    // 手動(dòng)清理后,后續(xù)不能再使用s1
    // print(&s1);
}

Rc<T> 引用計(jì)數(shù)啟用多所有權(quán)模式

在圖形結(jié)構(gòu)中,每個(gè)節(jié)點(diǎn)都有多個(gè)邊指向,所以每個(gè)節(jié)點(diǎn)都會(huì)擁有指向它的邊的所有權(quán)。

通過(guò)使用Rc<T>類型,記錄被引用的數(shù)量,來(lái)確定這個(gè)值有沒(méi)有被引用。如果為 0 沒(méi)有被引用,則會(huì)被清理。

Rc<T> 只適用于單線程

創(chuàng)建Rc類型的變量s,然后通過(guò)Rc::clone克隆變量s生成s1\s2.

use std::rc::Rc;

fn main(){
    let s = Rc::new(String::from("hboot"));

    let s1 = Rc::clone(&s);
    let s2 = Rc::clone(&s);

    println!("s:{},s1:{},s2:{}", s, s1, s2)
}

這里可以看到s1、s2沒(méi)有獲取s的所有權(quán),它們?nèi)匀煌瑫r(shí)生效。Rc::clone不同于深拷貝,只會(huì)增加引用計(jì)數(shù)。

可以通過(guò)strong_count()方法查看被引用次數(shù)

fn main(){
    let s = Rc::new(String::from("hboot"));
    println!("s create - {}", Rc::strong_count(&s));
    let s1 = Rc::clone(&s);
    println!("s1 create - {}", Rc::strong_count(&s));

    {
        let s2 = Rc::clone(&s);
        println!("s2 create - {}", Rc::strong_count(&s));
    }

    println!("s2 goes out of scope  - {}", Rc::strong_count(&s));
}

執(zhí)行測(cè)試輸出為

rust 自動(dòng)化測(cè)試、迭代器與閉包、智能指針、無(wú)畏并發(fā),rust,rust,開(kāi)發(fā)語(yǔ)言,前端,后端

通過(guò)不可變引用,Rc<T>允許程序在多個(gè)部分之間只讀地共享數(shù)據(jù)。

RefCell<T> 允許修改不可變引用

根據(jù) rust 的不可變引用規(guī)則,被引用的變量是不允許修改。但是在某些模式下,可以做到修改,也就是內(nèi)部可變性模式。

內(nèi)部可變性通過(guò)在數(shù)據(jù)結(jié)構(gòu)中使用unsafe代碼來(lái)模糊 rust 的不可變性和借用規(guī)則。unsafe不安全代碼表明我們需要手動(dòng)去檢查代碼而不是讓編譯器檢查。

RefCell<T>類型是在代碼運(yùn)行時(shí)作用檢測(cè)不可變或可變借用規(guī)則,而通常的規(guī)則檢測(cè)是在編譯階段。

特點(diǎn):

  • 可以在允許出現(xiàn)特定內(nèi)存安全的場(chǎng)景中使用。
  • 需要確認(rèn)你的代碼遵守了借用規(guī)則,但是 rust 無(wú)法理解
  • 只能用于單線程

RefCell<T> 在運(yùn)行時(shí)記錄借用,通過(guò)borrow()borrow_mut()方法,會(huì)返回Ref<T>RefMut<T>智能指針,并實(shí)現(xiàn)了Dereftrait.

定義一個(gè)MixNametrait,然后結(jié)構(gòu)體User實(shí)現(xiàn)了它,并實(shí)現(xiàn)它的方法mix.

use std::cell::RefCell;

pub trait MixName {
    fn mix(&self, suffix: &str);
}

struct User {
    name: RefCell<String>,
}

impl User {
    fn new() -> User {
        User {
            name: RefCell::new(String::from("hboot")),
        }
    }
}

impl MixName for User {
    fn mix(&self, suffix: &str) {
        self.name.borrow_mut().push_str(suffix);
    }
}

mix方法修改了 self 內(nèi)部屬性name的值,但是我們可以看到&self時(shí)不可變引用,這歸功于RefCell<T>創(chuàng)建值,使得不可變借用可以修改其內(nèi)部值。

fn main(){
    let user = User::new();

    user.mix(" hello");
    println!("{:?}", user.name.borrow());
}

執(zhí)行程序可以看到內(nèi)部的值已經(jīng)被修改了。RefCell<T>會(huì)在調(diào)用borrow時(shí),記錄借用次數(shù),當(dāng)離開(kāi)了作用域時(shí),借用次數(shù)減一。

RefCell<T>只能有一個(gè)所有者,結(jié)合Rc<T>使其擁有多個(gè)可變數(shù)據(jù)所有者。

use std::cell::RefCell;
use std::rc::Rc;

fn main(){
    let s = Rc::new(RefCell::new(String::from("hboot")));

    let s1 = Rc::clone(&s);

    let s2 = Rc::clone(&s);

    *s.borrow_mut() += " good";
    println!("{:?}", s);
}

通過(guò)RefCell來(lái)創(chuàng)建變量,然后通過(guò)Rc開(kāi)啟多所有權(quán),這樣在*s.borrow_mut() += " good";,修改后,變量s、s1、s2的值都發(fā)生了變更。

但是這只能在單線中使用,如果想要多線程使用,則需要使用并發(fā)安全的Mutex<T>類型。

無(wú)畏并發(fā)

并發(fā)編程 - 代表程序的不同部分相互獨(dú)立的運(yùn)行。

并行編程 - 代表程序不同部分同時(shí)執(zhí)行。

thread多線程運(yùn)行代碼

多線程運(yùn)行代碼可以提高程序的執(zhí)行效率。也會(huì)造成一些問(wèn)題

  • 多個(gè)線程在不同時(shí)刻訪問(wèn)同一數(shù)據(jù)資源,形成競(jìng)爭(zhēng)
  • 相互等待對(duì)方,造成死鎖
  • 一些情況下出現(xiàn)的難以修復(fù)的 bug

使用thread::spawn創(chuàng)建一個(gè)線程,它接受一個(gè)閉包函數(shù)

use std::thread;

fn main() {
    thread::spawn(|| {
        println!("hello!");
    });

    println!("rust!");
}

可以看到輸出,先是rust!,也就是主線程先執(zhí)行??梢远啻螆?zhí)行cargo run以觀察結(jié)果,會(huì)出現(xiàn)新線程沒(méi)有打印輸出,這是因?yàn)橹骶€程結(jié)束,新線程也會(huì)結(jié)束,而不會(huì)等待新線程是否執(zhí)行完畢。

可以通過(guò)線程休眠,展示這一特點(diǎn)

use std::thread;
use std::time::Duration;

fn main() {
    thread::spawn(|| {
        thread::sleep(Duration::from_millis(2));
        println!("hello!");
    });

    println!("rust!");
}

程序基本沒(méi)有什么機(jī)會(huì)切換到新線程去執(zhí)行,也看不到新線程的打印輸出。

可以通過(guò)thread::spawn的返回值線程實(shí)例,然后調(diào)用join()方法,來(lái)等待線程結(jié)束

let thread = thread::spawn(|| {
    thread::sleep(Duration::from_millis(2));
    println!("hello!");
});

println!("rust!");

thread.join().unwrap();

再次執(zhí)行,可以看到新線程的打印輸出。join()會(huì)阻塞當(dāng)前線程,知道線程實(shí)例thread執(zhí)行完畢。
可以將thread.join().unwrap();放在主線程輸出之前,優(yōu)先執(zhí)行

thread.join().unwrap();

println!("rust!");

通過(guò)move關(guān)鍵字強(qiáng)制閉包獲取其所有權(quán),thread::spawn創(chuàng)建線程給的閉包函數(shù)沒(méi)有任何參數(shù),需要使用主線程里的變量

let name = String::from("hboot");

let thread = thread::spawn(move || {
    thread::sleep(Duration::from_millis(2));
    println!("hello! - {}", name);
});

新線程強(qiáng)制獲取了環(huán)境中變量的所有權(quán),保證了新線程執(zhí)行不會(huì)出錯(cuò)。如果是引用,那么由于新線程的執(zhí)行順序,可能會(huì)在主線程執(zhí)行過(guò)程使引用失效,從而導(dǎo)致新線程執(zhí)行報(bào)錯(cuò)

線程間消息傳遞

通過(guò)channel實(shí)現(xiàn)線程間消息的傳遞并發(fā)。

通過(guò)mpsc::channel創(chuàng)建通信通道,這個(gè)通道可以有多個(gè)發(fā)送端,但只能有一個(gè)接收端.

use std::sync::mpsc;

fn main(){
    let (send, receive) = mpsc::channel();

    thread::spawn(move || {
        let name = String::from("rust");
        send.send(name).unwrap();
    });

    let receive_str = receive.recv().unwrap();

    println!("get thread msg :{}", receive_str);
}

mpsc::channel()生成一個(gè)通過(guò),返回一個(gè)元組,第一個(gè)是發(fā)送者,第二個(gè)是接收者。然后創(chuàng)建一個(gè)新線程,通過(guò)實(shí)例對(duì)象send發(fā)送一條信息;在主線程中通過(guò)實(shí)例對(duì)象receive接受數(shù)據(jù)。

不管是send()發(fā)送方法還是recv()方法,它們都返回Result<T,E>類型,如果接受端或發(fā)送端被清除了,則會(huì)返回錯(cuò)誤。

接受recv()方法是阻塞線程的,也就是必須接收到一個(gè)值。還有一個(gè)方法try_recv()方法則不會(huì)阻塞,需要頻繁去調(diào)用,在有可用消息時(shí)進(jìn)行處理。

新線程將變量name發(fā)送出去,那么它的所有權(quán)也被轉(zhuǎn)移 出去了,后續(xù)不能使用它

send.send(name).unwrap();

// 在發(fā)送后,不能再使用改變量
println!("{}", name);

當(dāng)在子線程中連續(xù)多次發(fā)送多個(gè)值時(shí),可以通過(guò)迭代器遍歷receive獲取值

fn main(){
    let (send, receive) = mpsc::channel();

    thread::spawn(move || {
        send.send(1).unwrap();
        send.send(10).unwrap();
        send.send(100).unwrap();
    });

    for receive_str in receive {
        println!("{}", receive_str);
    }
}

上述例子只是單發(fā)送者,可以通過(guò)clone()方法克隆send發(fā)送對(duì)象,然后傳給另一個(gè)線程

fn main(){
    let (send, receive) = mpsc::channel();

     let send_str = send.clone();
    thread::spawn(move || {
        send_str.send("hello").unwrap();
        send_str.send("rust").unwrap();
    });

    thread::spawn(move || {
        send.send("good").unwrap();
        send.send("hboot").unwrap();
    });

    for receive_str in receive {
        println!("{}", receive_str);
    }
}

創(chuàng)建兩個(gè)線程,一個(gè)線程傳入時(shí)克隆的send_str,它們都發(fā)送消息,然后在主線程中,接收到所有消息。

多個(gè)線程由于執(zhí)行順序?qū)е麓蛴≥敵龅捻樞蛞膊槐M相同。這依賴于系統(tǒng),我們可以通過(guò)線程休眠做實(shí)驗(yàn),觀察到輸出的順序不同

線程間共享狀態(tài)

除了相互之間發(fā)送消息外, 還可以通過(guò)共享數(shù)據(jù),來(lái)傳遞數(shù)據(jù)狀態(tài)變化。

通過(guò)Mutex<T>創(chuàng)建共享數(shù)據(jù),在需要使用的線程中通過(guò)lock()獲取鎖,以訪問(wèn)數(shù)據(jù)。

use std::sync::{Mutex};

fn main()[
    let name = Mutex::new(String::from("hboot"));

    {
        let mut name = name.lock().unwrap();

        *name += " good!";
    }

    println!("{:?}", name.lock().unwrap());
]

新創(chuàng)建的數(shù)據(jù)hboot,在局部作用域中獲取鎖,然后解引用后變更值,最終打印輸出可以看到變更后的數(shù)據(jù)。

Mutext<T>是一個(gè)智能指針,調(diào)用lock()返回了一個(gè)MutexGuard智能指針,它實(shí)現(xiàn)了Deref來(lái)指向內(nèi)部數(shù)據(jù),同時(shí)也提供Drop實(shí)現(xiàn)了當(dāng)離開(kāi)作用域時(shí)自動(dòng)釋放鎖。

正因?yàn)檫@樣,我們?cè)诰幋a時(shí),不會(huì)因?yàn)橥涐尫沛i而導(dǎo)致其他線程訪問(wèn)不了數(shù)據(jù)。

如果想要在多個(gè)線程中訪問(wèn)共享數(shù)據(jù),因?yàn)榫€程需要轉(zhuǎn)移所有權(quán),這樣導(dǎo)致共享數(shù)據(jù)每次只能在一個(gè)線程中使用,通過(guò)Arc<T>來(lái)創(chuàng)建多所有者,使得共享數(shù)據(jù)可被多個(gè)線程同時(shí)訪問(wèn)。

use std::sync::{Arc, Mutex};
use std::thread;

fn main(){
    let name = Arc::new(Mutex::new(String::from("hboot")));

    let mut thread_arr = vec![];
    for val in ["admin", "test", "hello", "rust"] {
        let name = Arc::clone(&name);

        let thread = thread::spawn(move || {
            let mut name = name.lock().unwrap();

            *name += val;
        });

        thread_arr.push(thread);
    }

    for thread in thread_arr {
        thread.join().unwrap();
    }
    println!("{:?}", name.lock().unwrap())
}

Arc<T>擁有和Rc<T>相同的 api,它可以用于并發(fā)環(huán)境的類型。這是一個(gè)原子引用計(jì)數(shù)類型。

Mutex<T>RefCell<T>一樣,提供了內(nèi)部可變性,通過(guò)獲取內(nèi)布值的可變引用修改值。當(dāng)然,Mutex<T>也會(huì)有出現(xiàn)相互引用鎖死的風(fēng)險(xiǎn),兩個(gè)線程需要鎖住兩個(gè)資源而各自已經(jīng)鎖了一個(gè),造成了互相等待的問(wèn)題。

Sync和Send trait擴(kuò)展并發(fā)

除了使用 rust 標(biāo)準(zhǔn)庫(kù)提供的處理并發(fā)問(wèn)題,還可以使用別人編寫的并發(fā)功能

當(dāng)嘗試編寫并發(fā)功能時(shí),有兩個(gè)并發(fā)概念:

  • 通過(guò)Send trait表明實(shí)現(xiàn)了Send的類型值的所有權(quán)可以在線程間傳遞。rust 幾乎所有類型都是Send, 還有一些不能Send,比如Rc<T>,它只能用于單線程,

  • 通過(guò)Sync trait 表明實(shí)現(xiàn)了Sync的類型可以安全的在多個(gè)線程中擁有其值的引用。Rc<T>、RefCell<T>都不是Sync類型的。

根據(jù)這兩個(gè)概念,可以手動(dòng)創(chuàng)建用于并發(fā)功能的并發(fā)類型,在使用時(shí)需要多加小心,以維護(hù)其安全保證。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-559088.html

到了這里,關(guān)于rust 自動(dòng)化測(cè)試、迭代器與閉包、智能指針、無(wú)畏并發(fā)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【Rust】——編寫自動(dòng)化測(cè)試(一)

    ??個(gè)人專欄: ?? 算法設(shè)計(jì)與分析:算法設(shè)計(jì)與分析_IT閆的博客-CSDN博客 ??Java基礎(chǔ):Java基礎(chǔ)_IT閆的博客-CSDN博客 ??c語(yǔ)言:c語(yǔ)言_IT閆的博客-CSDN博客 ??MySQL:數(shù)據(jù)結(jié)構(gòu)_IT閆的博客-CSDN博客 ??數(shù)據(jù)結(jié)構(gòu):??????數(shù)據(jù)結(jié)構(gòu)_IT閆的博客-CSDN博客 ??C++:C++_IT閆的博客-CSDN博

    2024年04月09日
    瀏覽(24)
  • Rust之自動(dòng)化測(cè)試(一):如何編寫測(cè)試

    Rust之自動(dòng)化測(cè)試(一):如何編寫測(cè)試

    Windows 10 Rust 1.71.1 ? VS Code 1.81.1 這里繼續(xù)沿用上次工程rust-demo Edsger W. Dijkstra在他1972年的文章《謙遜的程序員》中說(shuō),“程序測(cè)試可以是一種非常有效的方法來(lái)顯示錯(cuò)誤的存在,但它對(duì)于顯示它們的不存在是完全不夠的?!边@并不意味著我們不應(yīng)該盡可能多地嘗試測(cè)試!? 我們

    2024年02月11日
    瀏覽(31)
  • 【Rust】Rust學(xué)習(xí) 第十一章編寫自動(dòng)化測(cè)試

    【Rust】Rust學(xué)習(xí) 第十一章編寫自動(dòng)化測(cè)試

    Rust 是一個(gè)相當(dāng)注重正確性的編程語(yǔ)言,不過(guò)正確性是一個(gè)難以證明的復(fù)雜主題。Rust 的類型系統(tǒng)在此問(wèn)題上下了很大的功夫,不過(guò)它不可能捕獲所有種類的錯(cuò)誤。為此,Rust 也在語(yǔ)言本身包含了編寫軟件測(cè)試的支持。 編寫一個(gè)叫做? add_two ?的將傳遞給它的值加二的函數(shù)。它

    2024年02月13日
    瀏覽(27)
  • Slither自動(dòng)化測(cè)試智能合約并進(jìn)行分類存儲(chǔ)

    Slither是一個(gè)用Python 3編寫的Solidity靜態(tài)分析框架。它運(yùn)行一套漏洞檢測(cè)器,打印有關(guān)智能合約細(xì)節(jié)的可視化信息,并提供一個(gè)API來(lái)輕松編寫自定義分析。Slither使開(kāi)發(fā)人員能夠發(fā)現(xiàn)漏洞,增強(qiáng)代碼理解能力,并快速原型定制分析。 并且Slither相比較其他工具而言,最好的一個(gè)地

    2024年02月12日
    瀏覽(18)
  • 微信小程序自動(dòng)化測(cè)試實(shí)戰(zhàn),支持錄制回放、智能遍歷

    ? ?為了滿足小程序性能、功能等方面的測(cè)試需求,微信團(tuán)隊(duì)上線 小程序云測(cè)服務(wù) ,提供豐富的自動(dòng)化測(cè)試能力。其中 智能化 Monkey 服務(wù) 憑借著零代碼、低成本的優(yōu)勢(shì)吸引不少開(kāi)發(fā)者使用。 在服務(wù)使用過(guò)程中,我們發(fā)現(xiàn)開(kāi)發(fā)者有更多的進(jìn)階需求: 先完成指定操作,例如登

    2024年02月03日
    瀏覽(27)
  • 一種基于閉包函數(shù)實(shí)現(xiàn)自動(dòng)化框架斷言組件的設(shè)計(jì)實(shí)踐

    目前測(cè)試組同學(xué)基本具備自動(dòng)化腳本編寫能力,為了提高效率,如何靈活運(yùn)用這些維護(hù)的腳本去替代部分手工的重復(fù)工作?為了達(dá)到測(cè)試過(guò)程中更多的去使用自動(dòng)化方式,如何能夠保證通過(guò)腳本覆蓋更多的校驗(yàn)點(diǎn),提高自動(dòng)化測(cè)試的精度和力度?那么一定是不斷的豐富斷言,

    2024年02月08日
    瀏覽(21)
  • 實(shí)現(xiàn)人工智能的自動(dòng)化測(cè)試:提高軟件質(zhì)量的關(guān)鍵步驟

    作者:禪與計(jì)算機(jī)程序設(shè)計(jì)藝術(shù) 1.1. 背景介紹 隨著人工智能技術(shù)的飛速發(fā)展,軟件測(cè)試領(lǐng)域也迎來(lái)了前所未有的挑戰(zhàn)。為了提高軟件質(zhì)量,減少測(cè)試工作量,自動(dòng)化測(cè)試應(yīng)運(yùn)而生。人工智能自動(dòng)化測(cè)試是指利用人工智能技術(shù)對(duì)軟件進(jìn)行自動(dòng)化測(cè)試,從而提高測(cè)試效率和測(cè)試質(zhì)

    2024年02月07日
    瀏覽(34)
  • AutoRunner自動(dòng)化測(cè)試工具新版本智能識(shí)別算法之視覺(jué)識(shí)別

    AutoRunner自動(dòng)化測(cè)試工具新版本智能識(shí)別算法之視覺(jué)識(shí)別

    澤眾AutoRunner(簡(jiǎn)稱AR)是國(guó)內(nèi)專業(yè)的支持C/S、B/S各種技術(shù)框架的、基于組件識(shí)別的自動(dòng)化測(cè)試工具,實(shí)現(xiàn)7*24小時(shí)的自動(dòng)化回歸測(cè)試和功能測(cè)試,讓測(cè)試更智能。 視覺(jué)識(shí)別是一種通過(guò)計(jì)算機(jī)技術(shù)對(duì)圖像或視頻進(jìn)行分析和理解的方法。這種算法能夠?qū)D像中的特征和模式與已知

    2024年02月11日
    瀏覽(30)
  • Rust編程語(yǔ)言入門之函數(shù)式語(yǔ)言特性:-迭代器和閉包

    閉包(closures) 迭代器(iterators) 優(yōu)化改善 12 章的實(shí)例項(xiàng)目 討論閉包和迭代器的運(yùn)行時(shí)性能 閉包:可以捕獲其所在環(huán)境的匿名函數(shù)。 閉包: 是匿名函數(shù) 保存為變量、作為參數(shù) 可在一個(gè)地方創(chuàng)建閉包,然后在另一個(gè)上下文中調(diào)用閉包來(lái)完成運(yùn)算 可從其定義的作用域捕獲值

    2023年04月08日
    瀏覽(27)
  • 【跟小嘉學(xué) Rust 編程】十三、函數(shù)式語(yǔ)言特性:迭代器和閉包

    【跟小嘉學(xué) Rust 編程】一、Rust 編程基礎(chǔ) 【跟小嘉學(xué) Rust 編程】二、Rust 包管理工具使用 【跟小嘉學(xué) Rust 編程】三、Rust 的基本程序概念 【跟小嘉學(xué) Rust 編程】四、理解 Rust 的所有權(quán)概念 【跟小嘉學(xué) Rust 編程】五、使用結(jié)構(gòu)體關(guān)聯(lián)結(jié)構(gòu)化數(shù)據(jù) 【跟小嘉學(xué) Rust 編程】六、枚舉

    2024年02月11日
    瀏覽(27)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包