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
andF
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 alsoCGPoint
andCGSize
, which encode two numeric properties in an unkeyed container. This is an intentional part ofCodable
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.