1

For example we have two simple protocols:

protocol Container {
    var items: [Item] {get}
}

protocol Item {
    var name: String {get}
}

And we want to use a function that requires Item to be Equatable, like:

extension Container {
    func indexOfItem(_ item: Item) -> Int? {
        items.firstIndex(of: item)
    }
}

Yet we can't write it like this, as firstIndex requires argument conforming to Equatable. But Item being protocol, can't conform to Equatable.

I've tried to implement it using type erasure, but it became too complicated.

So I am curious is there some workaround to implement such functions (provided that actual items are equatable)?

Vladimir
  • 7,670
  • 8
  • 28
  • 42
  • 1
    `func indexOfItem(_ item: T) -> Int? where T: Item & Equatable`? – TylerP Sep 09 '22 at 23:50
  • Does not compile also: Non-protocol, non-class type 'Equatable?' cannot be used within a protocol-constrained type – Vladimir Sep 09 '22 at 23:58
  • I think you copy-pasted the question mark at the end. Look closer – the question mark is not part of the code snippet I suggested. – TylerP Sep 10 '22 at 00:28
  • Yes, missed this question mark, sorry. And function declaration is OK, but compiler still not satisfied with: items.firstIndex(of: item) "Protocol 'Item' as a type cannot conform to 'Equatable'" – Vladimir Sep 10 '22 at 01:02

2 Answers2

1

Since you're using func firstIndex of an array in this func indexOfItem(_ item: Item) -> Int? therefore the Item has to be a concrete object (behind the scene of firstIndex func is comparing each element of an array and print out the index of the element).

There are 2 ways to do this

  • First is using associatedtype to keep your protocol generic
protocol Item: Equatable {
    var name: String { get }
}

protocol Container {
    associatedtype Item
    var items: [Item] { get }
}

struct MyItem: Item {
    var name: String
}

extension Container where Item == MyItem {
    func indexOfItem(_ item: Item) -> Int? {
        return items.firstIndex(of: item)
    }
}
  • Second is using an equatable object MyItem instead a protocol Item inside the Container protocol
protocol Item {
    var name: String { get }
}

protocol Container {
    var items: [MyItem] { get }
}

struct MyItem: Item, Equatable {
    var name: String
}

extension Container {
    func findIndex(of item: MyItem) -> Int? {
        return items.firstIndex(of: item)
    }
}
Tran To
  • 285
  • 1
  • 6
  • Second option is not acceptable as I have real class types behind this protocols. But first one works, thank you, I also came to same solution – Vladimir Sep 10 '22 at 01:08
  • yeah, that's engineer life. Asking questions and fount the answer immediately. For my second option, I just answered very generically. I don't know the full context of your code, so i't fine. – Tran To Sep 10 '22 at 01:31
0

Finally find simple enough solution:

То make protocol generic with associated type and constraint this type to Equatable.

public protocol Container {
    associatedtype EquatableItem: Item, Equatable
    var items: [EquatableItem] {get}
}

public protocol Item {
    var name: String {get}
}

public extension Container {
    func indexOfItem(_ item: EquatableItem) -> Int? {
        items.firstIndex(of: item)
    }
}

This compiles and now if I have some types

struct SomeContainer {
    var items: [SomeItem]
}

struct SomeItem: Item, Equatable {
    var name: String
}

I only need to resolve associatedtype to provide protocol conformance for SomeContainer type:

extension SomeContainer: Container {
    typealias EquatableItem = SomeItem
}
Vladimir
  • 7,670
  • 8
  • 28
  • 42