What is going on?

Hi, I have this code:

protocol Constructible {
    associatedtype RepresentativeData
    init? (_ data: RepresentativeData)
}
protocol ConstructibleFromJSON: Constructible {
    init? (_ data: JSON)
}
protocol ConstructibleFromProtobuffer: Constructible {
    init? (_ data: ProtoBuff)
}
func construct <I: Constructible,T>(instanceOf type: I.Type, from data: T)
-> I? where I.RepresentativeData == T

The problem comes from this:

class User: ConstructibleFromJSON, ConstructibleFromProtobuffer {
    init? (_ data: JSON) {...}
    init? (_ data: Protobuff) {...}
}

Compiler throws unclear error Type 'User' does not conform to protocol 'Constructible', which I assume caused by its inability to correctly reference correct method.
In c# this could be solved by parametrizing the protocol, like protocol ConstructibleFrom<T> but swift doesn't have this feature.
This raises two questions: how does Decodable work if decodable types can provide multiple versions of init(from decoder: Decoder) and everything seems to be fine, and how to solve it at all?

You're correct that this'd need generic protocol, which isn't supported in Swift. The Decodable initializer works because it uses the protocol Decoder instead of any specific type, so any type that conforms to Decoder can be supplied.

In your example, ConstructibleFromJSON can not initialize from ProtoBuff, where as Decodable types can initialize from either _JSONDecoder (not to be confused with JSONDecoder) or ProtobuffDecoder should there be one.

1 Like

This isn't quite it — the full issue is clarified a little by looking at the notes the compiler provides:

error: type 'User' does not conform to protocol 'Constructible'
class User: ConstructibleFromJSON, ConstructibleFromProtobuffer {
      ^
note: ambiguous inference of associated type 'RepresentativeData': 'JSON' vs. 'ProtoBuff'
    associatedtype RepresentativeData
                   ^
note: matching requirement 'init(_:)' to this declaration inferred associated type to 'JSON'
    required init? (_ data: JSON) { return nil }
             ^
note: matching requirement 'init(_:)' to this declaration inferred associated type to 'ProtoBuff'
    required init? (_ data: ProtoBuff) { return nil }
             ^

Your Constructible protocol has an associatedtype requirement which conforming types must satisfy; the compiler is trying to infer the type of RepresentativeData but can't because the conformances of ConstructibleFromJSON and ConstructibleFromProtobuffer have the same associatedtype requirement and it can't choose a type for you which would satisfy those constraints. To make this valid, you need to provide an explicit typealias RepresentativeData to express what you mean.

Decoder doesn't have this problem because it doesn't have associatedtype requirements.


Note also that the code you have likely doesn't quite represent what you mean. Your ConstructibleFromJSON and ConstructibleFromProtobuffer have initialization requirements that specify an init?(_:), but don't actually require that those initializers be acting on RepresentativeData. Although type inference works out so that if you have a type which conforms to either ConstructibleFromJSON or ConstructibleFromProtobuffer, providing an init?(_:) will likely meet the requirements of the protocol and allow for the right inference, the following is equally valid at the moment:

protocol ConstructibleFromProtobuffer: Constructible {
    init?(_ data: ProtoBuff) // does not require RepresentableData == ProtoBuff
    func foo()
}

extension ConstructibleFromProtobuffer {
    func foo() {
        print(Self.RepresentativeData.self)
    }
}

struct Foo: ConstructibleFromProtobuffer {
    typealias RepresentativeData = JSON
    init?(_ data: ProtoBuff) {} // meets the ConstructibleFromProtobuffer requirement
}

Foo(ProtoBuff())?.foo() // JSON

So for now, you can totally write

class User: ConstructibleFromJSON, ConstructibleFromProtobuffer {
    typealias RepresentativeData = JSON
    init?(_ data: JSON) {}
    init?(_ data: ProtoBuff) {}
}

and fulfill all protocol requirements since you have a RepresentativeData type and meet the init?(_:) requirements.

To constrain the protocols to likely mean what you want, you'd have to write

protocol ConstructibleFromJSON: Constructible where RepresentativeData == JSON {
    // ...
}

protocol ConstructibleFromProtobuffer: Constructible where RepresentativeData == ProtoBuff {
    // ...
}

Then you'd get

error: 'ConstructibleFromProtobuffer' requires the types 'User.RepresentativeData' (aka 'JSON') and 'ProtoBuff' be equivalent
struct User: ConstructibleFromJSON, ConstructibleFromProtobuffer {
       ^
note: requirement specified as 'Self.RepresentativeData' == 'ProtoBuff' [with Self = User]
struct User: ConstructibleFromJSON, ConstructibleFromProtobuffer {
       ^
4 Likes