Associated types are slightly different than generic types.
trait State {
type NextState;
fn transition(self) -> Self::NextState;
}
In this example one consumes the current state to yield the next state (one could use From/TryFrom, but I don't think that makes sense semantically, imo).
The advantage of this approach is that if you design your State types such that they can only be constructed by transitioning from a previous state, you cannot write code where your program is accidentally in an invalid state. You also never need to match against an enum.
I've had good luck in the past using From<T> and TryFrom<T> to go from State<A> -> State<B>. It's similar to what you propose insofar as there are only some transitions defined, just relying on the built-in traits instead of using your own. Since State::from(prev) is so much more ergonomic than the other ways you could manually construct an illegal transition, it's not much of a problem to prevent that in practice. But I would never use this approach without being able to generate code with macros, otherwise it'd be a pain.
you mention "never have to match against an enum" - I think that's the biggest thing I learned from my experiences, is that generics or traits + associated types are much nicer to use than enums for the same purposes. With an enum, you have to match to pull any data out. It becomes annoying quickly.
You should keep going with this and see how ergonomic it is when NextState can be one of many states. I tried implementing State Machines once with this method and ended up wanting to use an `enum` for `State::NextState`. After a while it ended up being pretty unwieldy.
The advantage of this approach is that if you design your State types such that they can only be constructed by transitioning from a previous state, you cannot write code where your program is accidentally in an invalid state. You also never need to match against an enum.