「这是我参与11月更文挑战的第 5 天,活动详情查看:2021最后一次更文挑战」
part2 介绍
第一部分的代码给我们留下了一个很酷的库,但是它只触及了泛型的力量的表面。本节将介绍:implementations and traits 。
在第一部分的末尾,我们使用了Fn特性,但是我们在Box里面使用了dyn。dyn关键字意味着我们使用的是运行时的抽象,而且 Box表示堆分配。当我们深入挖掘类型系统中最强大的部分时,我们将取消 Box<dyn Fn<T>>
。
为什么我们需要更多的复杂度?
看看我们第一部分的代码,我们已经决定:
- Point 有它独立的Id
- Line 必须有正好两个 Point
- 而一个连接器可以在运行时做任何库用户想要的事情
只要它用两个点做,并产生一条线。
我们的主函数使用调试格式,将我们的 Line Instance 打印到控制台。但是,如果我们想保证每个连接器都能创建一个可以用调试格式显示的线(即"{:?}")呢?
我们怎样才能有足够的选择性,以确保这总是有效的?如果我们想让它工作而不强制使用Debug类型呢?这就是我们所说的:选择性的灵活性。
我们可以写出:Line<Id: Debug>
。这种 trait绑定 会将Id类型限制为实现了Debug特质的类型。但是我们有令人信服的理由使我们的类型定义尽可能的简单。最重要的是,除非所有的字段都是公共的,否则我们将无法从它的模块之外构造这个类型。
因此,如果不使用至少一个相关的函数,我们根本无法对它做任何事情,因此需要一个新的方法。这就是为什么 trait约束 几乎总是写在 impl块 中而不是类型定义中。
std::iter 中有许多类型被设计为持有一个闭包,但要想知道它是哪种闭包,我们必须看一下相关的植入(比如: Map)。当你通过阅读标准库的代码来了解如何做某事时,你就知道你做得对了。但是一定要循序渐进地工作,并始终理解你所写的东西。我们现在就来看看这个过程。
实现
一个类型的定义(struct/enum)描述了它是什么。它的实现(impl)描述了它能做什么。当我们对类型进行参数化并在植入式中对其进行限制时,我们就描述了当该植入式的条件得到满足时该类型可以做什么。
让我们从上面讨论的事情开始:当Id是可调试的时候,允许Line是可调试的。
use std::fmt::{self, Debug, Formatter};
// Remember to remove `#[derive(Debug)]` from `Line`.
impl<Id: Debug> Debug for Line<Id> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Line").field("points", &self.points).finish()
}
}
重要的是要知道,这个类型仍然足够灵活,可以容纳非 Debug 类型。
这个实现只是说:"只有当 Id 能够使用 Debug 时,Line 才能使用 Debug"。而结构Line<Id: Debug>
则表示,除非Id实现了Debug,否则Line不能存在。我们只是做了 #[derive(Debug)]
(也就是直接在 Line 上进行宏标注) 在幕后所做的事情。这不是魔术,这只是创造性的编码!
现在,我们应该明确我们的目标是什么:Rust将灵活性和明确性结合在一起的伟大之处在于,当工具被理解并且目标明确时,代码实际上是自己写的。
我们将把 Connector 变成一个迭代器,这样它就可以从Point实例的集合中创建Line实例。稍后,我们将使它有可能创建任何类型的形状,而不仅仅是线,使用任何数量的点,而不仅仅是两个。但我们将逐步实现这一目标,以便我们完全理解我们的代码。
在第二部分结束时,我们的库的用户将把一些点放入任何集合(不仅仅是一个Vec)并调用一个方法。他们会让它知道如何将这些点与一个闭包结合起来。这类似于标准库如何使用collect方法将迭代器变成集合,或者使用map方法改变迭代器中的类型或值。
我们首先要让我们的迭代器看起来更像标准库中的迭代器。这意味着用一个无界的通用类型替换Box<dyn Fn>
,并在 impl Block
中指定一个实现Fn特性的类型。像往常一样,标准库提供了正确的最好例子:
struct Connector<Id, F> {
points: Vec<Point<Id>>,
op: F,
}
impl<Id, F> Connector<Id, F>
where
F: Fn(Point<Id>, Point<Id>) -> Line<Id>,
{
fn new(points: Vec<Point<Id>>, op: F) -> Self {
Connector { points, op }
}
}