56、impl Trait

impl Trait

 

上一节的Ticket::to_dos方法返回Vec<&ticket>

每次调用该函数签名的时候,该签名都会引入新的堆内存分配,这可能是不必要的,这取决于调用者需要对结果做什么。如果返回迭代器而不是Vecto_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是用于过滤元素的谓词(也就是包含判断语句的闭包)。

我们知道,在这种情况下,Istd::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>

这道题也就顺利通过了。

 

这一节的学习就先到这里了。

阅读剩余
THE END