「这是我参与11月更文挑战的第 6 天,活动详情查看:2021最后一次更文挑战」
现在除了我们的代码更干净一些,而且可能更有性能外,其实没有什么变化。
我们减少了运行时的开销,这很好。用户仍然不能用错误的类型创建一个连接器,因为他们必须使用new,而new()是在一个有 trait bound 的 impl 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来创建它。让我们对标准库中的类似类型做一些观察,并将这些观察应用到我们的代码中。