Rust 学习笔记:使用迭代器改进 minigrep
Rust 学习笔记:使用迭代器改进 minigrep
前情提要:https://blog.csdn.net/ProgramNovice/article/details/148192426
有了这些关于迭代器的新知识,我们可以通过使用迭代器使代码更清晰、更简洁。让我们看看迭代器如何改进 Config::build 函数和 search 函数的实现。
不使用 clone,而使用迭代器
原函数:
impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config { query, file_path, ignore_case })
}
}
有了关于迭代器的新知识,我们可以修改构建函数,使其接受迭代器的所有权作为参数,将 String 值从迭代器移到 Config 中,而不是调用 clone 并进行新的分配。
env::args 函数返回一个迭代器,类型是 std::env::Args,该类型实现了 Iterator trait 并返回 String 值。
修改代码,使得其所有权直接传递给 Config::build 函数。
fn main() {
let config = Config::build(env::args()).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});
// --skip--
}
接下来,我们需要更新 Config::build 函数:
impl Config {
pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
args.next();
let query = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a query string"),
};
let file_path = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a file path"),
};
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config { query, file_path, ignore_case })
}
}
我们更新了 Config::build 函数的签名,所以参数 args 有一个泛型类型,trait 约束为 impl Iterator<Item = String> 而不是 &[String],这意味着 args 可以是任何实现 Iterator trait 并返回 String 项的类型。因为我们获得了 args 的所有权我们将通过迭代来改变 args,我们可以在 args 参数的说明中添加 mut 关键字来使它可变。
使用迭代器适配器使代码更清晰
项目的 search 函数中利用了迭代器:
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
}
我们可以使用迭代器适配器方法以更简洁的方式编写此代码。这样做还可以避免使用可变的中间结果向量。
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines().filter(|line| line.contains(query)).collect()
}
函数式编程风格倾向于最小化可变状态的数量,以使代码更清晰。删除可变状态可能会使将来的增强实现并行搜索,因为我们不必管理对结果向量的并发访问。
类似的,我们修改 search_case_insensitive 函数:
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines()
.filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
.collect()
}
在循环或迭代器之间进行选择
大多数 Rust 程序员更喜欢使用迭代器风格。一开始很难掌握窍门,但是一旦了解了各种迭代器适配器及其功能,就会更容易理解迭代器。
代码没有处理循环的各个部分和构建新的向量,而是专注于循环的高级目标。这抽象掉了一些常见的代码,因此更容易看到这些代码特有的概念,例如迭代器中的每个元素必须通过的过滤条件。