• 关联类型
    • 定义关联类型
    • 实现关联类型
    • trait 对象和关联类型

    关联类型

    associated-types.md


    commit ccb1d87d6faa9ff528d22b96595a0e2cbb16c0f2

    关联类型是 Rust 类型系统中非常强大的一部分。它涉及到‘类型族’的概念,换句话说,就是把多种类型归于一类。这个描述可能比较抽象,所以让我们深入研究一个例子。如果你想编写一个Graph trait,你需要泛型化两个类型:点类型和边类型。所以你可能会像这样写一个 trait,Graph<N, E>

    1. trait Graph<N, E> {
    2. fn has_edge(&self, &N, &N) -> bool;
    3. fn edges(&self, &N) -> Vec<E>;
    4. // Etc.
    5. }

    虽然这可以工作,不过显得很尴尬,例如,任何需要一个Graph作为参数的函数都需要泛型化的Node和Edge类型:

    1. fn distance<N, E, G: Graph<N, E>>(graph: &G, start: &N, end: &N) -> u32 { ... }

    我们的距离计算并不需要Edge类型,所以函数签名中E只是写着玩的。

    我们需要的是对于每一种Graph类型,都使用一个特定的的Node和Edge类型。我们可以用关联类型来做到这一点:

    1. trait Graph {
    2. type N;
    3. type E;
    4. fn has_edge(&self, &Self::N, &Self::N) -> bool;
    5. fn edges(&self, &Self::N) -> Vec<Self::E>;
    6. // Etc.
    7. }

    现在,我们使用一个抽象的Graph了:

    1. fn distance<G: Graph>(graph: &G, start: &G::N, end: &G::N) -> uint { ... }

    这里不再需要处理Edge类型了。

    让我们更详细的回顾一下。

    定义关联类型

    让我们构建一个Graphtrait。这里是定义:

    1. trait Graph {
    2. type N;
    3. type E;
    4. fn has_edge(&self, &Self::N, &Self::N) -> bool;
    5. fn edges(&self, &Self::N) -> Vec<Self::E>;
    6. }

    十分简单。关联类型使用type关键字,并出现在trait体和函数中。

    这些type声明跟函数定义一样。例如,如果我们想N类型实现Display,这样我们就可以打印出点类型,我们可以这样写:

    1. use std::fmt;
    2. trait Graph {
    3. type N: fmt::Display;
    4. type E;
    5. fn has_edge(&self, &Self::N, &Self::N) -> bool;
    6. fn edges(&self, &Self::N) -> Vec<Self::E>;
    7. }

    实现关联类型

    就像任何 trait,使用关联类型的 trait 用impl关键字来提供实现。下面是一个Graph的简单实现:

    1. # trait Graph {
    2. # type N;
    3. # type E;
    4. # fn has_edge(&self, &Self::N, &Self::N) -> bool;
    5. # fn edges(&self, &Self::N) -> Vec<Self::E>;
    6. # }
    7. struct Node;
    8. struct Edge;
    9. struct MyGraph;
    10. impl Graph for MyGraph {
    11. type N = Node;
    12. type E = Edge;
    13. fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
    14. true
    15. }
    16. fn edges(&self, n: &Node) -> Vec<Edge> {
    17. Vec::new()
    18. }
    19. }

    这个可笑的实现总是返回true和一个空的Vec<Edge>,不过它提供了如何实现这类 trait 的思路。首先我们需要3个struct,一个代表图,一个代表点,还有一个代表边。如果使用别的类型更合理,也可以那样做,我们只是准备使用struct来代表这 3 个类型。

    接下来是impl行,它就像其它任何 trait 的实现。

    在这里,我们使用=来定义我们的关联类型。trait 使用的名字出现在=的左边,而我们impl的具体类型出现在右边。最后,我们在函数声明中使用具体类型。

    trait 对象和关联类型

    这里还有另外一个我们需要讨论的语法:trait对象。如果你试图从一个带有关联类型的 trait 创建一个 trait 对象,像这样:

    1. # trait Graph {
    2. # type N;
    3. # type E;
    4. # fn has_edge(&self, &Self::N, &Self::N) -> bool;
    5. # fn edges(&self, &Self::N) -> Vec<Self::E>;
    6. # }
    7. # struct Node;
    8. # struct Edge;
    9. # struct MyGraph;
    10. # impl Graph for MyGraph {
    11. # type N = Node;
    12. # type E = Edge;
    13. # fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
    14. # true
    15. # }
    16. # fn edges(&self, n: &Node) -> Vec<Edge> {
    17. # Vec::new()
    18. # }
    19. # }
    20. let graph = MyGraph;
    21. let obj = Box::new(graph) as Box<Graph>;

    你会得到两个错误:

    1. error: the value of the associated type `E` (from the trait `main::Graph`) must
    2. be specified [E0191]
    3. let obj = Box::new(graph) as Box<Graph>;
    4. ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    5. 24:44 error: the value of the associated type `N` (from the trait
    6. `main::Graph`) must be specified [E0191]
    7. let obj = Box::new(graph) as Box<Graph>;
    8. ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    我们不能这样创建一个trait对象,因为我们并不知道关联的类型。相反,我们可以这样写:

    1. # trait Graph {
    2. # type N;
    3. # type E;
    4. # fn has_edge(&self, &Self::N, &Self::N) -> bool;
    5. # fn edges(&self, &Self::N) -> Vec<Self::E>;
    6. # }
    7. # struct Node;
    8. # struct Edge;
    9. # struct MyGraph;
    10. # impl Graph for MyGraph {
    11. # type N = Node;
    12. # type E = Edge;
    13. # fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
    14. # true
    15. # }
    16. # fn edges(&self, n: &Node) -> Vec<Edge> {
    17. # Vec::new()
    18. # }
    19. # }
    20. let graph = MyGraph;
    21. let obj = Box::new(graph) as Box<Graph<N=Node, E=Edge>>;

    N=Node语法允许我们提供一个具体类型,Node,作为N类型参数。E=Edge也是一样。如果我们不提供这个限制,我们不能确定应该impl那个来匹配trait对象。