Associated types are "outputs", they are controlled by the impl (`<Vec<String> as IntoIterator>::into_iter()` will always return a `std::vec::IntoIter<String>`, because that is how the trait is implemented for `Vec<T>`).
You can often use generic parameters for "outputs" as well (you could have a world where you had `impl<T> IntoIterator<IntoIter<T>> for Vec<T>`). However, this prevents the compiler from inferring the "output" type since there is no longer a guarantee that it is unique for a given set of "inputs".
A: type level functions from types to (associated) types (i.e. normal generics)
B: type level functions from types to type level functions (either As or Bs) (GAT).
with generic parameters (i.e. higher kinded types, or template template parameters in C++) you could have type level functions that map type functions to (types or type functions), but once you have GATs you can fake them by passing to first order functions a type that has an associated type.
In practice you could basically just have generic parameters and not bother with associated types, but it would be much less nice to use these APIs.
Here's an example of how an API would change if Rust didn't have associated types:
let x = [1,2,3]; // an array of i32
let mut y = x.iter(); // an iterator over it
y.next(); // pull an item from the iterator
If Rust didn't have associated types, then that last line would have to look like this: y.next::<&i32>(); // if Rust didn't have associated types