有了这些关于迭代器的新知识,我们可以在
第 12 章 通过使用迭代器使代码中的位置更清晰、更清晰
简明。让我们看看迭代器如何改进函数和函数的实现。 Config::build
search
在示例 12-6 中,我们添加了代码,它获取了 slice 的值并创建了
结构的实例,方法是对切片进行索引并克隆
值,允许结构体拥有这些值。在示例 13-17 中,
我们按原样复制了函数的实现
在示例 12-23 中: String
Config
Config
Config::build
文件名: src/lib.rs
use std::env;
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
}
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,
})
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
let results = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};
for line in results {
println!("{line}");
}
Ok(())
}
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_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
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}
示例 13-17:示例 12-23 中函数的重现 Config::build
当时,我们说不要担心低效的调用,因为
我们将来会删除它们。好吧,现在就是那个时候! clone
我们需要这里,因为我们有一个 slice,其中包含
parameter 的 ,但该函数不拥有 .返回
的所有权,我们必须从 的 和 字段中克隆值,以便实例可以拥有其值。 clone
String
args
build
args
Config
query
file_path
Config
Config
凭借我们对迭代器的新知识,我们可以将函数更改为
将迭代器的所有权作为其参数,而不是借用 slice。
我们将使用迭代器功能,而不是检查长度
的切片并索引到特定位置。这将阐明函数正在做什么,因为迭代器将访问这些值。 build
Config::build
Once 获得迭代器的所有权并停止使用索引
作,我们可以将 Iterator 中的值移动到其中,而不是调用并进行新的分配。 Config::build
String
Config
clone
打开 I/O 项目的 src/main.rs 文件,该文件应如下所示:
文件名: src/main.rs
use std::env;
use std::process;
use minigrep::Config;
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::build(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});
// --snip--
if let Err(e) = minigrep::run(config) {
eprintln!("Application error: {e}");
process::exit(1);
}
}
我们首先更改 清单 中函数的开头
12-24 添加到示例 13-18 中的代码中,这次使用了迭代器。这
在我们更新之前也不会编译。 main
Config::build
文件名: src/main.rs
use std::env;
use std::process;
use minigrep::Config;
fn main() {
let config = Config::build(env::args()).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});
// --snip--
if let Err(e) = minigrep::run(config) {
eprintln!("Application error: {e}");
process::exit(1);
}
}
示例 13-18:将 的返回值传递给 env::args
Config::build
该函数返回一个迭代器!而不是收集
iterator 值转换为 vector 中,然后将 slice 传递给 ,现在
我们将返回的迭代器的所有权直接传递给 to。 env::args
Config::build
env::args
Config::build
接下来,我们需要更新 的定义。在 I/O 中
项目的 src/lib.rs 文件,让我们将 的签名改为
如示例 13-19 所示。这仍然不会编译,因为我们需要更新
函数体。 Config::build
Config::build
文件名: src/lib.rs
use std::env;
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
}
impl Config {
pub fn build(
mut args: impl Iterator<Item = String>,
) -> Result<Config, &'static str> {
// --snip--
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,
})
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
let results = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};
for line in results {
println!("{line}");
}
Ok(())
}
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_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
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}
示例 13-19:更新 的签名以期待一个迭代器 Config::build
该函数的标准库文档显示,
type 是 ,并且该类型实现
trait 并返回 values。 env::args
std::env::Args
Iterator
String
我们更新了函数的签名,因此参数具有具有 trait bounds 的泛型类型,而不是 .我们在
第 10 章的“Traits as Parameters” 部分
表示可以是实现 trait 的任何类型,而
返回项目。 Config::build
args
impl Iterator<Item = String>
&[String]
impl Trait
args
Iterator
String
因为我们正在获得所有权,并且我们将通过
迭代它,我们可以将关键字添加到参数的规范中,使其可变。 args
args
mut
args
接下来,我们将修复 .因为实现了 trait,所以我们知道我们可以在它上面调用方法!示例 13-20
更新示例 12-23 中的代码以使用该方法: Config::build
args
Iterator
next
next
文件名: src/lib.rs
use std::env;
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
}
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,
})
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
let results = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};
for line in results {
println!("{line}");
}
Ok(())
}
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_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
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}
示例 13-20:更改 的主体以使用 iterator 方法 Config::build
请记住,返回值中的第一个值是
程序。我们想忽略它并获取下一个值,所以首先我们调用并且不对返回值执行任何作。其次,我们调用以获取
值,我们要放在 字段中。如果返回 a ,我们使用 a 提取值。如果返回 ,则表示
没有给出足够的参数,我们提前返回一个值。我们这样做
value 也是如此。 env::args
next
next
query
Config
next
Some
match
None
Err
file_path
我们还可以在 I/O 的函数中利用迭代器
project,在示例 13-21 中复制了它,就像示例 12-19 中一样: search
文件名: src/lib.rs
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String,
pub file_path: String,
}
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();
Ok(Config { query, file_path })
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
Ok(())
}
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
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
}
示例 13-21:示例 12-19 中函数的实现 search
我们可以使用 iterator 适配器方法以更简洁的方式编写此代码。
这样做还可以避免使用可变的中间向量。这
函数式编程风格倾向于将可变状态的数量最小化为
使代码更清晰。删除可变状态可能会启用将来的增强功能
使搜索并行进行,因为我们不必管理
对 vector 的并发访问。示例 13-22 显示了这个变化: results
results
文件名: src/lib.rs
use std::env;
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
}
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,
})
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
let results = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};
for line in results {
println!("{line}");
}
Ok(())
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}
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
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}
示例 13-22:在函数的实现中使用迭代器适配器方法 search
回想一下,该函数的目的是返回 中包含 .类似于 清单 中的示例
13-16 中,此代码使用 adapter 仅保留返回 for 的行。然后我们收集匹配的行
转换为另一个向量。简单多了!随意做同样的事情
更改为在函数中使用迭代器方法,如
井。 search
contents
query
filter
filter
line.contains(query)
true
collect
search_case_insensitive
下一个逻辑问题是您应该在自己的代码中选择哪种样式,并且
原因:示例 13-21 中的原始实现或使用
示例 13-22 中的 iterators。大多数 Rust 程序员更喜欢使用 iterator
风格。一开始要掌握窍门有点困难,但一旦你有了感觉
对于各种迭代器适配器及其功能,迭代器可能更容易
理解。而不是摆弄各种循环和构建
new vectors,则代码侧重于循环的高级目标。这
抽象出一些常见的代码,以便更容易看到概念
对于此代码是唯一的,例如每个元素中的
iterator 必须通过。
但是这两种实现真的等价吗?直观的假设
可能是 MORE LOW Level Loop 会更快。我们来谈谈
性能。