What's the difference/advantage/disadvantage between using one over the other? In the video, it was said that I'd prefer to use the associated type if I expect there to be only one implementation of the trait but I don't know what that means in this context.
With the first trait, each type can only implement Iterator at most once, but with the second trait, a type may implement both Iterator<i32> and Iterator<String> at the same time.
all this happens at compile-time, so it will have no impact whatsoever at runtime (c.f. "zero cost abstractions")
I'd expect the compile-time difference to be negligible, if any.
Where it matters is w.r.t.type inference. If we imagine that world where Iterator would have been generic over Item, then for c in "some_string" { println!("{}", c) } would lead to a type inference error because it wouldn't know whether item would be a u8 or a char.
A nice example to compare the two approaches is AsRef<T>vs.Deref<Target = T>. Indeed, try and see what happens when you do
use ::core::convert::AsRef;
println!("{}", String::from("…").as_ref());
vs.
use ::core::ops::Deref;
println!("{}", String::from("…").deref());
With an associated type we don't have that problem
Granted, sometimes Rust is smart enough to observe that there is only one generic parameter for which a generic trait is implemented for a type, and in that case it may be able to resolve the type inference issue on its own. But this is fragile, since anybody can bring their own impls to the table, breaking this "the only one by default" behavior / reasoning. It also makes the whole documentation and API less clear, for the cases where you truly intend to only allow one implementor. This has been the rationale behind Iterator, for instance.
Aside: for the case of &str, Rust has still managed to support both shapes in an unambiguous manner by not making &str be an iterator / iterable per se, but rather, by making it yield two different iterators through the special .bytes() and .chars() methods. So, as you can see, using associated types is not even that restrictive
Well, the difference is the same as the difference between function arguments and return values. Think of traits and generic types as "type-level functions". That's what they are.
In Trait<T>, the type parameter T is really a type variable that the user of the trait choses. You can say you want a type to implement Trait<bool> or Trait<String> or whatever. Many generic types impl Trait<T> for all choices of T, for example.
However, in the trait Iterator, the Item is an associated type. An associated type is something that the implementation of the trait choses, and the user of the trait has no influence on it. Therefore you can't request, for example, an Iterator<Item=bool> from "some string".chars(). The iterator returned by str::chars() simply doesn't and can't return an iterator over booleans; its implementation defines that it will yield char.
One interesting detail is that since a type system is all about constraints, you can still apply bounds and equalities to an associated type in a where clause, in which case the constraint sort of "propagates" backwards to the implementing type. E.g. the following function will only accept iterators that yield char:
fn consume_chars<I>(iter: I)
where
I: Iterator<Item=char>,
{
let _: Vec<char> = iter.collect();
}