impl Trait

TicketStore::to_dos 返回一个 Vec<&Ticket>。这样的签名导致每次调用 to_dos 时都会进行一次堆分配,而实际上根据调用者的后续操作,这可能是不必要的开销。如果 to_dos 能返回一个迭代器而不是 Vec,就能让调用者决定是否将结果收集到 Vec 中或直接进行迭代,这样会更佳。

但这有点棘手!下面这样实现的 to_dos 返回类型是什么呢?

#![allow(unused)]
fn main() {
impl TicketStore {
    pub fn to_dos(&self) -> ??? {
        self.tickets.iter().filter(|t| t.status == Status::ToDo)
    }
}
}

无法命名的类型

filter 方法返回一个 std::iter::Filter 实例,其定义如下:

#![allow(unused)]
fn main() {
pub struct Filter<I, P> { /* 省略字段 */}
}

这里 I 是被过滤的迭代器的类型,P 是用于过滤元素的谓词。我们知道在这个例子中 Istd::slice::Iter<'_, Ticket>,但 P 呢?P 是一个闭包,一个匿名函数。正如其名所示,闭包没有名字,所以我们无法直接在代码中写出它的类型。

Rust 对此提供了一个解决方案:impl Trait

impl Trait

impl Trait 是一个特性,允许你在不指定类型名称的情况下返回类型。你只需声明类型实现了哪些特征(trait),剩下的交给 Rust 解决。

在这种情况下,我们想返回一个对 Ticket 的引用的迭代器:

#![allow(unused)]
fn main() {
impl TicketStore {
    pub fn to_dos(&self) -> impl Iterator<Item = &Ticket> {
        self.tickets.iter().filter(|t| t.status == Status::ToDo)
    }
}
}

这就对了!

泛型吗?

返回位置上的 impl Trait 不是 泛型参数。

泛型是函数调用者填充的类型占位符。具有泛型参数的函数是多态的:它可以被不同类型的调用,并且编译器会为每种类型生成不同的实现。

impl Trait 不是这样。带有 impl Trait 的函数的返回类型在编译时是固定的,编译器会为其生成单一的实现。这也是为什么 impl Trait 也被称作不透明返回类型:调用者不知道返回值的确切类型,只知道它实现了指定的特征(trait)。但编译器知道确切的类型,这里不涉及多态。

RPIT

如果你阅读 Rust 的 RFC 或深入探讨文章,可能会遇到 RPIT 这个缩写。它代表 "Return Position Impl Trait",指的是在返回位置使用 impl Trait 的情况。