SE-0295: Codable synthesis for enums with associated values (second review)

There are a few things to unpack in this thread, so I hope I manage to get them all.

Not a direct response to what you wrote, but for context: I think it's important to call out that what this pitch is proposing is a strategy for Codable synthesis for enums, so we're talking about compile-time behavior and not necessarily runtime behavior (which is separate, and not incompatible). In scope, we're looking at a way to influence how a type implements init(from:) and encode(to:), and are not able to influence how a property encodes — property wrappers (and strategies) are a nice solution, but out of scope here.

To discuss synthesis specifically — a while back I had a similar suggestion, along slightly different lines:

Given that enums span a wide range of use cases, I've always been in favor of offering customization for those use-cases. The (understandable) lack of consensus among how folks would prefer an enum encode was something that always prevented an easy implementation of this feature, and while I sympathize with the desire to get a default in place, I wish the pitch called this out a little more explicitly.

A static property would give us an "in" to inform the compiler of how you might expect the enum to encode and decode, in the same way that CodingKeys allow us to inform the compiler of expected synthesis for structural types.

So, my thoughts on this are: given thoughtful design of what customization options are possible, I'm obviously in favor. This design would effectively depart entirely from the pitch, which is, of course, entirely out of my hands.

Now, this design gives customization for a compile-time implementation of init(from:)/encode(to:), but that is entirely orthogonal to runtime overrides on an encoder/decoder or property level. Individual encoders and decoders could look for some info in .userInfo to override specific types (already possible today, done on a type-by-type basis with buy-in from the encoder/decoder), and property wrappers can be written to override enum properties. These are already possible today, though, and still require an existing Codable implementation, which the pitch intends to offer a default for.


A few unrelated thoughts on some things called out here:

  • Edge cases not covered by the pitch, such as case bar(number: Int, number: Int, number: String), case barbar(Void, (Void, Void)), and others: enums offer a surface area that other structural types do not (e.g. you can't have overloaded property names on a struct, but you can have repeated labels), and I think that regardless of future directions, we can come up with solutions to these, either by defining behavior, or preventing synthesis. Either way, it'd be nice to collect these edge cases in the proposal and explicitly call out what is to be done in each case

  • For "ambiguous" encoding of something like

    @jayton hits the nail on the head: if E and F output the same serialized representation, you should be able to decode either from that representation. This matches existing behavior for all other types, e.g., [1, 42] can be decoded as any of [Int]/[Int8]/.../[Float]/[Double], but also CGPoint and CGSize, which encode two numeric properties in an unkeyed container. This is an intentional part of Codable design: the format should not prescribe what is possible to decode — that's part of the code itself

  • For historic reference, Codable was released with Swift 4:

    I'm happy to discuss this elsewhere as I don't want to derail conversation here, but property wrappers solve an entirely different problem from strategies — the difficulty you note in solving one problem with the other alludes to that. Had property wrappers existed in that time frame I'm sure Codable would have made thorough use of them, but I feel fairly confident we would have introduced strategies along with them.

9 Likes