特征的泛型类型和泛型关联类型有什么区别?

What's the difference between a trait's generic type and a generic associated type?

这个问题是在泛型关联类型在 Rust 中可用之前被问到的,尽管它们是 proposed and 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) {}
}

有什么区别?

通用关联类型 (GAT) 是 关联类型,它们本身是 通用。 RFC 以 motivating example 开头,强调我的:

Consider the following trait as a representative motivating example:

trait StreamingIterator {
    type Item<'a>;
    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}

This trait is very useful - it allows for a kind of Iterator which yields values which have a lifetime tied to the lifetime of the reference passed to next. A particular obvious use case for this trait would be an iterator over a vector which yields overlapping, mutable subslices with each iteration. Using the standard Iterator interface, such an implementation would be invalid, because each slice would be required to exist for as long as the iterator, rather than for as long as the borrow initiated by next.

This trait cannot be expressed in Rust as it exists today, because it depends on a sort of higher-kinded polymorphism. This RFC would extend Rust to include that specific form of higher-kinded polymorphism, which is refered to here as associated type constructors. This feature has a number of applications, but the primary application is along the same lines as the StreamingIterator trait: defining traits which yield types which have a lifetime tied to the local borrowing of the receiver type.

请注意关联类型 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

你的具体例子

what is the difference between Generic from the previous example and this generic associated type

可能有 none,并且 GAT 的存在对您的情况没有帮助,它似乎不需要本身是通用的关联类型。

让我们再看看你的最后一个例子(被我缩短了):

trait GenericAssociated {
    type GenericAssociated;
}

impl<G> GenericAssociated for Struct {
    type GenericAssociated = G;
}

具有泛型关联类型!您只是在 impl 块上拥有一个通用类型,您将其分配给关联类型。嗯,好吧,我明白了混乱的来源。

您的示例错误 "the type parameter G is not constrained by the impl trait, self type, or predicates"。这在实施 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
}

但是要回答你的主要问题:

What's the difference between a trait's generic type and a generic associated type?

简而言之:它们允许延迟具体类型(或生命周期)的应用,这使得整个类型系统更加强大。

the RFC 中有许多激励性示例,最著名的是流式迭代器和指针系列示例。让我们快速了解为什么流式迭代器不能在 trait 上使用泛型实现。

流式迭代器的 GAT 版本如下所示:

trait Iterator {
    type Item<'a>;
    fn next(&self) -> Option<Self::Item<'_>>;
}

在当前的 Rust 中,我们可以将生命周期参数放在特征上而不是关联类型上:

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)存在于 our 栈帧中。这意味着调用者不知道 it 的生命周期。因此我们得到了一个编译器(Playground)。这不是一个选项。
  • fn count<I: for<'a> Iterator<'a>>(it: I)(使用HRTBs):这似乎可行,但它有一些微妙的问题。现在我们要求 Iany 生命周期 'a 实施 Iterator。这对许多迭代器来说不是问题,但一些迭代器 return 项目不会永远存在,因此它们无法在 any 生命周期内实现 Iterator -- 只是寿命比他们的项目短。使用这些排名更高的特征边界通常会导致非常严格的秘密 'static 边界。所以这也不总是有效。

如您所见:我们无法正确写下 I 的界限。实际上,我们甚至不想在 count 函数的签名中提及生命周期!应该没有必要。而这正是 GAT 允许我们做的(以及其他事情)。使用 GAT,我们可以写:

fn count<I: Iterator>(it: I) { ... }

它会起作用的。因为 "application of a concrete lifetime" 只有在我们调用 next.

时才会发生

如果您对更多信息感兴趣,可以查看 my blog post “ Solving the Generalized Streaming Iterator Problem without GATs”,我在其中尝试对特征使用通用类型来解决缺少 GAT 的问题。并且(剧透):它通常不起作用。