I think the key idea here is that Rust consistently uses static dispatch by default. When you invoke some method defined by a trait, it needs to look up
at compile-time which actual implementation it should dispatch to. Since the if-else expression isn't returning the same type, it doesn't matter if they both implement Iterator -- Rust still doesn't know which actual type will be produced, so it won't be able to tell which method implementations should actually be dispatched to.
Dynamic dispatch, which solves the issue you faced, needs to be explicitly opted into using the `dyn Trait` syntax, since it introduces a hidden indirection through a fat pointer.
This is definitely a difference from languages like Java or Python, where dynamic dispatch is the default (and sometimes there's no way to explicitly use static dispatch). On the other hand, languages like C and C++ also use static dispatch by default, with mechanisms like function pointers or `virtual` to provide dynamic dispatch as needed.
You would very likely have faced a similar problem in C++, had you used `auto x = (..) ? ... : ...`; (If you used `T x = ...; if (...) { x = ... } ...`, you'd have been faced immediately with the issue of "what type should T be" anyway, I think.)