But it is an uninitialized optional

Why does the following code compile, even though an uninitialised (not set to an Int value) variable is being used in an expression?

var value: Int?
    
let yes = value == 3

I thought I would have to do this (unwrap the value first):

if let value {
    let yes = value == 3
}

But, while reading SE-0338, I saw a similar pattern and I was puzzled to see it.

EDIT: This is explained in the documentation for Optional.

Optional

Operator

==(::)

Returns a Boolean value indicating whether two optional instances are equal.

static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool

Available when Wrapped conforms to Equatable.

Parameters

lhs

An optional value to compare.

rhs

Another optional value to compare.

Discussion

Use this equal-to operator (==) to compare any two optional instances of a type that conforms to the Equatable protocol. The comparison returns true if both arguments are nil or if the two arguments wrap values that are equal. Conversely, the comparison returns false if only one of the arguments is nil or if the two arguments wrap values that are not equal.

let group1 = [1, 2, 3, 4, 5]
let group2 = [1, 3, 5, 7, 9]
if group1.first == group2.first {
    print("The two groups start the same.")
}
// Prints "The two groups start the same."

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 numberToMatch: Int = 23
let numberFromString: Int? = Int("23")      // Optional(23)
if numberToMatch == numberFromString {
    print("It's a match!")
}
// Prints "It's a match!"

An instance that is expressed as a literal can also be used with this operator. In the next example, an integer literal is compared with the optional integer numberFromString. The literal 23 is inferred as an Int instance and then wrapped as an optional before the comparison is performed.

if 23 == numberFromString {
    print("It's a match!")
}
// Prints "It's a match!"

The compiler will automatically wrap the literal into an Optional before performing the comparison.

Every non-optional value can easily become an optional value by wrapping it while the reverse is clearly not true:

let i1: Int? = 3   // valid, literal is auto-wrapped into Optional
let i2: Int = nil  // invalid
2 Likes

Thank you, but I am still puzzled because an uninitialized (not set to an Int value) optional variable is being used in an expression, which I believe is wrong. The whole point of optionals are to prevent this from happening.

var foo: Int?

if foo == 7 {
   ....
}
else {
   // foo is not even initialised (not set to an Int value); how can we then say that it is not 7?
   // The compiler should at least issue a warning, even better emit an error
  ....
}

Optionals are the only types in Swift (as far as I know) that are default-initialized. var foo: Int? is equivalent to var foo: Int? = nil.

3 Likes

Also tuples of optionals... which includes empty tuples:

var foo: ()
print(foo)

I pitched removing this 6 years ago (Pitch: Remove default initialization of optional bindings) but there wasn't a lot of interest in proceeding. While perhaps it's not the best language feature, at this point it would be too disruptive to remove it, and it doesn't have any serious consequences from the viewpoint of code readability or implementation complexity. And as they say, these funny quirks give each programming language its unique texture and flavor and bring joy into our lives.

9 Likes

But, isn't there an inconsistency here?

func f (u: Int?) {
    let v = u == 7 // Ok!
    let w = u + 7  // Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
}

Why is the == operator more privileged?

Without arguing what the compiler is doing: You can check if a value is nil (so it is not 7), but you cannot add nil to a number.

You could make + returning optional (nil if either argument is optional), but then it would be inconsistent the other way around (with == returning non optional and + returning optional). To fix that inconsistency you'd need two make == returning optional (nil if either argument is optional). In principle that's possible, not sure if that'll be better or worse compared to what we have now.

It is not a privilege. It is a matter of 1. having a clear definition and 2. being useful to developers.

There is no sound definition of the addition of 7 and nil. And when considering an addition Int + Int? that would return, say, an Int?, the language designers in their high wisdom have decided that such an operation would not make the life of Swift programmers better, and would even make it worse. There is no demand for such an operation, and adding an integer to an optional is generally a programming error that the compiler should detect. When you want to do maths, make sure you have what it takes to do maths, i.e. non-optional numbers. That's what the standard library wants you to do.

On the other side, the comparison of 7 and nil is not only well-defined (they are obviously not equal), but it is also very useful. Once a Swift developer knows that a value can be compared to optionals of the same type, it starts happening everywhere. It's a key element of Swift fluency.

