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.
fn count<I: for<'a> Iterator<'a>>(it: I)
— Could you please provide an example on how it would fail for being restrictive (i.e secret'static
bound)? – Southwestwards