在this question中,出现了一个问题,可以通过更改将泛型类型参数用于关联类型的尝试来解决。这引发了一个问题"为什么相关类型在这里更合适?",这让我想知道更多。
RFC that introduced associated types说:
此RFC通过以下方式澄清特征匹配:
- 将所有特征类型参数视为输入类型和
- 提供相关类型,输出类型。
RFC使用图形结构作为激励示例,这也在the documentation中使用,但我承认没有完全理解相关类型版本相对于类型参数化的好处版。主要的是distance
方法不需要关心Edge
类型。这很好,但似乎有点关联类型的原因。
我发现相关类型在实践中非常直观,但在决定在我自己的API中何时何地使用它们时,我发现自己很挣扎。
编写代码时,何时应该在泛型类型参数上选择关联类型,何时应该反过来呢?
答案 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
的给定类型trait
,hello_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
产生任何一个都有意义i32
或Complex<i32>
取决于具体情况?尽管如此,提交人仍然行使了他们的判断,并决定不必重载加法的返回类型。
我的个人立场是没有正确的答案。但是,除了unicity论证之外,我还会提到相关类型使得使用特征更容易,因为它们减少了必须指定的参数数量,因此如果使用常规特征参数的灵活性的好处不明显,我建议从相关类型开始。
答案 1 :(得分:24)
关联类型是分组机制,因此在将类型组合在一起时应该使用它们。
文档中介绍的Graph
特征就是一个例子。您希望Graph
是通用的,但是一旦您拥有特定类型的Graph
,您就不希望Node
或Edge
类型再次变化。特定的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)是有意义的,则使用关联类型。