Optional auto unwrapped

Hi forum,

In line 9, the optional index is with Not equal to operator and isn't compared with nil. Why it doesn't need to be followed by exclamation point (!) to force unwrap it like this: index!

But it needs force unwrap when Less than operator is used:
while index! < a.count {

Thanks

import Foundation

//find all occurrences of specified element in array
//there're 2: "aaa", "aaa"

let a = ["aaa", "bbb", "ccc", "aaa", "eee"]
//var index: Int? = 0 //wrong
var index = 0

while index != a.count { // line 9
    guard let tmp = a[index...].firstIndex(of: "aaa") else { // no index!
        break
    }
    print(tmp, ":", a[tmp])
    index = tmp + 1
}

/*
0 : aaa
3 : aaa
*/

!= (really ==) is defined for the Optional versions of values that are Equatable (and others). The range operator (...) is not, since it doesn't make sense semantically to create a range from a nil value. You could define your own custom operator, such as ?... but typically Swift developers work to avoid Optional values in the first place.

The topic title “Optional auto unwrapped” implies a misunderstanding. Nothing in your example is auto-unwrapped. Instead, a.count is automatically wrapped as Optional<Int>.some(a.count).

When Swift sees the expression index != a.count, it looks for a definition of != with arguments Int? (on the left) and Int (on the right). There is no such definition. Swift does find a generic definition of != when the left and right arguments have the same type and that type is Equatable.

Int? (also called Optional<Int>) is Equatable, because Int is Equatable. So Swift wraps a.count automatically, treating it as Int?.some(a.count). Now the left and right sides of the != operator have the same type, and that type is Equatable, so Swift has a usable definition of !=.

The conformance of Optional to Equatable looks like this:

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

As you can see, nil == nil and .some(a) == .some(b) if and only if a == b. All other combinations are considered not-equal: nil != .some(0), nil != .some(1), etc.

Now consider index < a.count. There is no definition of the < operator that takes Int? on the left and Int on the right. There is a declaration where both sides have the same type and that type is Comparable. And Int is Comparable. Could we make Optional conform to Comparable when the wrapped type (Int in this case) conforms to Comparable?

The conformance would look something like this:

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

The problem is that there is no obvious or almost-universally-correct choice for the default case. Should nil be considered less than Int.min? Should nil be considered greater than Int.max? Should it be considered neither less than nor greater than, but also not equal to, any Int? There are use cases for all of these possibilities, and none of them is obviously “the best”. And whatever we put there, it would have to work for any type that conforms to Comparable, not just Int. There's no best choice, so Optional does not conform to Comparable.

In your algorithm, you never actually assign nil to index, so there is no reason for index to be Optional. If you change its type to Int, you can use the < operator and avoid force-unwrapping index:

var index: Int = 0 // no longer Optional
while index < a.count {
    guard let tmp = a[index...].firstIndex(of: "aaa") else { break }
    print("\(tmp): \(a[tmp])")
    index = tmp + 1
}
5 Likes

Hi Rob,

In the beginning, I thought I need an optional to hold firstIndex()'s return value which is an optional too. I forgot that the return value is unwrapped in guard once. Yes, no need to declare index as optional.

Thank you very much.

Just mentioning that there used to be comparison operators for optionals, but they were intentionally removed in Swift 3:

SE-0121 Remove Optional Comparison Operators

1 Like

Hello Martin,

I read your post on stackoverflow about using readLine() in swift.

I had a problem with readLine() from stdin and restore stdin.

Could you please kindly help me here:

Terms of Service

Privacy Policy

Cookie Policy