让我们再看看你的最后一个例子(被我缩短了):
trait GenericAssociated {
type GenericAssociated;
}
impl<G> GenericAssociated for Struct {
type GenericAssociated = G;
}
这不具有通用关联类型!您只是在分配给关联类型的 impl 块上有一个泛型类型。嗯,好的,我知道混乱来自哪里了。
您的示例错误“类型参数 G 不受 impl trait、self 类型或谓词的约束”。这在实施 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 中有很多激励性的示例,最值得注意的是流式迭代器和指针族示例。让我们快速了解为什么不能在 trait 上使用泛型实现流式迭代器。
流式迭代器的 GAT 版本如下所示:
trait Iterator {
type Item<'a>;
fn next(&self) -> Option<Self::Item<'_>>;
}
在当前的 Rust 中,我们可以将生命周期参数放在 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(在next 调用中将变为self)存在于我们的 堆栈框架中。这意味着调用者不知道it 的生命周期。因此我们得到了一个编译器(Playground)。这不是一个选项。
-
fn count<I: for<'a> Iterator<'a>>(it: I)(使用 HRTB):这似乎可行,但存在一些微妙的问题。现在我们需要I 来实现Iterator 的any 生命周期'a。这对许多迭代器来说不是问题,但一些迭代器返回的项目不会永远存在,因此它们无法在 any 生命周期内实现 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 不足的问题。而且(剧透):它通常不起作用。