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

Let's take a look at your last example again (shortened by me):

trait GenericAssociated {
    type GenericAssociated;
}

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

This does not feature generic associated types! You are just having a generic type on your impl block which you assign to the associated type. Mh, ok, I can see where the confusion comes from.

Your example errors with "the type parameter G is not constrained by the impl trait, self type, or predicates". This won't change when GATs are implemented, because, again, this has nothing to do with GATs.

Using GATs in your example could look like this:

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
}

But to answer you main question:

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

In short: they allow to delay the application of the concrete type (or lifetime) which makes the whole type system more powerful.

There are many motivational examples in the RFC, most notably the streaming iterator and the pointer family example. Let's quickly see why the streaming iterator cannot be implemented with generics on the trait.

The GAT version of the streaming iterator looks like this:

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

In current Rust, we could put the lifetime parameter on the trait instead of the associated type:

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

So far so good: all iterators can implement this trait as before. But what if we want to use it?

fn count<I: Iterator<'???>>(it: I) -> usize {
    let mut count = 0;
    while let Some(_) = it.next() {
        count += 1;
    }
    count
}

What lifetime are we supposed to annotate? Apart from annotating the 'static lifetime, we have two choices:

  • fn count<'a, I: Iterator<'a>>(it: I): this won't work because generic types of a function are choosen by the caller. But the it (which will become self in the next call) lives in our stack frame. This means that the lifetime of it is not known to the caller. Thus we get a compiler (Playground). This is not an option.
  • fn count<I: for<'a> Iterator<'a>>(it: I) (using HRTBs): this seems to work, but it has subtle problems. Now we require I to implement Iterator for any lifetime 'a. This is not a problem with many iterators, but some iterators return items that don't life forever and thus they cannot implement Iterator for any lifetime -- just lifetimes shorter than their item. Using these higher ranked trait bounds often leads to secret 'static bounds which are very restricting. So this also doesn't always work.

As you can see: we cannot properly write down the bound of I. And actually, we don't even want to mention the lifetime in the count function's signature! It shouldn't be necessary. And that's exactly what GATs allow us to do (among other things). With GATs we could write:

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

And it would work. Because the "application of a concrete lifetime" only happens when we call next.

If you are interested in even more information, you can take a look at my blog post “ Solving the Generalized Streaming Iterator Problem without GATs” where I try using generic types on traits to work around the lack of GATs. And (spoiler): it usually doesn't work.


What's the difference?

Generic Associate Types (GATs) are associated types which are themselves generic. The RFC starts with a motivating example, emphasis mine:

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.

Note how the associated type Item has a generic lifetime 'a. Most examples in the RFC use lifetimes, but there's also an example using a generic type:

trait PointerFamily {
    type Pointer<T>: Deref<Target = T>;
    fn new<T>(value: T) -> Self::Pointer<T>;
}

Note how the associated type Pointer has a generic type T.

Your specific example

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

There may be none, and the existence of GATs would not help your case, which does not seem to require an associated type which is itself generic.