I've seen a lot of attempts on these teams to ensure these mistakes aren't made (special parsing libraries, using default fallback values, etc.), but someone always manages to sneak in an enum somewhere where they shouldn't and then something blows up 6 months later when a new value is introduced. Enthusiastic engineers will spend a lot of time trying to figure out how to add an over-strict enum, because hey, who doesn't want type safety?
Imagine you have two servers communicating with
enum Packet { Ping, Pong, }
Where the semantics are that if you receive a ping, you say pong, and if you see pong, you say ping.
Suppose we deploy this out to several servers, with code built on 2020-7-12. Now on 2020-07-13 you commit to head a new enum value `Stats`, which returns a new kind of reply.
If you send outgoing requests in the same 2020-07-13 build, you're going to have a bad time, as your fleet is going to start at 100% 2020-07-12 and switch over to 100% 2020-07-13, but during that switch you'll have some 50% 50% split. If a new server sends a request to an old one, you'll have a crash.
Instead you have to add receivers in 2020-07-13, but not use it yet. Roll it out, make sure it's past how far you might roll back, then you can deploy a 2020-07-20 build which makes calls using the new enum.
If you skip that step, you're going to have a bad time. Maybe you want to instead have a default case handler instead of crashing, but that means each of your enums has to know about "UNKNOWN" and you're deserialization error has to be future proof.
I wrote the Haskell implementation. It maps unions to variants (what Rust calls enums), BUT it always generates an extra 'unkown' variant, which gets used whenever the variant is one not recognized by the generated code.
In this case the value proposition of variants/enums, including exhaustiveness checking, is still really useful -- it can not only deal with the possibility of more variants added in the future, it forces you to handle that possibility, which imo is the best of both worlds.
But yes, in general, when modeling data that comes from the outside world, you need to about overfitting.
Vaguely related: https://lexi-lambda.github.io/blog/2020/01/19/no-dynamic-typ...
The way protobuf handles enums, it recommends having an "UNKNOWN" as value 0, as when deserializing from the wire any unknown enum value gets turned into value 0.
So it's recommended to have something like
enum FailReason { UNKNOWN_ERROR = 0, ERROR_1 = 1, ERROR_2 = 2, etc }
But someone long ago made the enum I was dealing with look like this
enum { SUCCESS = 0, ERROR_1 = 1, ERROR_2 = 2, etc. }
But this means I can't easily add a new error case, as it'll get interpreted as a success unless I carefully roll out the change to the receiving servers first.
But if I wasn't dealing with a distributed system I would like to have exhaustive checking.
I guess one option is to replace the endpoints that use the enum, but that seems heavy handed if it's wide spread.
Also, I'm not sure if you understand Rust enums. They should probably be called union types, because an enum in Rust signifies that the value can be one of a number of different types. This is extremely useful for code which has to handle a few different things differently. It's much more useful than the standard concept of an enum in languages such as Java and Rust, where it's just a dressed up integer flag.
Sorry to say but JavaScript is the only ecosystem I've seen where people blithely `JSON.parse` anything they get over the network and directly access the JSON structure expecting everything they want to be there without checking.
Enum types are supposed to be strict. Decoding raw data into enums can always fail but the failure should be handled at a higher level.
I deal a lot with this, because I interface with software outside my control. In the case of Rust I find super easy to solve, just use this litte trick:
https://serde.rs/attr-flatten.html
#[derive(Serialize, Deserialize)] struct User { id: String, username: String,
#[serde(flatten)]
extra: HashMap<String, Value>,
}
Serde is very good. Near all you want is there.