Add default implementation of == to Comparable?

Currently Comparable inherits from Equatable, but does not provide a default implementation for ==, so the compiler tries to synthesize one composed of member-wise ==s. This leads to a problem where if a type's < is not composed of member-wise inequalities, then <, >, and == all evaluate to false for some pairs of values. For example:

struct Length {
    enum Unit: Double, Comparable {
        case mm = 0.001
        case m = 1
        case km = 1000
        case banana = 0.178 // according to
    let magnitude: Double
    let unit: Unit
    static func < (lhs: Self, rhs: Self) -> Bool {
        lhs.magnitude * lhs.unit.rawValue < rhs.magnitude * rhs.unit.rawValue

let aBanana = Length(magnitude: 1, unit: .banana)
let anotherBanana = Length(magnitude: 0.178, unit: .m)

print(aBanana < anotherBanana) // prints 'false'
print(aBanana > anotherBanana) // prints 'false'
print(aBanana == anotherBanana) // prints 'false'

Logically, a = b iff a ≮ b and a ≯ b, so one would reasonably think that aBanana == anotherBanana, because neither the < nor the > evaluates to true. This is also the source of a SwiftPM bug where one package version can be neither equal to nor greater/lesser than another at the same time.

Additionally, the current behaviour does not match Comparable's documentation:

Types with Comparable conformance implement the less-than operator (<) and the equal-to operator (==). These two operations impose a strict total order on the values of a type, in which exactly one of the following must be true for any two values a and b:

  • a == b
  • a < b
  • b < a

I tried to think of an example where the status quo is preferred, but can't come up with a good one.

Adding a default implementation of == to Comparable like this:

extension Comparable {
    static func == (lhs: Self, rhs: Self) {
        !(lhs < rhs || lhs > rhs)

is silently source breaking, but likely the sources it breaks are already incorrect.

What does everyone think?


That is true only for totally ordered sets.

As an example, Double, which conforms to Comparable, is not totally ordered. 3.0 > .nan is false, 3.0 < .nan is false, but 3.0 == .nan is false.


That's interesting. I didn't know that. It makes sense too, because .nan is "not a number", and you can't compare a number with a not-a-number.

Exceptions like .nan allowed for in the note at the bottom of the docs:


A conforming type may contain a subset of values which are treated as exceptional—that is, values that are outside the domain of meaningful arguments for the purposes of the Comparable protocol. For example, the special “not a number” value for floating-point types ( FloatingPoint.nan ) compares as neither less than, greater than, nor equal to any normal floating-point value. Exceptional values need not take part in the strict total order.


But maybe we should shouldn't (EDIT) auto synthesize equatable conformance, when comparable is manually implemented?

1 Like

Maybe we should stop auto synthesising func == / func < when one of them is implemented manually.


That's what I meant (and thought I wrote). Updated my comment. :-)


See the related / which covers the same basic issue for Hashable / Equatable.

It's not quite as clear-cut that a type with a custom Comparable conformance requires a custom Equatable, but a type with a custom Equatable conformance almost certainly needs a custom Comparable conformance.


I think you're right. In fact, I thought == was implemented according to your proposed default before I read this post.

Thanks for posting the links to SR-11588 and the related pull request. I read them and just opened SR14665 for this.

Maybe instead of either adding a default == or disabling synthesizing == for Comparable, which are source-breaking, we can add a warning to non-synthesized < that the synthesized == might be incorrect?


Yes, that sounds right to me.

Terms of Service

Privacy Policy

Cookie Policy