user2215977 user2215977 - 17 days ago 11
Swift Question

Strange Behaviour on implementing Equatable on Generic Struct in Swift

import Foundation

struct NotEquable {}

struct Box<T> {
let id: Int
let value: T
}

extension Box: Equatable {
static func ==<T>(lhs: Box<T>, rhs: Box<T>) -> Bool {
return lhs.id == rhs.id
}

static func ==<T: Equatable>(lhs: Box<T>, rhs: Box<T>) -> Bool {
return lhs.id == rhs.id && lhs.value == rhs.value
}
}

infix operator ====: AdditionPrecedence
public protocol OperatorEqual {
static func ====(lhs: Self, rhs: Self) -> Bool
}

extension Box: OperatorEqual {
static func ====<T>(lhs: Box<T>, rhs: Box<T>) -> Bool {
return lhs.id == rhs.id
}

static func ====<T: Equatable>(lhs: Box<T>, rhs: Box<T>) -> Bool {
return lhs.id == rhs.id && lhs.value == rhs.value
}
}

public protocol MethodStyleEquatable {
static func equal(lhs: Self, rhs: Self) -> Bool
}

extension Box: MethodStyleEquatable {
static func equal<T>(lhs: Box<T>, rhs: Box<T>) -> Bool {
return lhs.id == rhs.id
}

static func equal<T: Equatable>(lhs: Box<T>, rhs: Box<T>) -> Bool {
return lhs.id == rhs.id && lhs.value == rhs.value
}
}

func freeEqual<T>(lhs: Box<T>, rhs: Box<T>) -> Bool {
return lhs.id == rhs.id
}

func freeEqual<T: Equatable>(lhs: Box<T>, rhs: Box<T>) -> Bool {
return lhs.id == rhs.id && lhs.value == rhs.value
}

let a = Box(id: 1, value: 1)
let b = Box(id: 1, value: 2)
a == b
a ==== b
freeEqual(lhs: a, rhs: b)
Box<Int>.equal(lhs: a, rhs: b)

let c = Box(id: 1, value: NotEquable())
let d = Box(id: 1, value: NotEquable())
c == d
c ==== d
freeEqual(lhs: c, rhs: d)
Box<NotEquable>.equal(lhs: c, rhs: d)


In the above snippet, there are 4 implementation of
Equatable
: the default implementation, the custom operator style, the method style and the free function style. I found that using the operator style in default or custom case always call the generic version of the equal function. On the other hand, using method or free function style will call correct version according to
T
conform to
Equatable
or not. Its this a bug or how can I make generic struct conforms to
Equatable
correctly.

Answer

You are confusing generic parameter to the class to that of the equality function. As written, your code is equivalent to this:

struct Box<T1> {
    let id: Int
    let value: T1
}

extension Box: Equatable {
    static func ==<T2>(lhs: Box<T2>, rhs: Box<T2>) -> Bool {
        return lhs.id == rhs.id
    }

    static func ==<T3: Equatable>(lhs: Box<T3>, rhs: Box<T3>) -> Bool {
        return lhs.id == rhs.id && lhs.value == rhs.value
    }
}

Change your definition to this:

extension Box : Equatable {
    static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool {
        return lhs.id == rhs.id
    }
}

extension Box where T: Equatable {
    static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool {
        return lhs.id == rhs.id && lhs.value == rhs.value
    }
}

And it works as expected.