使用迭代器优化minigrep项目

前言

学习了迭代器,我们现在来优化minigrep项目

1. 使用迭代器优化Config结构体new方法

回顾之前的一个代码片段,在Config的new函数中包含如下代码

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

        let query = args[1].clone();
        let filename = args[2].clone();
        // CASE_SENSITIVE 环境变量出现则代表区分大小写,而并不关心起具体值
        let case_sensitive = env::var("CASE_SENSITIVE").is_err();

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

因为query、filename对于new函数没有拥有其所有权,所以使用clone方法。传进来仅仅只字符串切片,而返回的Config需要拥有字符串的所有权。针对queryfilename两个字段,必须克隆出一份,这样Config才拥有它俩的所有权。

而迭代器有用数据的所有权,这样就不需要使用切片了,我们还可以使用迭代器自带的功能进行长度检查和索引。在main函数中,env::args()产生了个迭代器,我们可以直接将迭代器传给new函数。

改造如下:

main.rs

Rust 复制代码
// env::args()就是迭代器
let config = Config::new(env::args()).unwrap_or_else(|err| {
    eprintln!("Problem parsing arguments:{}", err);
    process::exit(1);
});

lib.rs

Rust 复制代码
impl Config {
    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enought arguments");
        }

        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 query string"),
        };

        // CASE_SENSITIVE 环境变量出现则代表区分大小写,而并不关心起具体值
        let case_sensitive = env::var("CASE_SENSITIVE").is_err();

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

这里的args的第一个参数不需要,手动调用。而next方法返回的是Options枚举中,使用match进行取值。

2. 使用迭代器优化搜索函数

我们还可以优化search方法,使用迭代器替换原来的逻辑,如下示例代码

Rust 复制代码
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

    contents
        .lines()
        .filter(|line| line.contains(query))
        .collect()
}

使用迭代器的好处是让开发者能专注于高层的业务逻辑,而不必陷入写循环、维护临时变量的细节工作里。

search_case_insensitive函数也可以优化成如下示例代码

Rust 复制代码
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let query = query.to_lowercase();
    // let mut results = Vec::new();

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

     contents
        .lines()
        .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
        .collect()
}

3. 循环vs迭代器

在上面的"2"中,如果我们把一本小说的内容放到一个String里面,搜索"the"。

rs 复制代码
test bench_search_for  ...bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ...bench: 19,234,900 ns/iter (+/- 657,200)

实际上测试的结果是,使用迭代器比手写循环还要快。

零开销抽象(Zero-Cost Abstraction)

不过重点不在这,而我们要知道的是:迭代器在rust里是一种高层次的抽象,但是它在编译后,生成的逻辑和我们手写底层代码几乎一样的。 这套机制在rust里叫做零开销抽象(Zero-Cost Abstraction),意味着

  • 使用抽象时不会引入额外的运行时开销。所以我们可以尽情使用类似迭代器这样高层次的抽象。

实际上,rust使用了很多优化,使得产出的代码非常地高效,我们可以无所畏惧地使用闭包或者迭代器,它们既能让代码在感观上保持高层次的抽象,又不会因此带来任何的运行时性能的损失。

相关推荐
Henrii_历小海19 分钟前
WhatsApp Web 不是手机镜像:多设备协议、本地数据流与出海获客流水线
前端·智能手机
ayqy贾杰27 分钟前
Claude Fable 5 提示词泄漏,抓紧学习下
前端·后端·面试
To_OC9 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic9 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy9 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志10 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble11 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯12 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜12 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司12 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能