Swift Generics: How to avoid this strange behaviour?

Viewed 62

I met with the strange behaviour. Maybe i'm doing it wrong but I have not a clue.

Error: Cannot assign value of type '[Itemable]' to type '[T]'

protocol Itemable {
    var id: Int { get set }
}

protocol Paginatable {
    var items: [Itemable] { get set }
}

class Fetcher<T: Itemable, P: Paginatable> {
    var items = [T]()
    
    func onReceive(pagination: P) {
        items = pagination.items
    }
}
2 Answers

Class Fetcher expects a T type, i.e. a specific type conforming to Itemable.

So, for example, if you had a type Foo: Itemable, Fetcher<Foo, ...> would expect to work with Foos - not any Itemable, like AnotherFoo: Itemable - but only Foo.

Yet Ps don't have such as restriction on their items property - both Foo and AnotherFoo and something else could be in items.

So, you're basically trying to do the following:

let items: [Itemable] = [...]
let foos: [Foo] = items // error

If you want to constrain P to hold the same items as T, then you could do the following:

protocol Itemable {
    var id: Int { get set }
}

protocol Paginatable {
    associatedtype Item: Itemable
    var items: [Item] { get set }
}

class Fetcher<T, P: Paginatable> where P.Item == T {
    var items = [T]()

    func onReceive(pagination: P) {
        items = pagination.items
    }
}

You need Paginatable to be a generic. Then everything falls into place. You do not want it to declare an array of Itemable but an array of some Itemable adopter. That is exactly what a generic lets you say.


In other words:

protocol Paginatable {
    var items: [Itemable] { get set }
}

...means: "Whoever adopts me (I'm looking at you, Fetcher), must declare that items is [Itemable]." That is not what you want Fetcher to have to say. But you are forcing Fetcher to say that.

Instead, say this:

protocol Paginatable {
    associatedtype T : Itemable
    var items: [T] { get set }
}

Now you are saying: "Whoever adopts me can declare items as an array of any type as long as that type adopts Itemable." That is exactly what you do want to say.


To illustrate further, this is illegal:

protocol Itemable { }
protocol Paginatable {
    var items: [Itemable] { get set }
}
struct Dummy : Itemable {}
struct Fetcher : Paginatable { // error
    var items: [Dummy]
}

But this is fine:

protocol Itemable { }
protocol Paginatable {
    associatedtype T : Itemable
    var items: [T] { get set }
}
struct Dummy : Itemable {}
struct Fetcher : Paginatable {
    var items: [Dummy]
}

And that second thing is exactly what you want to say. Now that you've understood that, if you then want to make Fetcher a generic too, no problem:

protocol Itemable { }
protocol Paginatable {
    associatedtype T : Itemable
    var items: [T] { get set }
}
struct Dummy : Itemable {}
struct Fetcher <T:Itemable> : Paginatable {
    var items: [T]
}
Related