0
var opt1: Int??? = nil
var opt2: Int?? = nil
print(opt1 == opt2) // why is false?

At first I thought it was because of the different types(Int??? and Int??), so I custom a operator <==> and use the same code as the Optional' == source code to implement it:

extension Optional {
    public static func <==> (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
        switch (lhs, rhs) {
        case let (l?, r?):
            return l <==> r
        case (nil, nil): //I think it should match here
            return true
        default:
            return false
        }
    }
}
print(opt1 <==> opt2) // false

(the source code link: https://github.com/apple/swift/blob/main/stdlib/public/core/Optional.swift)

I saw from the breakpoint that lhs and rhs become the same type.

enter image description here

In other words, this is not caused by the type.

And Optional' ~= operator implementation in the source code:

public static func ~=(lhs: _OptionalNilComparisonType, rhs: Wrapped?) -> Bool {
    switch rhs {
    case .some:
      return false
    case .none:
      return true
    }
  }

According to the code above,I think case (nil, nil): in static func <==> should match, so print(opt1 == opt2) and print(opt1 <==> opt2) should be true, but why it's false?

Joiner
  • 81
  • 9

1 Answers1

3

There are four kinds of values that a Int??? can have:

  1. .none (nil)
  2. .some(.none) (a non nil Int??? wrapping a nil Int??)
  3. .some(.some(.none)) (a non nil Int??? wrapping a non nil Int?? wrapping a nil Int?)
  4. .some(.some(.some(n))) where n is an Int (an Int wrapped by 3 layers of Optionals)

Similarly, there are three kinds of values that an Int?? can have

  1. .none
  2. .some(.none)
  3. .some(.some(n)) where n is an Int

When you write nil, it always means .none of whatever type that context needs, so both opt1 and opt2 are .none here.

What happens when you pass them to the == operator? Well, After some overload resolution/type inference, the compiler finds that == takes a two Int??? parameters, but you have passed an Int?? as the second argument. Luckily, there exists a conversion from any value t of type T to type T? - .some(t).

So after being passed into the ==, opt2 changes from .none to .some(.none), as you can see from this code snippet:

func test(lhs: Int???, rhs: Int???) {
    if case .none = lhs, case .some(.none) = rhs {
        print("lhs is .none and rhs is .some(.none)")
    }
}

test(lhs: opt1, rhs: opt2) // prints

Hence they are "not equal".

The debugger seems to be showing both .none and .some(.none) as "nil", possibly because both of their debugDescription/description is "nil".


If you don't care about the multiple layers of optionals, you can just unwrap them to a single layer by doing as? Int, then compare:

print((opt1 as? Int) == (opt2 as? Int)) // true
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Thanks. And I have another question, I extension Optional in my own swift file like this: `extension Optional where Wrapped: Equatable` and wrote three `==` operator static func with the same signature as standard library, I thought it should fail to compile: **Invalid redeclaration of '=='**. But it succeeded! Why can it be override successfully? The same method signature is override instead of overloaded, according to the documentation, the existing method cannot be overwritten – Joiner Nov 13 '21 at 04:43
  • Is it because of **access control** ? – Joiner Nov 13 '21 at 05:15
  • @Joiner [I had this question too](https://stackoverflow.com/q/46123963/5133585). It’s apparently because `Optional` is in another module. – Sweeper Nov 13 '21 at 08:17
  • you saved me, thanks – Joiner Nov 14 '21 at 07:07