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
是用于过滤元素的谓词。我们知道在这个例子中 I
是 std::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
的情况。