Compare:

// 🙄 Meh
if let opt, opt == value {
    // handle equality
} else {
    // handle inequality
}

// 🙄 Come on
let found: Bool
if let opt {
    found = items.contains { $0.value == opt }
} else {
    found = false
}

With:

// 🚀 Just let me code already!
if opt == value {
    // handle equality
} else {
    // handle inequality
}

// 🚀 Fly me to the moon!
let found = items.contains { $0.value == opt }
2 Likes

Looks like the == operator is receiving special treatment.

func f (u: Int?) {
    let v = u == 7 // Ok!
    let w = u + 7  // Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
    let x = u > 7  // Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
}

It is starting to make sense now. :slight_smile:

Thank you, everyone!

Not sure about decision, but think we got == for free just treating Optional as Equatable if WrappedValue is Equatable which is often needed by default, e.g. where you create your own struct and conform it to Equatable and Swift will do conformance automatically.

As for Comparable there is no such rule and for each type you need to implement it manually. Frankly speaking you can implement Comparable for Optional type the same way:

extension Optional: Comparable where Wrapped: Comparable {
  public static func < (lhs: Optional, rhs: Optional) -> Bool {
    switch (lhs, rhs) {
    case let (l?, r?):
      return l < r
    default:
      return false
    }
  }
}

But for arithmetic operations (AdditiveArithmetic protocol and others) think Optional is not a number to treat it that way. Or for example what does let w = u - 7 mean if u is .none? :thinking:

Note that your example would not be much harder to deal with with "==" returning optional:

// 🚀 Just let me code already!
if (opt == value) ?? false {
    // handle equality
} else {
    // handle inequality
}

// 🚀 Fly me to the moon!
let found = items.contains { $0.value == opt } ?? false

Same could have been the case for <, >, and even for + or -

So you program a puzzle solver and at one point found this entry: "3 1 nil 6".
Could it be "3 1 7 6"? Yes it could, you just don't know at that point. It's not a blanket "no it can't". This "maybe" result could be well modeled with nil.

.none obviously.

As Gwendal has explained, there is no “special treatment” for ==.

This is not a semantically sound implementation of < because two nil values would be both equal to and less than each other.

A semantically sound implementation of the comparison operators was removed from Swift 3 in SE-0121. See that discussion for detailed rationale.

2 Likes

This is not what’s modeled by the equivalence relation ==, which is what Gwendal is speaking of. Else your puzzle would already be “solved” with the entry (nil, nil, nil, nil).

That would be “special treatment,” since it would be tantamount to implicit optional chaining.

While we can debate what counts as “serious”, in Swift codebases I’ve worked in I fairly routinely find (and write!) bugs due to the implicit initialization of optionals defeating the usual DI guarantees we get for non-optional properties. It’s just way too easy to write an init which never initializes some property, especially as a codebase evolves and a type has more and more properties added to it.

10 Likes

agreed. it’s a big source of round-tripping bugs when writing custom Codable logic.

2 Likes

Yeah, of course, just copied and haven't double checked :man_facepalming:

That's why I've wrote you can, don't suggest to reimplement it back into the language. Though want to go back and read the discussion, as some other languages have Comparable by default (as I remember). Thx for pointing!

To be precise, optionals declared using the ? syntax are initialized to nil by default. Optionals declared as Optional<…> are not initialized at all by default:

var x: Optional<Int>
print(x)
      ^ Variable ‘x’ used before being initialized
8 Likes

Sorry, I should have made it clearer; I apologise.

This is what I meant.

Imagine that you are learning Swift from the TSPL Book.

For the < operator, you would write:

let u: Int?

// must unwrap u first
if let u, u < 7 {
   ...
}
else {
   ...
}

And for the == operator, you would write:

let u: Int?

// must unwrap u first
if let u, u == 7 {
   ...
}
else {
   ...
}

But then if you encounter this:

let u: Int?

if u == 7 {
   ...
}
else {
   ...
}

you would naturally say: hang on a minute, there is something special about the == operator here because the first operand, an optional variable, is not being unwrapped, and the code still compiles.