1

I have a very simple class in a Playground in Swift 4.0 that overrides the == operator.

I'm not understanding why the Swift complier doesn't behave the same when the class inherits/doesn't inherit Equatable protocol.

Here the class when inheriting Equatable protocol

class Test: Equatable  {
    var value = 0

    init(_ initialValue:Int) {
        value = initialValue
    }

    static func == (lhs:Test, rhs:Test) -> Bool {
        return lhs.value == rhs.value ? true : false
    }
}

let test1 = Test(0)
var test4:Test? = nil

if test1 == test4 {
    print("test1 and test4 are equals")
} else {
    print("test1 not equals to test4")
}

When this code executes it displays "test1 not equals to test4". It's the expected behavior.

Next, when I just remove the "Equatable" protocol from the class

class Test  {
    var value = 0

    init(_ initialValue:Int) {
        value = initialValue
    }

    static func == (lhs:Test, rhs:Test) -> Bool {
        return lhs.value == rhs.value ? true : false
    }
}

let test1 = Test(0)
let test3 = Test(0)

var test4:Test? = nil


if test1 == test4 {
    print("test1 and test4 are equals")
} else {
    print("test1 not equals to test4")
}

I get a compilation error on the line

if test1 == test4 {

with the following message: "Value of optional type 'Test?' not unwrapped; did you mean to use "!" or '?'?

Why the behavior is different with/without Equatable?

In fact, I was also expecting the same compilation error when the class inherits from Equatable because I compare a non-optional with an optional.

Is it safe to compare a non-optional with an optional when a class inherits Equatable ?

mfaani
  • 33,269
  • 19
  • 164
  • 293
sebastien
  • 2,489
  • 5
  • 26
  • 47
  • "inheriting Equatable" you mean conforming to Equatable... – mfaani Nov 01 '17 at 16:22
  • Agree but it doesn't tell me why it is different. Why I can compare optional with non-optional with Equatable and why I can compare only non optional when not inheriting Equatable – sebastien Nov 01 '17 at 16:25

2 Answers2

1

There is a == operator

public func ==<T>(lhs: T?, rhs: T?) -> Bool where T : Equatable

which allows to compare two optional values if the underlying type is Equatable. That operator is called in your first case

let test1 = Test(0)
var test4:Test? = nil

if test1 == test4 { ... }

(and the left operand is automatically wrapped into an optional.)

If Test does not conform to Equatable then that operator does not match, so that there is no == operator taking two Test? operands. Therefore the compiler error.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • That's clear and now I know I can compare safely 2 optionals implementing Equatable. – sebastien Nov 01 '17 at 16:46
  • I guess it wraps the non-optional, then passes it through `public func ==(lhs: T?, rhs: T?)` and then unwraps them both and uses the static function written by the dev. right? – mfaani Nov 01 '17 at 20:08
  • @Honey: Here https://github.com/apple/swift/blob/master/stdlib/public/core/Optional.swift#L360 is the implementation. If both operands are non-nil (tested with the "optional pattern") then the unwrapped values are compared with ==. – Martin R Nov 01 '17 at 20:13
  • Nice. So if at this line: `case let (l?, r?):` `l` is `nil`, then that line would just fail *silently* and it would move unto the default case? – mfaani Nov 01 '17 at 20:27
  • @Honey: `case let (l?, r?)` matches if both lhs and rhs are non-nil, and then binds l, r to the unwrapped values. – Martin R Nov 01 '17 at 20:52
1

If you command click on the one that conforms to Equatable it would take you to here:

/// ....
/// You can also use this OPERATOR TO COMPARE A NON-OPTIONAL VALUE TO AN
/// OPTIONAL that wraps the same type. The non-optional value is wrapped as an
/// optional before the comparison is made. In the following example, the
/// `numberToMatch` constant is wrapped as an optional before comparing to the
/// optional `numberFromString`:
///
///     let numberToFind: Int = 23
///     let numberFromString: Int? = Int("23")      // Optional(23)
///     if numberToFind == numberFromString {
///         print("It's a match!")
///     }
///     // Prints "It's a match!"
///
/// ....
public func ==<T>(lhs: T?, rhs: T?) -> Bool where T : Equatable

But for the version that doesn't conform to Equatable, you don't get this. It will only use the static function you provided.

mfaani
  • 33,269
  • 19
  • 164
  • 293