99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Rust 改進(jìn)之前的 I/O 項目

2023-03-22 15:12 更新
ch13-03-improving-our-io-project.md
commit cc958ca579816ea6ac7e9067d628b0423a1ed3e4

有了這些關(guān)于迭代器的新知識,我們可以使用迭代器來改進(jìn)第十二章中 I/O 項目的實現(xiàn)來使得代碼更簡潔明了。讓我們看看迭代器如何能夠改進(jìn) Config::new 函數(shù)和 search 函數(shù)的實現(xiàn)。

使用迭代器并去掉 clone

在示例 12-6 中,我們增加了一些代碼獲取一個 String slice 并創(chuàng)建一個 Config 結(jié)構(gòu)體的實例,他們索引 slice 中的值并克隆這些值以便 Config 結(jié)構(gòu)體可以擁有這些值。在示例 13-24 中重現(xiàn)了第十二章結(jié)尾示例 12-23 中 Config::new 函數(shù)的實現(xiàn):

文件名: src/lib.rs

impl Config {
    pub fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }

        let query = args[1].clone();
        let filename = args[2].clone();

        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();

        Ok(Config {
            query,
            filename,
            case_sensitive,
        })
    }
}

示例 13-24:重現(xiàn)第十二章結(jié)尾的 Config::new 函數(shù)

那時我們說過不必?fù)?dān)心低效的 clone 調(diào)用了,因為將來可以對他們進(jìn)行改進(jìn)。好吧,就是現(xiàn)在!

起初這里需要 clone 的原因是參數(shù) args 中有一個 String 元素的 slice,而 new 函數(shù)并不擁有 args。為了能夠返回 Config 實例的所有權(quán),我們需要克隆 Config 中字段 query 和 filename 的值,這樣 Config 實例就能擁有這些值。

在學(xué)習(xí)了迭代器之后,我們可以將 new 函數(shù)改為獲取一個有所有權(quán)的迭代器作為參數(shù)而不是借用 slice。我們將使用迭代器功能之前檢查 slice 長度和索引特定位置的代碼。這會明確 Config::new 的工作因為迭代器會負(fù)責(zé)訪問這些值。

一旦 Config::new 獲取了迭代器的所有權(quán)并不再使用借用的索引操作,就可以將迭代器中的 String 值移動到 Config 中,而不是調(diào)用 clone 分配新的空間。

直接使用 env::args 返回的迭代器

打開 I/O 項目的 src/main.rs 文件,它看起來應(yīng)該像這樣:

文件名: src/main.rs

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = Config::new(&args).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {}", err);
        process::exit(1);
    });

    // --snip--
}

修改第十二章結(jié)尾示例 12-24 中的 main 函數(shù)的開頭為示例 13-25 中的代碼。在更新 Config::new 之前這些代碼還不能編譯:

文件名: src/main.rs

fn main() {
    let config = Config::new(env::args()).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {}", err);
        process::exit(1);
    });

    // --snip--
}

示例 13-25:將 env::args 的返回值傳遞給 Config::new

env::args 函數(shù)返回一個迭代器!不同于將迭代器的值收集到一個 vector 中接著傳遞一個 slice 給 Config::new,現(xiàn)在我們直接將 env::args 返回的迭代器的所有權(quán)傳遞給 Config::new

接下來需要更新 Config::new 的定義。在 I/O 項目的 src/lib.rs 中,將 Config::new 的簽名改為如示例 13-26 所示。這仍然不能編譯因為我們還需更新函數(shù)體:

文件名: src/lib.rs

impl Config {
    pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
        // --snip--

示例 13-26:以迭代器作為參數(shù)更新 Config::new 的簽名

env::args 函數(shù)的標(biāo)準(zhǔn)庫文檔顯示,它返回的迭代器的類型為 std::env::Args。我們已經(jīng)更新了 Config :: new 函數(shù)的簽名,因此參數(shù) args 的類型為 std::env::Args 而不是 &[String]。因為我們擁有 args 的所有權(quán),并且將通過對其進(jìn)行迭代來改變 args ,所以我們可以將 mut 關(guān)鍵字添加到 args 參數(shù)的規(guī)范中以使其可變。

使用 Iterator trait 代替索引

接下來,我們將修改 Config::new 的內(nèi)容。標(biāo)準(zhǔn)庫文檔還提到 std::env::Args 實現(xiàn)了 Iterator trait,因此我們知道可以對其調(diào)用 next 方法!示例 13-27 更新了示例 12-23 中的代碼,以使用 next 方法:

文件名: src/lib.rs

impl Config {
    pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
        args.next();

        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };

        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a file name"),
        };

        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();

        Ok(Config {
            query,
            filename,
            case_sensitive,
        })
    }
}

示例 13-27:修改 Config::new 的函數(shù)體來使用迭代器方法

請記住 env::args 返回值的第一個值是程序的名稱。我們希望忽略它并獲取下一個值,所以首先調(diào)用 next 并不對返回值做任何操作。之后對希望放入 Config 中字段 query 調(diào)用 next。如果 next 返回 Some,使用 match 來提取其值。如果它返回 None,則意味著沒有提供足夠的參數(shù)并通過 Err 值提早返回。對 filename 值進(jìn)行同樣的操作。

使用迭代器適配器來使代碼更簡明

I/O 項目中其他可以利用迭代器的地方是 search 函數(shù),示例 13-28 中重現(xiàn)了第十二章結(jié)尾示例 12-19 中此函數(shù)的定義:

文件名: src/lib.rs

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();

    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }

    results
}

示例 13-28:示例 12-19 中 search 函數(shù)的定義

可以通過使用迭代器適配器方法來編寫更簡明的代碼。這也避免了一個可變的中間 results vector 的使用。函數(shù)式編程風(fēng)格傾向于最小化可變狀態(tài)的數(shù)量來使代碼更簡潔。去掉可變狀態(tài)可能會使得將來進(jìn)行并行搜索的增強(qiáng)變得更容易,因為我們不必管理 results vector 的并發(fā)訪問。示例 13-29 展示了該變化:

文件名: src/lib.rs

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()
        .filter(|line| line.contains(query))
        .collect()
}

示例 13-29:在 search 函數(shù)實現(xiàn)中使用迭代器適配器

回憶 search 函數(shù)的目的是返回所有 contents 中包含 query 的行。類似于示例 13-19 中的 filter 例子,可以使用 filter 適配器只保留 line.contains(query) 返回 true 的那些行。接著使用 collect 將匹配行收集到另一個 vector 中。這樣就容易多了!嘗試對 search_case_insensitive 函數(shù)做出同樣的使用迭代器方法的修改吧。

接下來的邏輯問題就是在代碼中應(yīng)該選擇哪種風(fēng)格:是使用示例 13-28 中的原始實現(xiàn)還是使用示例 13-29 中使用迭代器的版本?大部分 Rust 程序員傾向于使用迭代器風(fēng)格。開始這有點難以理解,不過一旦你對不同迭代器的工作方式有了感覺之后,迭代器可能會更容易理解。相比擺弄不同的循環(huán)并創(chuàng)建新 vector,(迭代器)代碼則更關(guān)注循環(huán)的目的。這抽象掉那些老生常談的代碼,這樣就更容易看清代碼所特有的概念,比如迭代器中每個元素必須面對的過濾條件。

不過這兩種實現(xiàn)真的完全等同嗎?直覺上的假設(shè)是更底層的循環(huán)會更快一些。讓我們聊聊性能吧。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號