In this blog post where existential types finally clicked for me, the author solves his opening problem seemingly without using existential types, or at least, without the fn() -> impl SomeTrait syntax, instead opting for the associated types feature.
Are there some use cases where one would use existential types over associated types for traits?
The primary one I’ve run into is about setting expectations for authors that interact with my code. Each implementation of the trait needs to publically specify every associated type for that trait. This raises the possibility that some client of my API ends up relying on some feature of that associated type that I considered an implementation detail, which limits my ability to make changes.
If you're returning something with closures, it's impossible to name that directly in an associated type. If it has no captures, the closure can be cast to a plain function pointer, fn(Arg) -> Output, but otherwise you'd have to use dynamic dispatch, Box<dyn Fn(Arg) -> Output>. Existential types let you directly return a type even with such closures.
That's a little worse because the function pointer makes an indirect call, but it's manageable. With enough inlining, it might still optimize to a direct/inlined call.
It's also worse in the sense of exposing what should be an implementation detail i.e. the name of the type rather than just a declaration that there is a type returned that meets the contract specified by the trait (which is of course the obvious part of what existential types / ET accomplish).
The reason it's worse is because it allows for other implementation details to leak through (e.g. inherent methods on the type implementing Iterator), which is impossible with ETs.
A well known downside of ETs that's hopefully resolved at some point not too far from now is that the feature can't be used to e.g. name the type of a field or a local.