2
votes

I'm learning Swift coming from other languages with powerful type systems and I am wondering if their is any way to make a protocol conditionally conform to another protocol.

Let's take an example: I define a ShowableAsInt protocol, that allows to get an Int representation for any type that conforms to it.

protocol ShowableAsInt {
        func intRepr() -> Int
}

extension Int : ShowableAsInt {
    func intRepr() -> Int {
        return self
    }
}

extension String : ShowableAsInt {
    func intRepr() -> Int {
        return self.count
    }
}

func show(_ s: ShowableAsInt) {
    print(s.intRepr())
}

show("abc") // "3"
show(42) // "42"

Now I define a Container protocol that simply wraps an element.

protocol Container {
    associatedtype T
    var element: T {
        get
    }
}

struct StringContainer : Container {
    typealias T = String
    let element: String
}

struct IntContainer : Container {
    typealias T = Int
    let element: Int
}

Because Container is a simple wrapper, whenever the wrapped type can be shown as an Int, the container can also be shown as an Int. I tried to express this, but failed:

// Doesn't compile: can't extend a protocol to conform to another one?
extension Container : ShowableAsInt where T : ShowableAsInt {
    func intRepr() -> Int {
        return element.intRepr()
    }
}

// I would like these to compile
show(IntContainer(42)) // "42"
show(StringContainer("abc")) // "3"

I know that this conditional conformance can be expressed on class and struct. Is there any way to do the same for protocols? If not, is there any reason for this restriction?

1

1 Answers

4
votes

The reason why this is not allowed is stated here:

This protocol extension would make any Collection of Equatable elements Equatable, which is a powerful feature that could be put to good use. Introducing conditional conformances for protocol extensions would exacerbate the problem of overlapping conformances, because it would be unreasonable to say that the existence of the above protocol extension means that no type that conforms to Collection could declare its own conformance to Equatable, conditional or otherwise.

See also this question.

If you just don't want to write the duplicate implementation of intRepr everytime, you can do this:

struct StringContainer : ShowableAsIntContainer {
    typealias T = String
    let element: String
}

struct IntContainer : ShowableAsIntContainer {
    typealias T = Int
    let element: Int
}

extension Container where T : ShowableAsInt {
    func intRepr() -> Int {
        return element.intRepr()
    }
}

typealias ShowableAsIntContainer = ShowableAsInt & Container