struct Connected { peer_ip: u32 }
struct Disconnected;
struct Connection<State> { state: State } struct Connected { peer_ip: u32 }
struct Disconnected;
struct Connection<State> { state: State }A better example is an HTTP writer. One state is `Headers` with the methods `add_header()` and `body()` which returns the response in state `Body`. This enforces that one can't add headers after `body()` has been called. But you can finish the response in the `Body` state. If you do something not in this order, you get a compile error.
None = disconnected
In some cases using None that way is totally fine, but it is a trade off and the cleaner way is to use a separate type
But defining a new type allows you to give the two cases more descriptive names. That saves you from having to remember and comment what the two cases represent. In a small but significant way, the code documents itself.
Also, doing it this way gives the Connection type its own name and identity separate from Option<int32>. So if you have a function that takes a Connection and some other Option<int32>, you can’t accidentally switch them: even though the two types are equivalent for optimizations and codegen, they’re NOT equivalent for the type checker.
It also makes function signatures more self-documenting: you can tell just from the type that the function takes or returns a Connection, and that helps in a small but real way to understand what it does.
I like programming like this a lot! So much so that the reason I personally like Rust a lot is simply that it’s a mainstream language with great tooling that has sum types and reasonable generics. It’s like if OCaml or Haskell had the community or tooling, or if Go or Python had the type features.