Writing a trait alone as a type, like Iterator, is the old way of writing what is now dyn Iterator, a dynamically-dispatched trait object. This can be filled by any type that implements that trait, which is why it has indeterminate size.
In some cases, you might want to write impl Iterator instead, but that can't be returned from trait methods yet. That leaves you with the option of a boxed trait object, Box<dyn Iterator>.
I thought part of the magic of traits is that I can have this generate a unique function for every NodeId type and thus have it be zero cost abstraction.
Box<dyn Iterator> sounds like there is a heap allocation going on. Is this necessary?
This now allows rustc to generate a copy of the function per NodeId, per iterator type used to implement Iterator<NodeId> right? Does everything look right? Any pitfalls I should watch out for?
I think you'll find that using a generic parameter for ItT is difficult (if not impossible) for both the implementer and caller to use. It might work in your connected_component as written, but just because you've pushed the choice of ItT up a level. The problem is that at some point that type has to be chosen by the caller, which likely isn't what you want.
An associated type is meant for situations like this, where an implementation of the trait has exactly one appropriate type for the given trait (and all its parameters). The IntoIterator trait is a good example of this, or you might write your example like this: