0

I'm experimenting with Swift protocols, and I've run into something funny. There are lots of workarounds, but can anyone explain the error below?

protocol UserRenderable {
    var name : String { get }
}

protocol PostRenderable {
    var title: String { get }
    var author: UserRenderable { get }
}

struct User {
    let id: String
    let name : String
}

struct Post {
    let id: String
    let title: String
    let author: User
}

extension User : UserRenderable {}

extension Post: PostRenderable {}

The above code (drop it in a playground, or whatever) will throw a compilation error:

protocol requires property 'author' with type 'UserRenderable'; do you want to add a stub?
var author: UserRenderable { get }

What's the reason for this?

jscs
  • 63,694
  • 13
  • 151
  • 195
Sam Ballantyne
  • 487
  • 6
  • 18

2 Answers2

0

The best solution for this isn't with associated types. It looks like there's a neater option (from the answer to another question):

There's no real reason why this shouldn't be possible, a read-only property requirement can be covariant, as returning a ConformsToB instance from a property typed as ProtocolB is perfectly legal.

Swift just currently doesn't support it. In order to do so, the compiler would have to generate a thunk between the protocol witness table and conforming implementation in order to perform the necessary type-conversion(s). For example, a ConformsToB instance would need to be boxed in an existential container in order to be typed as ProtocolB (and there's no way the caller can do this, as it might not know anything about the implementation being called).

But again, there's no reason why the compiler shouldn't be able to do this.

It turns out you can trick the compiler into doing your bidding, without writing a thunk, using good old typelias. This code compiles without issue:

protocol UserRenderable {
    var name : String { get }
}

protocol PostRenderable {
    var title: String { get }
    var author: UserRenderable { get }
}

struct User {
    let id: String
    let name : String
}

struct Post {
    let id: String
    let title: String
    let author: User
}

extension User : UserRenderable {}

extension Post: PostRenderable {
    typealias User = UserRenderable
}
jscs
  • 63,694
  • 13
  • 151
  • 195
Sam Ballantyne
  • 487
  • 6
  • 18
-1

Your Post struct has a very specific type for the author. The protocol specifies that something that is PostRenderable needs to have an author that is UserRenderable. While all Users are UserRenderable, not all UserRenderable items are User. Therefor, you should change the type of author to UserRenderable instead of User.

donnywals
  • 7,241
  • 1
  • 19
  • 27