Using some protocol as a concrete type conforming to another protocol is not supported

Víctor Albertos picture Víctor Albertos · Nov 3, 2015 · Viewed 22.9k times · Source

I’m trying to mix generics with protocols and I’m getting a really hard time xD

I have certain architecture implemented in an Android/Java project, and I’m trying to rewrite it to fit it in a swift/iOS project. But I’ve found this limitation.

ProtocolA

protocol ProtocolA {

}

ProtocolB

protocol ProtocolB : ProtocolA {

}

ImplementProtocolA

class ImplementProtocolA <P : ProtocolA> {

    let currentProtocol : P

    init(currentProtocol : P) {
        self.currentProtocol = currentProtocol
    }

}

ImplementProtocolB

class ImplementProtocolB : ImplementProtocolA<ProtocolB> {

}

So, when I try to set ProtocolB as the concrete type that implements ProtocolA, I get this error:

Using 'ProtocolB' as a concrete type conforming to protocol 'ProtocolA' is not supported

1 Is there any reason for this “limitation”?

2 Is there any workaround to get this implemented?

3 Will it be supported at some point?

--UPDATED--

Another variant of the same problem, I think:

View protocols

protocol View {

}

protocol GetUserView : View {
    func showProgress()
    func hideProgress()
    func showError(message:String)
    func showUser(userDemo:UserDemo)
}

Presenter protocols

protocol Presenter {
    typealias V : View
}

class UserDemoPresenter : Presenter {
    typealias V = GetUserView
}

Error:

UserDemoPresenter.swift Possibly intended match 'V' (aka 'GetUserView') does not conform to 'View’

What is that?? It conforms!

Even if I use View instead of GetUserView, it does not compile.

class UserDemoPresenter : Presenter {
    typealias V = View
}

UserDemoPresenter.swift Possibly intended match 'V' (aka 'View') does not conform to 'View'

xxDD I don’t get it, really.

--UPDATED--

With the solution proposed by Rob Napier the problem is not fixed, instead, it is just delayed.

When a try to define a reference to UserDemoPresenter, I need to specify the generic type, so I get the same error:

private var presenter : UserDemoPresenter<GetUserView>

Using 'GetUserView' as a concrete type conforming to protocol 'GetUserView' is not supported

Answer

Rob Napier picture Rob Napier · Nov 4, 2015

The underlying reason for the limitation is that Swift doesn't have first-class metatypes. The simplest example is that this doesn't work:

func isEmpty(xs: Array) -> Bool {
    return xs.count == 0
}

In theory, this code could work, and if it did there would be a lot of other types I could make (like Functor and Monad, which really can't be expressed in Swift today). But you can't. You need to help Swift nail this down to a concrete type. Often we do that with generics:

func isEmpty<T>(xs: [T]) -> Bool {
    return xs.count == 0
}

Notice that T is totally redundant here. There is no reason I should have to express it; it's never used. But Swift requires it so it can turn the abstract Array into the concrete [T]. The same is true in your case.

This is a concrete type (well, it's an abstract type that will be turned into a concrete type any time it's instantiated and P is filled in):

class ImplementProtocolA<P : ProtocolA>

This is a fully abstract type that Swift doesn't have any rule to turn into a concrete type:

class ImplementProtocolB : ImplementProtocolA<ProtocolB>

You need to make it concrete. This will compile:

class ImplementProtocolB<T: ProtocolB> : ImplementProtocolA<T> {}

And also:

class UserDemoPresenter<T: GetUserView> : Presenter {
    typealias V = T
}

Just because you're likely to run into the issue later: your life will go much easier if you'll make these structs or final classes. Mixing protocols, generics, and class polymorphism is full of very sharp edges. Sometimes you're lucky and it just won't compile. Sometimes it will call things you don't expect.

You may be interested in A Little Respect for AnySequence which details some related issues.


private var presenter : UserDemoPresenter<GetUserView>

This is still an abstract type. You mean:

final class Something<T: GetUserView> {
    private var presenter: UserDemoPresenter<T>
}

If that creates a problem, you'll need to create a box. See Protocol doesn't conform to itself? for discussion of how you type-erase so that you can hold abstract types. But you need to work in concrete types. You can't ultimately specialize on a protocol. You must eventually specialize on something concrete in the majority of cases.