65

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

Víctor Albertos
  • 8,093
  • 5
  • 43
  • 71
  • 2
    Also observed here: http://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself. – Martin R Nov 03 '15 at 16:16

1 Answers1

63

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.

Community
  • 1
  • 1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Thanks a lot, for the solution and the explanation. Could you provide an example of code using structs instead of class, in order to fully understand what you have suggested? – Víctor Albertos Nov 04 '15 at 16:25
  • 1
    I just mean replace the word `class` with the word `struct` in your code. If you use `struct`, you'll get value semantics (when you pass it to a function, the function gets its own independent copy). If you need reference semantics (when you pass it to a function, modifications the function makes to it are seen by the caller), then use `final class`. Both `struct` and `final class` forbid subclassing, which makes things much easier. (Subclassing brings a lot of crazy.) – Rob Napier Nov 04 '15 at 16:35
  • Ok, thanks! I'm going to do the class final, in fact it is final in the original java project. But I think I'm going to continue using class instead of struct, I need this project to mimic as much as possible the behaviour defined at the java project, and changing class for struct could lead to a really really big different scenario xD – Víctor Albertos Nov 04 '15 at 16:46
  • 'This is a fully abstract type that Swift doesn't have any rule to turn into a concrete type: class ImplementProtocolB : ImplementProtocolA' Hey Rob, can you elaborate on how is this not a concrete type? Thanks – Mercurial Aug 08 '16 at 08:42
  • 1
    @Mercurial Swift doesn't know the actual implementation of ProtocolB, so it can't work out how much storage to allocate to hold "the thing you will later pass me that conforms to ProtocolB but might be any size." It would be possible for Swift to automatically create an indirection box to store it (which is how it handles variables with protocol types), but that isn't part of Swift today. It needs a concrete type (one that it knows the final size of). – Rob Napier Aug 08 '16 at 12:17
  • @RobNapier Makes sense. Thanks. – Mercurial Aug 09 '16 at 11:59
  • Given we have this: protocol Base {} protocol Extended: Base {} If I do this it compiles: struct Struct {} let str = Struct() BUT if I do this it gives an error: struct Struct {} let str = Struct() // ERROR: Using 'Extended' as a concrete type conforming to protocol 'Base' is not supported. // How come? – Igor Vasilev Sep 27 '18 at 09:02
  • Because `Extended` is not a concrete type conforming to `Base`, which is what `T: Base` requires. `Extended` is a protocol. – Rob Napier Sep 27 '18 at 12:45