I'm not sure you can only see this in the light of 'more features'. From a more fundamental perspective GATs just lift a restriction.
Rust already has generics and Rust already has associated types. Previously you needed to know/remember those features cannot interact for some reason, now they can. To me this is in a sense 'less' and not 'more'.
Came here to say this. Adding new syntaxes is something I have mixed feelings about at this stage, but opening up what you can do with existing ones (and, indeed, removing the complexity of remembering special-cases of what's not allowed) is a strict win in my book.
Even in Java, I'm rather sick of passing around types with `<X, Y, ?, ?>` with two honest-to-goodness generic types and two actually-chosen-by-the-implementation generic types. There's no good way in Java to hide those visible wildcards without making a wrapper class that only exposes the meaningful ones.
Associated types make the distinction far clearer. They better capture the qualitative distinction that "you can choose these, I get to choose those".
Also, associated types are inherently "functionally dependent" in the sense of Haskell's multi-parameter type classes. If you have a trait `F<X, Y>` with an associated type Z, you know that given X and Y, Z is fixed. Without associated types, `F<X, Y, Z>` could have multiple legal implementations for the same X, Y and different Z.
This is extremely meaningful in languages like Haskell and Rust which implicitly thread around the trait methods. Typeclasses in Haskell can be imagined as describing concrete dictionaries of functions over the given types. Languages like Java (manually) and Scala (automatically, using "implicit") reify these typeclasses as dictionaries that are threaded through functions as extra parameters. You can often define multiple implementations of a given trait for the same types, and you get to choose which implementation to pass along.
Rust and Haskell assert that traits have a single implementation for a given batch of types, and they automatically look up the correct implementation given the types you've specified. These "functional dependencies" mean that, in my example of `F<X, Y>` with associated type Z, it's sufficient to state what X and Y are to find the right implementation of F -- Z doesn't contribute to the lookup. If you don't have associated types, you could have multiple implementations that simply vary on Z, so you have to tell the compiler explicitly which one to use (by stating what Z is).
Because they don't. Your link even has an answer that highlights one way which they differ in expressivity - namely, that associated types don't appear in the instance head, so they can produce orphan instances where generics would not.
Consider the example of Rust's Iterator trait[0]. It has an associated type for the iteration item. It could have a generic type argument instead, but the two implementations would be different:
1. The associated type is a direct consequence of the instance head. That means that, for the type that Iterator is being implemented on, the associated type is known entirely from that type. If you have an iterable value, you know it only produces one kind of iterator item.
2. As a result, the associated-type version can only be implemented at most once. The generic version would be implementable any number of times, for any number of choices of type argument - and as a result, you would have to specify that you are iterating over an iterator with a particular choice of iterator item type, every time you iterate.
[0]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
You can still do similarly hellish impls with `Index` - especially if you mix `Deref` into it. IIRC, the compiler will perform automatic recursive derefs when you try to use the indexing operator `[]`, until it finds a deref target which matches the index you're using. I doubt anything relies on that search behaviour outside of the compiler code itself.
I imagine abuse of specialisation could also increase the confusion, if you were so inclined.
> "The difference is that when using generics, as in Listing 19-13, we must annotate the types in each implementation; because we can also implement Iterator<String> for Counter or any other type, we could have multiple implementations of Iterator for Counter. In other words, when a trait has a generic parameter, it can be implemented for a type multiple times, changing the concrete types of the generic type parameters each time. When we use the next method on Counter, we would have to provide type annotations to indicate which implementation of Iterator we want to use."
Type parameters are decided by the use of the type. The associated types are decided by the implementation of the type. Type params are input, associate type are output.
Rust already has generics and Rust already has associated types. Previously you needed to know/remember those features cannot interact for some reason, now they can. To me this is in a sense 'less' and not 'more'.
So, a matter of perspective I guess.