Indeed, it's not that magic
There should be no difference here between letting the compiler generate the CodingKeys for you versus writing them yourself manually; when you let the compiler generate the CodingKeys for you, it inserts values into the AST to generate the equivalent of what you would have typed in manually. The following are equivalent:
struct S1 : Codable {
let var1: String
let var2: Int
let var3: Double
}
struct S2 : Codable {
let var1: String
let var2: Int
let var3: Double
private enum CodingKeys : String, CodingKey {
case var1, var2, var3
}
}
(In both of these implementations, the compiler goes on to use the CodingKeys enum [whether manually written or synthesized] to implement init(from:) and encode(to:).)
The only difference is that when you write them yourself, you control the access level, as you originally point out; that being said, this can indeed be an inconvenience. The motivation behind giving CodingKeys a private access level is that they are implementation detail that no one outside of the type should rely on (by default, at least; you're welcome to expose it yourself if you really want to for some reason).
However, private does indeed in this case hermetically seal the type. There was discussion about exactly this issue elsewhere that unfortunately, I can't seem to dig up anywhere (either on the forums or on JIRA), but if I find it I will link to it; since extensions can only come at file scope, there isn't a way to extend this type from the outside.
And while I agree that this case is pretty rare (generally speaking, many folks would either write a private type with all functionality built in [i.e. no `extension`s], or they'd write a fileprivate type in order to extend it), I don't think this should hold back a proposal to address the underlying issue. I think more types could stand to be private rather than fileprivate were this changed.
I think that it is. There's no reason in my mind to distinguish between private and fileprivate types here; in fact, there is more of a motivating case for private over fileprivate, since extensions of fileprivate types can come at the file scope:
struct Outer {
fileprivate struct Inner {}
}
fileprivate extension Outer.Inner {
func foo() {
print("Foo")
}
}
There are largely two ways to solve the issue at hand here (for CodingKeys types):
- Have the compiler synthesize the type at
fileprivate access level (and use the existing mechanisms to extend the type)
- Allow
extensions in inner scopes
(1) is especially problematic because of a use-case that many folks forget about: inheritance. If you write a Codable subclass of a Codable superclass, you should neither need nor want access to the superclass's CodingKeys, which you would get if they were fileprivate and you wrote both classes in the same file. Worse, it would be easier to accidentally inherit the superclass's CodingKeys by forgetting to write your own.
I think that it should be easy enough to find other cases of manually-written fileprivate nested types which have extensions at file scope which could be re-written as totally private types were extensions allowed in inner scopes.