Home > Back-end >  why multiple optionals(wrap nil) compare with operator "==" return false?
why multiple optionals(wrap nil) compare with operator "==" return false?

Time:11-13

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: 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?

CodePudding user response:

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
  • Related