Why do the Codable protocols list all methods instead of just one generic

For example, the protocol SingleValueDecodingContainer, has this method:

func decode<T: Decodable>(_ type: T.Type) throws -> T

It also has a bunch of others like:

func decode(_ type: Bool.Type) throws -> Bool
func decode(_ type: String.Type) throws -> String
func decode(_ type: Int.Type) throws -> Int
func decode(_ type: Int32.Type) throws -> Int32
... etc ...

Those are all covered by the generic method. What is the purpose of the protocol explicitly listing out a bunch of specializations (is that what they're called?) of the generic in this way?

Most decode<T: Decodable> just call init(from:), which in turn call other decode functions. To avoid infinite recursion, there needs to be a ground level somewhere.

Sometimes you could have that ground level inside the generic decode<T> function.

It's interesting that I don't actually have to implement those other methods. They are marked as "required" by the protocol, but if I implement only the generic one, my code works. For example, one of my implementations looks like this:

func decode<T>(_ type: T.Type) throws -> T where T : Decodable {       
    let key = codingPath.last!.stringValue
    return record[key] as! T
}

I don't see a reason to implement all the other ones, at least in this case.

Which is fair, and that implementation will satisfy all other requirements given that Int, Bool, etc., conforms to Decodable. So you can make do with only one decode implementation.

PS

Note that your implementation needs to correctly predict the types of data and put it in the record, which may not be possible in some format, eg. Int vs Int32 in JSON.

In all seriousness, any Coder can be implemented like this:

func decode<T>(...) ... {
  switch T.self {
  case Int.self: ...
  case String.self: ...
  default: ...
  }
}

Though that'd miss a lot of optimization opportunity if Decoder has only one function as a requirement.

These methods make up the core required list of types which must be supported by a Decoder that all Decodable types can rely on (and equivalently, Encoder/Encodable). If a Decoder and its containers cannot support one of these core types, it is unlikely to be a good fit for the Codable protocols.

We introduced the overloads as a reference both for Encoder/Decoder implementers to ensure they can handle all base cases, and a useful reference for clients to know what they can depend on. While Swift does technically allow you to to implement a single decode<T>(_:) to cover all protocol requirements, there's no guarantee that you will remember to switch on all possible types. In your specific case, it may be possible to avoid the switch altogether, but in the general case, it is not.

2 Likes