Skip to content

Swift: Type 'Protocol' does not conform to protocol 'Protocol'?

Published: at 03:05 PM

Introduction

Today, while working with Swift, I encountered a strange bug. The code was roughly like this:

protocol A {
}

protocol B: A {}

class C: B {}

protocol Test {
    associatedType AProtocol: A
    var a: AProtocol { get }
}

class TestImpl<T: A>: Test {
    var a: T

    init(a: T) {
        self.a = a
    }
}

let c: B = C()

let test = TestImpl(a: c)

The intention behind the code above was simple: to make TestImpl depend only on ProtocolA without relying on its specific implementation.

However, the Swift compiler threw an error: “Type ‘any B’ cannot conform to ‘A”. This puzzled me - isn’t B inheriting from A?

So, I modified the code to:

let c: A = C()

let test = TestImpl(a: c)

Result: The Swift compiler continued to throw “Type ‘any A’ cannot conform to ‘A”.

Astonishingly, A doesn’t even conform to itself? Let’s delve into this further.

Protocol in Swift

Those familiar with Swift are somewhat aware of the concept of protocols, which define a set of behaviors, including constructors, static properties, and methods.

The primary use of protocols is to declare the type of a value as the type of that protocol. However, unlike other languages, protocols cannot be used as concrete types.

What does “used” mean? In Swift, any time a type is referenced other than during its declaration, it is considered “used”, such as when passing parameters or declaring generic types.

However, the used type must be a concrete type; in other words, it cannot be a protocol.

Why did Swift design it this way? For those who are interested can refer to Protocol doesn’t conform to itself? for more insight.

One convincing argument is that protocols can declare constructors.

If they could be used as concrete types, how would Swift know which type’s instance to build when calling its constructor?

protocol A {
    init()
}

class B: A {
    init() {}
}

class C: A {
    init() {}
}

class Test<T: A> {
    func foo() -> T {
        return T() // If Swift only knows T is A, how can it determine whether to call B's init or C's init?
    }
}

PS: I am a bit puzzled here. Why does Swift allow protocols to include declarations for initializers and static properties/methods? This leads to significant restrictions when using the protocol, and I have yet to find an elegant way to mock it.

Solution

Having understood Swift’s type mechanism, we can now address the issue encountered earlier.

let c: B = C()
let test = TestImpl(a: c)

The error above occurred because we declared c as a protocol type and then passed it as a parameter to the constructor of TestImpl, which is not allowed since we used a protocol type.

Since this is a language restriction, the best approach is to adhere to it and only use concrete types, like so:

let c = C() // Compiler automatically infers c as C

let test = TestImpl(a: C)

Of course, there are ways to work around this limitation, but they are not recommended.

For specific methods, refer to the answers in Protocol doesn’t conform to itself?.