「Generics 解密」2-3

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


step 1

Map类型有一个极其灵活的类型定义,一个没有边界的泛型。新方法也没有边界,但它只在std::iter中可用。

让我们把这个应用到我们的Connector上:

1
2
3
4
5
6
7
8
9
10
rust复制代码struct Connector<I, F> {
points_iter: I,
op: F,
}

impl<I, F> Connector<I, F> {
fn new(points_iter: I, op: F) -> Self {
Connector { points_iter, op }
}
}

step 2

一般来说,std::iter类型在其内部迭代器上调用next并很容易改变其行为。其中许多类型,如Take::next,都是用非常简单的代码完成的。

标准库还告诉我们,impl block 是我们应该施加 trait bounds 的地方,因为这是严格意义上的必要的地方。请注意,迭代器类型有一个相关的类型叫做Item。语法 <Item = Something> 将其与通用类型区分开来。

这取代了我们简单地从Vec中抓取项目的旧代码。这将适用于任何能够创建迭代器的类型,其中Item是Point:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rust复制代码impl<Id, I, F> Iterator for Connector<I, F>
where
I: Iterator<Item = Point<Id>>,
F: Fn(Point<Id>, Point<Id>) -> Line<Id>,
{
type Item = Line<Id>;

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

Some(line)
}
}

step 3

迭代器有产生其他迭代器的方法。如果一个例子还不够,可以在 Iterator trait 中还可以找到几十个其他的例子。

首先,我们需要改变Connect trait,因为它的方法会返回一个Connector,这一点已经被改变了。我们重写了Connector以使其更加灵活,所以让我们对Connect做同样的事情。我们将继续采用将 trait bounds 从定义中移出并移入实现的方式:

1
2
3
rust复制代码trait Connect<I, F> {
fn connect(self, op: F) -> Connector<I, F>;
}

我们不能给Iterator添加方法,它不是我们代码的一部分。但是我们可以使用一个非常有用的技巧:我们将为每个实现了Iterator的类型实现我们的Connect方法。

1
2
3
4
5
6
7
8
rust复制代码impl<I, Id, F> Connect<Id, F> for I
where
I: Iterator,
{
fn connect(self, op: F) -> Connector<I, F> {
Connector::new(self, op)
}
}

这被称为自动实现。标准库经常使用它们。一个众所周知的例子是,为一个类型实现Display会给它一个ToString的自动实现。

Fn trait bound 也被删除了,因为Connect和Connector不再需要它了。由于选择性的灵活性,我们优雅地使我们的代码更简单,更强大。让我们看看我们的新代码的运行情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
rust复制代码fn main() {
let points = {
let mut map = std::collections::HashMap::new();

map.insert("A", Point { id: 1001 });
map.insert("B", Point { id: 1002 });
map.insert("C", Point { id: 1003 });
map.insert("D", Point { id: 1004 });
map.insert("E", Point { id: 1005 });

map
};

let mut connector = points.into_values().connect(|left, right| Line {
points: (left, right),
});

println!("This should be a line: {:?}.", connector.next());
println!("This should be a line: {:?}.", connector.next());
println!("This should be nothing: {:?}.", connector.next());
}

我们的代码比我们为Vec手动实现Connect时做的要多得多,但我们使用的行数却完全相同。想象一下,为std::collection中的每个类型手动实现Connect所需要的代码量。由于泛型的复杂性,我们只用一个代码块就能得到同样的东西,并且让编译器来填补空白。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%