Rust散记【100-exercises-to-learnRust】
06_ticket_management
07_combinators
练习要求:
实现TicketStore的to_dos方法,使用迭代器组合子(combinators)从TicketStore中筛选出所有状态为ToDo的Ticket,返回它们的引用向量。
核心知识点:
关键在于使用迭代器组合子(combinators)——这是 Rust 中函数式编程风格的精髓:
// 要求:过滤status = Status::ToDo 的工单
pub fn to_dos(&self) -> Vec<&Ticket>{
// 管道函数
self.tickets //集合
.iter() //迭代器 (前面都是Combinators常规引用)
.filter(|tickets|tickets.status == Status::ToDo) //过滤条件
.collect() //返回Vec,作集合
}
逐步拆解:
self.tickets.iter()— 产生一个Iter<Ticket>,每次产出&Ticket(不可变引用).filter(|ticket| ticket.status == Status::ToDo)— 惰性过滤,只保留状态为ToDo的元素.collect()— 将过滤后的迭代器收集为Vec<&Ticket>
👀: 为什么返回
Vec<&Ticket>而不是Vec<Ticket>?
Ticket包含TicketTitle和TicketDescription(可能涉及堆分配),返回引用避免了不必要的克隆或所有权转移,性能更优
【练习的测试中:assert_eq!(todos[0], &todo),返回什么类型具体还是要看情况】
补充: combinators的哲学
filter 只是众多组合子之一。Rust 迭代器还有 map、filter_map、find、any、fold、take 等。它们的共同特点是惰性求值——只在最终 collect 或消费时才真正执行,中间步骤零开销抽象
08_impl_trait
练习要求:TODO: 实现
in_progress方法。它必须返回一个迭代器(Iterator),遍历 TicketStore 中状态为 Status::InProgress 的工单
前置知识:
Filter方法 :条件过滤,返回一个std::iter::Filter实例,其定义如下:
pub struct Filter<I, P> { /* fields omitted */ }
I 是被过滤的迭代器的类型: std::slice::Iter<'_, Ticket>
而P是用来过滤元素的谓语(即过滤条件),是一个闭包或者说一个匿名函数
👀设计返回类型为闭包的好处:
① 闭包可以为不同的类型,返回类型 == 过滤条件可多类型
【设计为T就固定只有一个过滤条件】
② 支持捕获环境变量;如果p不是泛型,假设过滤条件中一个变量发生改变,Filter就无法适配不同大小的闭包,导致运行错误
③ 用泛型P,编译器知道闭包的确切类型,可以直接内联闭包调用——零开销抽象。用fn指针,编译器只能间接调用,无法内联优化
对比07_combinators
- 07_combinators练习中返回的类型为
Vec<&Ticket>,
fn to_dos(&self) -> Vec<&Ticket>
- ❗局限性: 每次调用都强制堆分配一个
Vec——哪怕调用者只想遍历,不需要收集成容器 - 08_impl_trait 的改进:返回迭代器,让调用者决定是否 collect
07 to_dos |
08 in_progress |
|
|---|---|---|
| 返回类型 | Vec<&Ticket> |
impl Iterator<Item = &Ticket> |
| 堆分配 | ✅ 每次调用都分配 | ❌ 惰性,按需分配 |
| 调用者控制 | 被迫收成 Vec | 自由选择遍历/收集/组合 |
| 类型可命名? | ✅ | ❌(闭包不可命名,用 impl Trait 隐藏) |
08_impl_trait练习目的
当iteration(迭代器)的具体类型无法预知,可以使用
impl Trait方式表示(匿名函数写法)
- 07 的
filter链实际返回的是:
std::iter::Filter<std::slice::Iter<'_, Ticket>, 闭包类型>
Filter 的第二个泛型参数 P 是闭包的类型——而闭包是匿名类型,没有名字可写。
错误写法:
// ❌ 编译不过——闭包类型无法命名
fn in_progress(&self) -> std::iter::Filter<std::slice::Iter<'_, Ticket>, ???>
-- 这就是所谓的不可命名类型(unnameable type)问题
解法
impl TicketStore {
pub fn in_progress(&self) -> impl Iterator<Item = &Ticket> {
self.tickets
.iter()
.filter(|ticket| ticket.status == Status::InProgress)
}
}
impl Iterator<Item = &Ticket> 的含义:
| 部分 | 含义 |
|---|---|
impl |
"某种实现了以下 trait 的具体类型" |
Iterator<Item = &Ticket> |
该类型必须实现 Iterator,且 Item 是 &Ticket |
编译器知道具体类型是什么(Filter<Iter, 闭包>),但对外隐藏了细节——调用者只知道"这是个迭代器,产出 &Ticket" |
补充
impl Trait不是泛型
练习08_impl_trait.md 特别强调了这一点:
泛型 fn foo<T: Trait>() |
impl Trait 返回 fn foo() -> impl Trait |
|
|---|---|---|
| 谁决定具体类型? | 调用者 | 实现者 |
| 是否多态? | ✅ 是,不同类型生成不同代码 | ❌ 否,只有一种具体类型 |
| 编译产物 | 多份代码副本 | 单份代码 |
impl Trait 返回类型是编译期固定的,只是对调用者不透明。所以它也叫 RPIT(Return Position Impl Trait)
设计概念:不可命名类型 → impl Trait → 惰性迭代器 → 零成本抽象
完整答案
impl TicketStore {
pub fn in_progress(&self) -> impl Iterator<Item = &Ticket> {
self.tickets
.iter()
.filter(|ticket| ticket.status == Status::InProgress)
}
}

关于rust练习100-exercises-to-learnRust中:
06_ticket_management章节的 07_combinators 和 08_impl_trait的学习笔记
浙公网安备 33010602011771号