Reputation: 42690
The following code compiled without issue
protocol Animal {
}
var animals = [Animal]()
However, we have a new requirement, where we need to compare array of Animal
protocol Animal {
}
func x(a0: [Animal], a1: [Animal]) -> Bool {
return a0 == a1
}
var animals = [Animal]()
The above code will yield compilation error
protocol 'Animal' as a type cannot conform to 'Equatable'
We tend to fix by
protocol Animal: Equatable {
}
func x(a0: [Animal], a1: [Animal]) -> Bool {
return a0 == a1
}
var animals = [Animal]()
At array declaration line, we are getting error
protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements
May I know,
Upvotes: 2
Views: 2260
Reputation: 63227
This part of Swift can be a little confusing, and there are plans to improve it.
When you write something like a0: [Animal]
, you saying that your function takes an array argument, whose elements are protocol existentials (of the Animal
protocol).
An existential Animal
is an object that gives its user uniform access to all the requirements (properties, methods, subscripts, initializers, etc.) of the Animal
protocol, regardless of the concrete type of the underlying conforming object (Cat
, Dog
, etc.).
In the new world post SE-0335, you code would have to be spelled like this:
func x(a0: [any Animal], a1: [any Animal]) -> Bool {
return a0 == a1
}
The issue becomes more clear: there's no guarantee that a0
and a1
contain animals of the same type. It's now literally written in the code: they're arrays of any animal type. Each one can contain animals of any type, and there's no relationship between the types of the animals in a0
vs in a1
. This is an issue because Equatable
is verify specific about its requirements: its ==
operator is only applicable to two objects of the same type.
To remedy this, you would need to make your function generic, to constrain a0
and a1
to contain objects of some particular type:
func x<A: Animal>(a0: [A], a1: [A]) -> Bool {
return a0 == a1
}
Upvotes: 1
Reputation: 42690
Thanks to @Alexander and his pointed video resource - https://youtu.be/_m6DxTEisR8?t=2585
Here's is the good workaround, to overcome the current limitation of Swift's protocol.
protocol Animal {
func isEqual(to: Animal) -> Bool
}
func isEqual(lhs: [Animal], rhs: [Animal]) -> Bool {
let count0 = lhs.count
let count1 = rhs.count
if count0 != count1 {
return false
}
for i in 0..<count0 {
if !(lhs[i].isEqual(to: rhs[i])) {
return false
}
}
return true
}
// struct. By conforming Equatable, struct is getting an auto
// implementation of "static func == (lhs: Dog, rhs: Dog) -> Bool"
struct Dog: Animal, Equatable {
func isEqual(to other: Animal) -> Bool {
guard let other = other as? Dog else { return false }
return self == other
}
}
// class
class Cat: Animal, Equatable {
static func == (lhs: Cat, rhs: Cat) -> Bool {
// TODO:
return true
}
func isEqual(to other: Animal) -> Bool {
guard let other = other as? Cat else { return false }
return self == other
}
}
var animals0 = [Animal]()
var animals1 = [Animal]()
// Instead of using
// if animals0 == animals1 {
// we will use the following function call
if isEqual(lhs: animals0, rhs: animals1) {
}
Upvotes: 0
Reputation: 470
Protocol can't conform to Equatable. The reason is, that it requires Self. Self refers to the concrete(e.g. struct/class) type that conforms to the Equatable. If you want to be able to use protocol instead of concrete type for array, then you need to write compression function yourself:
protocol Animal {
var name: String { get }
}
func compare(lhsAnimals: [Animal], rhsAnimals: [Animal]) -> Bool {
guard lhsAnimals.count == rhsAnimals.count else { return false}
for i in 0..<lhsAnimals.count {
if lhsAnimals[i].name != rhsAnimals[i].name {
return false
}
}
return true
}
Upvotes: 0