G
N
I
D
A
O
L

AIGC标识 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,作集合

    }

逐步拆解:

  1. self.tickets.iter() — 产生一个 Iter<Ticket>,每次产出 &Ticket(不可变引用)
  2. .filter(|ticket| ticket.status == Status::ToDo) — 惰性过滤,只保留状态为 ToDo 的元素
  3. .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)
    }
}
posted @ 2026-08-29 19:48  Bingo39  阅读(9)  评论(0)    收藏  举报