Search code examples
arraysswiftoption-typeindices

Accessing indices of Swift arrays safely: Ext return not an optional?


In the docs, there's an example of an extension to add safe and unknowing access to any index (real or imagined) of a Swift array (Edit - SO Docs is now defunct so this example was inserted at the bottom of this question).

Swift arrays normally crash if the index doesn't exist. It seems this extension is explicitly designed to solve this problem by returning nil if there's nothing at the index requested.

The extension looks like this:

extension Array {
    subscript (safe index: Int) -> Element? {
        return indices ~= index ? self[index] : nil
    }
}

Use of the extension looks like this:

if let thirdValue = array[safe: 2] {
    print(thirdValue)
}

Trying this out in playgrounds reveals nothing coming back for an unsafe index. That I can tell. Not an optional, not a nil, nothing. And no crash.

How is this not an optional that's coming back from this when it's a safe or unsafe index value? I admire that this works, just don't really understand how it works, or what it's doing.

UPDATE

As Martin R helpfully points out, most of my problem is not understanding/remembering/knowing that if let is doing a forceful unwrapping of the optional that results from the extension's function returning a value that is most definitely an Optional. If this forceful unwrapping fails to divulge a value (ie no value at the index returns an optional containing nil) this prevents the {trailing closure} from being invoked.

However it brings up another subquestion.

If using a variable to capture the return value, like so:

let val = array[safe: 2]
print("val:",  val)

regardless of the result, it is an optional, but Swift provides a "helpful" error saying:

Expression implicitly coerced from 'Int?' to Any

I apologise if this should be a new question: Why is this being coerced to Any? Why can't (and why doesn't it) simply remain as an Optional of type Int?


The following content is from "Accessing indices safely" from Stack Overflow Documentation (archived here); copyright 2017 by Moriya; licensed under CC BY-SA 3.0. An archive of the full Stack Overflow Documentation content can be found at archive.org, in which this example is indexed by its topic ID: 284, as example: 16567.

By adding the following extension to array indices can be accessed without knowing if the index is inside bounds.

extension Array {
    subscript (safe index: Int) -> Element? {
        return indices ~= index ? self[index] : nil
    }
}

example:

if let thirdValue = array[safe: 2] {
    print(thirdValue)
}

Solution

  • if let val = someOptional { ... }
    

    is an optional binding. If someOptional != nil then the condition is true and the unwrapped value someOptional! is assigned to val. If someOptional == nil then the condition is false.

    So in your case

    if let thirdValue = array[safe: 2] {
        print(thirdValue)
    }
    

    the condition is true (and the if-block executed) if array[safe: 2] != nil. In that case, the unwrapped value is assigned to thirdValue. On the other hand, if array[safe: 2] returns nil then the condition is false and the if-block is not executed.

    You can print the return value without using an if-condition to verify that it is nil:

    let array = [1, 2]
    let val = array[safe: 2]
    print("val:",  val) // "val: nil"
    

    With Swift 3.0.1 (Xcode 8.1, tested with GM seed) this will cause compiler warnings

    main.swift:12:16: warning: expression implicitly coerced from 'Int?' to Any
    print("val:",  val)
                   ^~~
    main.swift:12:16: note: provide a default value to avoid this warning
    print("val:",  val)
                   ^~~
                       ?? 
    main.swift:12:16: note: force-unwrap the value to avoid this warning
    print("val:",  val)
                   ^~~
                      !
    main.swift:12:16: note: explicitly cast to Any with 'as Any' to silence this warning
    print("val:",  val)
                   ^~~
                       as Any
    

    as a consequence of the implementation of SE-0140 Warn when Optional converts to Any, and bridge Optional As Its Payload Or NSNull.

    The warning can be suppressed with

    print("val:",  val as Any) // "val: nil"