skip to Main Content

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,

  1. Why we can have an array of protocol, before the protocol is conforming to Equatable?
  2. Why we are not allowed to have an array of protocol, once the protocol is conforming to Equatable?
  3. What are some good ways to fix such error?

3

Answers


  1. Chosen as BEST ANSWER

    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) {
        
    }
    

  2. 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
    }
    
    Login or Signup to reply.
  3. 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 constain a0 and a1 to contain objects of some particular type:

    func x<A: Animal>(a0: [A], a1: [A]) -> Bool {
        return a0 == a1
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search