11

[First of all if someone has a better name for the question, proposal are well accepted. I didn't find a better name for this question so far.]

So, that's the question.

Assuming I have a protocol ProtocolA, a protocol ProtocolB and a protocol ProtocolX defined as following:

protocol ProtocolA {
  func doSomethingA()
}

protocol ProtocolB {
   func doSomethingB()
}

protocol ProtocolX {
   var handler: ProtocolA { get }
}

Then I have a proper implementation in a class of my ProtocolX as following:

class Donald: ProtocolX {
   ...
   var handler: ProtocolA { ... }
   ...
}

Then everything is ok and the protocol requirement is correctly fulfilled.

BUT

If I implement like that:

class Donald: ProtocolX {
   ...
   var handler: ProtocolA & ProtocolB
   ...
}

I have a compile-time issue reporting that my class Donald does not conform to ProtocolX (that, by specification, requires that the var handler must be conform to ProtocolA).

Theoretically the var handler IS conform to ProtocolA (but it's also conform to ProtocolB).

Then my question is: Why the compile-time issue? Swift limitation or conceptual issue on my side?

GrizzlyBear
  • 1,098
  • 1
  • 13
  • 27
  • 1
    Compare https://stackoverflow.com/q/42561685/2976878 – In short, there’s no real reason why it isn’t possible. The compiler just doesn’t handle it yet. – Hamish Oct 26 '17 at 13:14
  • Thanks. I'll follow this question as well. – GrizzlyBear Oct 26 '17 at 13:18
  • I dont see how it conforms... it is explicitly saying handler is something that conforms to A AND B – Michael Ramos Mar 29 '18 at 13:17
  • please read blog about protocols. https://github.com/apple/swift-evolution/blob/master/proposals/0095-any-as-existential.md – Sunil Prajapati Sep 01 '18 at 06:23
  • This has already been reported as a bug: [SR-629](https://bugs.swift.org/browse/SR-629?jql=text%20~%20%22protocol%22) but Apple said they wouldn't fix it. Protocol conformance has to be exact. I wonder what magic they pulled for `Codable = Encodable & Decodable` though – Code Different Sep 01 '18 at 14:45
  • @CodeDifferent Where have Apple said that they won't fix it? As I understand it, this is something the Swift team want (progress tracked by https://bugs.swift.org/browse/SR-522). – Hamish Sep 02 '18 at 11:25

3 Answers3

1

Theres no good reason why this shouldn't work, it just isn't supported by the compiler yet.

Currently, ProtocolX specifies that the handler must be of type ProtocolA, meaning that if you declare your class Donald with anything other than a ProtocolA type for the handler it will not fulfil the protocol's requirements.

You can however specify the handler as type ProtocolA but set it to a property of type ProtocolA & ProtocolB. In which case the property will be cast to ProtocolA and you will need to do an additional cast in order to use ProtocolB's attributes. For example:

typealias ProtocolAB = ProtocolA & ProtocolB

class AB: ProtocolAB {
    func doSomethingA() {}
    func doSomethingB() {}
}

class Donald: ProtocolX {
    var handler: ProtocolA {
        return AB()
    }

    func f() {
        handler.doSomethingA() // is valid
        handler.doSomethingB() // is not valid without casting the handler as `ProtocolB`
    }
}
user1636130
  • 1,615
  • 5
  • 29
  • 47
0

You could probably solve the issue by extending one of the protocols to implement the other one as well and that way you could workaround the issue.

You can make A inherits from B:

protocol ProtocolA: ProtocolB {
  func doSomethingA()
}

protocol ProtocolB {
   func doSomethingB()
}

protocol ProtocolX {
   var handler: ProtocolA { get }
}

Since A inherits from B, every property in B is available in A.

You could then use them both in protocol X as A inherits from b

Check this answer

AD Progress
  • 4,190
  • 1
  • 14
  • 33
0

Unfortunately the type specified in a protocol is, at least currently, required to be exact. There are some workarounds you can do at the present, however.

The simplest option is to just keep handler as ProtocolA but actually store a ProtocolA & ProtocolB object. This is doable as long as you don't need to conform to a protocol that also has var handler: ProtocolB.

class Donald: ProtocolX {
    var abHandler: ProtocolA & ProtocolB
    var handler: ProtocolA { return abHandler }
}

If you had a more complex situation where you actually need handler to be both ProtocolA and ProtocolB, you would have to use associated types in the protocols:

protocol ProtocolX {
    associatedtype Handler: ProtocolA
    var handler: Handler { get }
}
protocol ProtocolY {
    associatedtype Handler: ProtocolB
    var handler: Handler { get }
}

The problem with associated types is that you need a concrete type for them, so even though ProtocolA & ProtocolB is a valid type of variable, it cannot be used as the associated type Handler. So, you would either need to make a specific, concrete type to be the Handler:

struct PrintAB: ProtocolA, ProtocolB {
    func doSomethingA() { print("A") }
    func doSomethingB() { print("B") }
}
class Donald: ProtocolX, ProtocolY {
    var handler: PrintAB
}

…or you could make Donald itself generic:

class Donald<Handler>: ProtocolX, ProtocolY where Handler: ProtocolA & ProtocolB {
    init(handler: Handler) { self.handler = handler }
    var handler: Handler
}

While either of these would work, you would also lose the ability to use ProtocolX as the type of a variable, i.e. let someX: ProtocolX = donald would no longer work because of the associated type requirement in ProtocolX.

Of course if you can control the protocol definitions, you can avoid this situation by having different names for the handlers, reducing the problem to the simpler scenario:

class Donald: ProtocolX, ProtocolY {
    var abHandler: ProtocolA & ProtocolB
    var aHandler: ProtocolA { return abHandler } // renamed in ProtocolX
    var bHandler: ProtocolB { return abHandler } // renamed in ProtocolY
}
Arkku
  • 41,011
  • 10
  • 62
  • 84