Course Builder个人学习工作台 · 本地模式
Rust:从所有权到可测试的命令行工具

09|集合与迭代器:建立文本处理流水线

目标:使用借用迭代器筛选文本,并把需要保留的结果收集进 Vec。

前面用 .count() 统计一次遍历。现在考虑“保留长度至少为四个字节的词”:需要逐个产生元素、按规则过滤,最后保存结果。迭代器描述逐项处理,Vec<T> 保存一个可增长的连续序列;只有需要稍后复用结果时,才有必要收集成容器。

filter 接收一个返回布尔值的闭包。闭包像小型匿名函数,|word| word.len() >= 4 中竖线包住参数。map 把元素转换为另一种值。它们是惰性的:只构造迭代器不会完成整个计算;collect、count、sum 或 for 才会推进遍历。

保存文本视图,计算长度总和

创建 examples/iterators.rs:

fn main() {
    let text = String::from("rust go rust cargo");
    let words: Vec<&str> = text.split_whitespace().collect();
    let selected: Vec<&str> = words
        .iter()
        .copied()
        .filter(|word| word.len() >= 4)
        .collect();
    let total: usize = selected.iter().map(|word| word.len()).sum();
    println!("selected={}", selected.join(","));
    println!("selected_bytes={total} all_words={}", words.len());
}

Vec<&str> 保存引用,没有复制原文本的内容。words.iter() 借用容器并产生 &&str;.copied() 复制其中的引用,得到 &str,不复制字符串内容。filter 会借用每一项以决定保留与否,方法调用会自动处理这些引用层级。join 用分隔符把字符串片段拼成新的 String,便于稳定展示。

运行前先预测:过滤是否会从 words 里删除短词?

cargo run --quiet --example iterators

预期输出:

selected=rust,rust,cargo
selected_bytes=13 all_words=4

原容器仍然有 4 项,筛选结果有 3 项。iter() 只借用;如果对拥有的 Vec 调用 into_iter(),通常会消耗容器并移出元素。iter_mut() 则允许逐个可变借用已有元素。这三个入口应按“只读、接管、修改”的意图选择。

改变筛选规则

  1. 将阈值从 4 改为 5。
  2. 再运行,应保留 cargo,总字节数为 5,原容器仍有 4 项。
  3. 恢复阈值。不要通过删改输入来假装筛选规则生效。

独立练习:在收集到 selected 后去除重复词,按字典顺序输出 cargo,rust。可以把 selected 声明为可变,调用 sort_unstable() 排序,再调用 dedup() 删除相邻重复项。这两个方法在 Vec 上原地操作,不返回新 Vec。

完整参考实现
fn main() {
    let text = String::from("rust go rust cargo");
    let mut selected: Vec<&str> = text
        .split_whitespace()
        .filter(|word| word.len() >= 4)
        .collect();
    selected.sort_unstable();
    selected.dedup();
    assert_eq!(selected, vec!["cargo", "rust"]);
    println!("{}", selected.join(","));
}

完整替换实验文件,运行同一命令,预期 cargo,rust。vec! 创建向量,供断言比较。必须先排序再去掉相邻重复项;否则不相邻的两个 rust 可能不会一起被删除。这里所有重复字符串内容相等,不需要保留它们之间的原顺序,所以可使用不稳定排序。

如果 collect() 报无法推断类型,在绑定上写清 Vec<&str>;同一个迭代器可以收集成多种容器,编译器需要目标类型。如果提示文本活得不够久,检查容器是否保存了指向已经释放的 String 的切片。想让结果独立存活时才将元素变为 String。需要按键查找次数时可以进一步学习 HashMap,但本次统计不需要维护映射表。

本课新增 examples/iterators.rs。最终项目直接使用 count(),因为只要数量,不需要保存全部词。

参考资料