56、impl Trait
impl Trait
上一节的Ticket::to_dos
方法返回Vec<&ticket>
。
每次调用该函数签名的时候,该签名都会引入新的堆内存分配,这可能是不必要的,这取决于调用者需要对结果做什么。如果返回迭代器而不是Vec
,to_dos
会更好,从而使调用者能够决定是将结果收集到Vec
中,还是只是迭代它们。
不过这也挺难搞的,to_dos
的返回类型是什么?
impl TicketStore {
pub fn to_dos(&self) -> ??? {
self.tickets.iter().filter(|t| t.status == Status::ToDo)
}
}
不可命名的类型
filter
方法返回一个std::iter::Filter
的实例,该实例具有以下定义:
pub struct Filter<I, P> { /* 省略字段 */ }
其中,I
是被过滤的迭代器类型,P
是用于过滤元素的谓词(也就是包含判断语句的闭包)。
我们知道,在这种情况下,I
是std::slice::Iter<‘_,Ticket>
,但是P
呢?
P
是一个闭包,一个匿名函数。顾名思义,闭包没有名字,所以我们不能在代码中写出来。
Rust对此有一个解决方案:impl Trait
impl Trait
impl Trait
的功能是允许我们不指定类型名的情况下返回它的类型。我们只需要声明该类型实现了哪些trait
,Rust就会自动搞清楚其他的东西。
在这种情况下,如果我们想要返回对Tickets
的引用的迭代器的话:
impl TicketStore {
pub fn to_dos(&self) -> impl Iterator<Item = &Ticket> {
self.tickets.iter().filter(|t| t.status == Status::ToDo)
}
}
就这样了。
Generic(泛型)?
返回位置的 impl Trait
不是一个泛型参数。
泛型是类型的占位符,由函数的调用者填充。具有泛型参数的函数是polymorphic(多态的):它可以被不同的类型调用,编译器会为每种类型生成不同的实现。
impl Trait
的情况并非如此。具有impl Trait
的函数的返回类型在编译时是固定的,编译器为其生成单个实现。这就是为什么impl Trait
被称为opaque return type
(不透明返回类型):调用者不知道返回值的确切类型,只知道它实现了指定的trait。但是编译器知道确切的类型,因此不涉及多态性。
RPIT
如果我们阅读过 Rust 的 RFC 文档或深入的技术剖析文章,可能会遇到缩写词 RPIT。
它代表的是 “Return Position Impl Trait”(返回位置的 impl Trait
),指的是在函数返回类型位置使用 impl Trait
的语法。
练习
题目:
// TODO: Implement the `in_progress` method. It must return an iterator over the tickets in
// `TicketStore` with status set to `Status::InProgress`.
use ticket_fields::{TicketDescription, TicketTitle};
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status,
}
#[derive(Clone, Debug, Copy, PartialEq)]
pub enum Status {
ToDo,
InProgress,
Done,
}
impl TicketStore {
pub fn new() -> Self {
Self {
tickets: Vec::new(),
}
}
pub fn add_ticket(&mut self, ticket: Ticket) {
self.tickets.push(ticket);
}
pub /* TODO */
题解:
pub fn in_progress(&self) -> impl Iterator<Item = &Ticket> {
self.tickets.iter().filter(|t| t.status == Status::InProgress)
}
这道题其实也蛮简单的,主要是和前面的理论知识差别不大,我们就按照前面讲的返回迭代器的模式来写代码,跟上一节不同的是,我们不需要调用collect
了,而是直接返回迭代器。
为了返回迭代器,就是要实现上面说的RPIT,所以我们需要在返回类型那里填写impl Iterator<Item=&Ticket>
。
这道题也就顺利通过了。
这一节的学习就先到这里了。