在Rust中提供通用关联类型之前会询问此问题,尽管它们是proposed和developed。
我的理解是,特质泛型和相关类型在它们可以绑定到结构的类型数量上有所不同。
泛型可以绑定任意数量的类型:
struct Struct;
trait Generic<G> {
fn generic(&self, generic: G);
}
impl<G> Generic<G> for Struct {
fn generic(&self, _: G) {}
}
fn main() {
Struct.generic(1);
Struct.generic("a");
}
关联类型只绑定1种类型:
struct Struct;
trait Associated {
type Associated;
fn associated(&self, associated: Self::Associated);
}
impl Associated for Struct {
type Associated = u32;
fn associated(&self, _: Self::Associated) {}
}
fn main() {
Struct.associated(1);
// Struct.associated("a"); // `expected u32, found reference`
}
通用关联类型是这两者的混合。它们绑定到一个类型正好相关的1个生成器,而这个生成器又可以关联任意数量的类型。那么前面例子中的Generic
和这个通用关联类型有什么区别?
struct Struct;
trait GenericAssociated {
type GenericAssociated;
fn associated(&self, associated: Self::GenericAssociated);
}
impl<G> GenericAssociated for Struct {
type GenericAssociated = G;
fn associated(&self, _: Self::GenericAssociated) {}
}
让我们再看看你的最后一个例子(由我缩短):
trait GenericAssociated {
type GenericAssociated;
}
impl<G> GenericAssociated for Struct {
type GenericAssociated = G;
}
这不具有通用关联类型!您只是在impl
块上有一个泛型类型,您将其分配给关联类型。嗯,好的,我可以看到混乱的来源。
“类型参数G
不受impl trait,self type或谓词约束”的示例错误。实施GAT时,这不会改变,因为这与GAT无关。
在您的示例中使用GAT可能如下所示:
trait Associated {
type Associated<T>; // <-- note the `<T>`! The type itself is
// generic over another type!
// Here we can use our GAT with different concrete types
fn user_choosen<X>(&self, v: X) -> Self::Associated<X>;
fn fixed(&self, b: bool) -> Self::Associated<bool>;
}
impl Associated for Struct {
// When assigning a type, we can use that generic parameter `T`. So in fact,
// we are only assigning a type constructor.
type Associated<T> = Option<T>;
fn user_choosen<X>(&self, v: X) -> Self::Associated<X> {
Some(x)
}
fn fixed(&self, b: bool) -> Self::Associated<bool> {
Some(b)
}
}
fn main() {
Struct.user_choosen(1); // results in `Option<i32>`
Struct.user_choosen("a"); // results in `Option<&str>`
Struct.fixed(true); // results in `Option<bool>`
Struct.fixed(1); // error
}
但是回答你的主要问题:
特质的泛型类型和通用的相关类型之间有什么区别?
简而言之:它们允许延迟具体类型(或寿命)的应用,这使得整个类型系统更加强大。
在the RFC中有许多动机示例,最值得注意的是流迭代器和指针族示例。让我们快速了解为什么无法使用特征上的泛型实现流式迭代器。
流式迭代器的GAT版本如下所示:
trait Iterator {
type Item<'a>;
fn next(&self) -> Option<Self::Item<'_>>;
}
在当前的Rust中,我们可以将life参数放在trait而不是关联的类型上:
trait Iterator<'a> {
type Item;
fn next(&'a self) -> Option<Self::Item>;
}
到目前为止一切顺利:所有迭代器都可以像以前一样实现这个特性。但是如果我们想要使用呢?
fn count<I: Iterator<'???>>(it: I) -> usize {
let mut count = 0;
while let Some(_) = it.next() {
count += 1;
}
count
}
我们应该注释什么生命周期?除了注释'static
的生命周期,我们有两个选择:
fn count<'a, I: Iterator<'a>>(it: I)
:这不起作用,因为调用者选择函数的泛型类型。但it
(将在self
电话中成为next
)生活在我们的堆栈框架中。这意味着调用者不知道it
的生命周期。因此我们得到了一个编译器(Playground)。这不是一个选择。fn count<I: for<'a> Iterator<'a>>(it: I)
(使用HRTBs):这似乎有效,但它有微妙的问题。现在我们要求I
为任何一生Iterator
实施'a
。这对于许多迭代器来说不是问题,但是一些迭代器会返回永远不会生命的项目,因此它们无法在任何生命周期内实现Iterator
- 只比生命周期短。使用这些排名较高的特质界限往往导致秘密的'static
边界,这是非常有限的。所以这也并不总是有效。正如你所看到的:我们无法正确记下I
的界限。实际上,我们甚至不想在count
函数的签名中提及它的生命周期!它没有必要。这正是GAT允许我们做的事情(除其他事项外)。通过GAT,我们可以写:
fn count<I: Iterator>(it: I) { ... }
它会起作用。因为“具体生命的应用”只发生在我们称之为next
时。
如果您对更多信息感兴趣,可以查看my blog post “ Solving the Generalized Streaming Iterator Problem without GATs”,我尝试在特征上使用泛型类型来解决缺乏GAT问题。而且(剧透):它通常不起作用。
通用关联类型(GAT)是相关类型,它们本身是通用的。 RFC以motivating example开头,强调我的:
考虑以下特征作为代表性激励示例:
trait StreamingIterator { type Item<'a>; fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>; }
这个特性非常有用 - 它允许一种迭代器产生的值,这些值的寿命与传递给
next
的引用的生命周期相关。该特征的一个特别明显的用例是在向量上的迭代器,其在每次迭代时产生重叠的,可变的子序列。使用标准的Iterator
接口,这样的实现将是无效的,因为每个切片都需要与迭代器一样长,而不是像next
启动的借用一样长。这种特性不能用Rust表达,因为它依赖于一种更高级的多态性。此RFC将扩展Rust以包括特定形式的高级多态性,这里将其称为关联类型构造函数。此功能有许多应用程序,但主要应用程序与
StreamingIterator
特征的行相同:定义特征,这些特征产生的类型的生命周期与接收器类型的本地借用相关联。
请注意关联类型Item
如何具有通用生命周期'a
。 RFC中的大多数示例都使用生命周期,但也有an example using a generic type:
trait PointerFamily { type Pointer<T>: Deref<Target = T>; fn new<T>(value: T) -> Self::Pointer<T>; }
请注意关联类型Pointer
如何具有泛型类型T
。
上一个示例中的
Generic
与此通用关联类型之间有什么区别
可能没有,GAT的存在对你的情况没有帮助,这似乎不需要一个本身就是通用的相关类型。