「Generics 解密」2-2

1,306 阅读3分钟

「这是我参与11月更文挑战的第 6 天,活动详情查看:2021最后一次更文挑战


现在除了我们的代码更干净一些,而且可能更有性能外,其实没有什么变化。

我们减少了运行时的开销,这很好。用户仍然不能用错误的类型创建一个连接器,因为他们必须使用new,而new()是在一个有 trait boundimpl block 中。

如果我们后来发现我们只在类似的 impl block 中调用 Connector::new,我们就可以删除这个特定的 trait bound。注意,标准库使用 pub(in crate::iter) 来确保 Map::new 不会在其他地方被误用。现在让我们来谈谈 Point Vec

这并不是标准库中的做法。这个迭代器实际上拥有这个Vec。这不仅对使用Vec类型而不是其他集合施加了不必要的限制,而且它还拥有集合本身的所有权,而不仅仅是它的内容。这是一个初步的步骤,以确保我们理解我们在做什么。标准库在iter模块中对泛型特质的使用是高超的,但很容易模仿。在我们继续创建我们想要的确切水平的灵活性之前,这一步确保我们有正确的行为。

impl<Id, F> Iterator for Connector<Id, F>
where
    F: Fn(Point<Id>, Point<Id>) -> Line<Id>,
{
    type Item = Line<Id>;

    fn next(&mut self) -> Option<Self::Item> {
        let point_a = self.points.pop()?;
        let point_b = self.points.pop()?;
        let line = (self.op)(point_a, point_b);

        Some(line)
    }
}

Traits

trait 本身就是一个类型系统的抽象。将我们自己的trait与trait约束的泛型结合起来,是掌握的一个重要步骤

让我们从使用trait来实现Vec类型的一个方法开始。这看起来类似于在一个迭代器上调用map或filter。用一个方法来命名一个trait,这被认为是 "最佳做法",即以该方法命名。

所以我们的单方法trait将被命名为:Connect。这个trait将取代我们现在的 connect()

当我们写 impl<T, U> 时,我们是在声明哪些泛型在该 impl 块的范围内。我们必须将这些泛型作为类型参数传递到 trait 中。看一下trait,我们就知道为什么了:这些类型参数告诉编译器在trait的方法中期待什么类型。现在编译器知道我们的实现正确地使用了 connect()。

trait Connect<Id, F>
where
    F: Fn(Point<Id>, Point<Id>) -> Line<Id>,
{
    fn connect(self, op: F) -> Connector<Id, F>;
}

impl<Id, F> Connect<Id, F> for Vec<Point<Id>>
where
    F: Fn(Point<Id>, Point<Id>) -> Line<Id>,
{
    fn connect(self, op: F) -> Connector<Id, F> {
        Connector::new(self, op)
    }
}

fn main() {
    let points = vec![Point { id: 1001 }, Point { id: 1002 }];
    let mut connector = points.connect(|left, right| Line {
        points: (left, right),
    });

    print!(
        "This should be a line: {:?}.\nThis should be nothing: {:?}.",
        connector.next(),
        connector.next()
    )
}

现在我们将完成编写我们的连接器的迭代器。

我们将通过在另一个迭代器(不仅仅是一个Vec)上调用connect来创建它。让我们对标准库中的类似类型做一些观察,并将这些观察应用到我们的代码中。