Morty Choi
Morty Choi

Reputation: 2540

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.

Upvotes: 3

Views: 1270

Answers (1)

Code Different
Code Different

Reputation: 93181

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.

Upvotes: 5

Related Questions