何时使用关联类型与泛型类型是否合适?

时间:2015-08-17 20:44:32

标签: types rust idiomatic

this question中,出现了一个问题,可以通过更改将泛型类型参数用于关联类型的尝试来解决。这引发了一个问题"为什么相关类型在这里更合适?",这让我想知道更多。

RFC that introduced associated types说:

  

此RFC通过以下方式澄清特征匹配:

     
      
  • 将所有特征类型参数视为输入类型
  •   
  • 提供相关类型,输出类型
  •   

RFC使用图形结构作为激励示例,这也在the documentation中使用,但我承认没有完全理解相关类型版本相对于类型参数化的好处版。主要的是distance方法不需要关心Edge类型。这很好,但似乎有点关联类型的原因。

我发现相关类型在实践中非常直观,但在决定在我自己的API中何时何地使用它们时,我发现自己很挣扎。

编写代码时,何时应该在泛型类型参数上选择关联类型,何时应该反过来呢?

3 个答案:

答案 0 :(得分:47)

现在在the second edition of The Rust Programming Language中提到了这一点。但是,让我们再深入了解一下。

让我们从一个更简单的例子开始。

  

什么时候使用特质方法?

有多种方法可以提供后期绑定

trait MyTrait {
    fn hello_word(&self) -> String;
}

或者:

struct MyTrait<T> {
    t: T,
    hello_world: fn(&T) -> String,
}

impl<T> MyTrait<T> {
    fn new(t: T, hello_world: fn(&T) -> String) -> MyTrait<T>;

    fn hello_world(&self) -> String {
        (self.hello_world)(self.t)
    }
}

无论任何实施/绩效策略,上述两段摘录都允许用户以动态方式指定hello_world的行为方式。

一个区别(语义上)是trait实现保证对于实现T的给定类型traithello_world将始终具有相同的行为,而struct实现允许在每个实例的基础上具有不同的行为。

使用方法是否合适取决于用例!

  

何时使用相关类型?

与上面的trait方法类似,关联类型是后期绑定的一种形式(尽管它在编译时发生),允许trait的用户为给定的实例指定哪个类型为替代。这不是唯一的方式(因此问题):

trait MyTrait {
    type Return;
    fn hello_world(&self) -> Self::Return;
}

或者:

trait MyTrait<Return> {
    fn hello_world(&Self) -> Return;
}

等同于上述方法的后期绑定:

  • 第一个强制执行,对于给定的Self,只有一个Return关联
  • 第二个,允许为多个MyTrait
  • Self实施Return

哪种形式更合适取决于是否有必要强制执行单一性。例如:

  • Deref使用关联类型,因为没有单一性,编译器会在推理期间发疯“
  • Add使用关联类型,因为它的作者认为给定两个参数会有逻辑返回类型

正如您所看到的,虽然Deref是一个明显的用例(技术约束),但Add的情况并不那么明确:也许i32 + i32产生任何一个都有意义i32Complex<i32>取决于具体情况?尽管如此,提交人仍然行使了他们的判断,并决定不必重载加法的返回类型。

我的个人立场是没有正确的答案。但是,除了unicity论证之外,我还会提到相关类型使得使用特征更容易,因为它们减少了必须指定的参数数量,因此如果使用常规特征参数的灵活性的好处不明显,我建议从相关类型开始。

答案 1 :(得分:24)

关联类型是分组机制,因此在将类型组合在一起时应该使用它们。

文档中介绍的Graph特征就是一个例子。您希望Graph是通用的,但是一旦您拥有特定类型的Graph,您就不希望NodeEdge类型再次变化。特定的Graph不希望在单个实现中改变这些类型,事实上,希望它们始终是相同的。它们被组合在一起,或者甚至可以说关联

答案 2 :(得分:0)

可以使用关联类型来告诉编译器“这两个实现之间的这两种类型是相同的”。这是一个可编译的双重调度示例,几乎类似于标准库将迭代器与求和类型相关联的方式:

trait MySum {
    type Item;
    fn sum<I>(iter: I)
    where
        I: MyIter<Item = Self::Item>;
}

trait MyIter {
    type Item;
    fn next(&self) {}
    fn sum<S>(self)
    where
        S: MySum<Item = Self::Item>;
}

struct MyU32;

impl MySum for MyU32 {
    type Item = MyU32;

    fn sum<I>(iter: I)
    where
        I: MyIter<Item = Self::Item>,
    {
        iter.next()
    }
}

struct MyVec;

impl MyIter for MyVec {
    type Item = MyU32;
    fn sum<S>(self)
    where
        S: MySum<Item = Self::Item>,
    {
        S::sum::<Self>(self)
    }
}

fn main() {}

此外,https://blog.thomasheartman.com/posts/on-generics-and-associated-types也对此有一些很好的信息:

简而言之,当您要键入A以便能够多次针对不同类型的参数实现特征时,请使用泛型,例如From trait。

如果某个类型仅实现一次特征(例如使用Iterator和Deref)是有意义的,则使用关联类型。

相关问题