Swift multi-protocol conformance, compilation error

Viewed 108

I have the next protocols:

protocol ProtoAInput {
    func funcA()
}

protocol ProtoA {
    var input: ProtoAInput { get }
}

protocol ProtoBInput {
    func funcB()
}

protocol ProtoB {
    var input: ProtoBInput { get }
}

I want to have my StructC to conform to both protocols ProtoA and ProtoB, with just a single input property. Which itself is just a reference to self, as StructC also implements ProtoAInput and ProtoBInput in a separate extension.

struct StructC: ProtoA, ProtoB {
    var input: ProtoAInput & ProtoBInput { return self }
}

extension StructC: ProtoAInput {
    func funcA() { print("funcA") }
}

extension StructC: ProtoBInput {
    func funcB() { print("funcB") }
}

let s = StructC()
s.funcA()
s.funcB()

Swift 5.3 compiler fails to build this code with the following errors:

Type 'StructC' does not conform to protocol 'ProtoA'

Type 'StructC' does not conform to protocol 'ProtoB'

Are there any compilation rules that this code violates? I don't understand why I can't have input variable that simultaneously conforms to both protocols here.

2 Answers
struct StructC: ProtoA, ProtoB {
    var input: ProtoAInput & ProtoBInput { return self }
}

Instead of above you need something like the following

extension ProtoA where Self == StructC {
    var input: ProtoAInput { self }
}

extension ProtoB where Self == StructC {
    var input: ProtoBInput { self }
}

struct StructC: ProtoA, ProtoB {
}

Tested & worked with Xcode 12.1 / iOS 14.1

demo

This is SR-522 (Protocol funcs cannot have covariant returns). Swift just doesn't support this. If a protocol requires a type, then that's the type that's required. The same is true for subclasses:

class ProtoAInput {}
class ProtoAInputSub: ProtoAInput {}

protocol ProtoA {
    var input: ProtoAInput { get }  // <=== requires superclass
}

struct StructA: ProtoA {
    var input: ProtoAInputSub // <=== So can't use a subclass here
}

In theory, the compiler could support this as long as the required property is only get. If you added set, this can't work. But it doesn't work today for either.

My suspicion is that supporting this would be complicated due to the layouts of the existential containers. You can't just put a P & Q in the same places that you put a Q (in terms of Swift implementation, not in terms of type theory). The offsets into the witness table would be wrong. It feels like something that could be implemented, but I see why it would be complicated without sacrificing performance through additional indirection.

There are many things that the Swift type system could support that it just doesn't today. In many cases it's not because it's impossible or the Swift team has rejected it. They're just not supported by the compiler.

Related