Safe (bounds-checked) array lookup in Swift, through optional bindings?

XcodeSwift

Xcode Problem Overview


If I have an array in Swift, and try to access an index that is out of bounds, there is an unsurprising runtime error:

var str = ["Apple", "Banana", "Coconut"]

str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION

However, I would have thought with all the optional chaining and safety that Swift brings, it would be trivial to do something like:

let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
    print(nonexistent)
    ...do other things with nonexistent...
}

Instead of:

let theIndex = 3
if (theIndex < str.count) {         // Bounds check
    let nonexistent = str[theIndex] // Lookup
    print(nonexistent)   
    ...do other things with nonexistent... 
}

But this is not the case - I have to use the ol' if statement to check and ensure the index is less than str.count.

I tried adding my own subscript() implementation, but I'm not sure how to pass the call to the original implementation, or to access the items (index-based) without using subscript notation:

extension Array {
    subscript(var index: Int) -> AnyObject? {
        if index >= self.count {
            NSLog("Womp!")
            return nil
        }
        return ... // What?
    }
}

Xcode Solutions


Solution 1 - Xcode

Alex's answer has good advice and solution for the question, however, I've happened to stumble on a nicer way of implementing this functionality:

Swift 3.2 and newer
extension Collection {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}
Swift 3.0 and 3.1
extension Collection where Indices.Iterator.Element == Index {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Credit to Hamish for coming up with the solution for Swift 3.


Example
let array = [1, 2, 3]

for index in -20...20 {
    if let item = array[safe: index] {
        print(item)
    }
}

Solution 2 - Xcode

If you really want this behavior, it smells like you want a Dictionary instead of an Array. Dictionaries return nil when accessing missing keys, which makes sense because it's much harder to know if a key is present in a dictionary since those keys can be anything, where in an array the key must in a range of: 0 to count. And it's incredibly common to iterate over this range, where you can be absolutely sure have a real value on each iteration of a loop.

I think the reason it doesn't work this way is a design choice made by the Swift developers. Take your example:

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"

If you already know the index exists, as you do in most cases where you use an array, this code is great. However, if accessing a subscript could possibly return nil then you have changed the return type of Array's subscript method to be an optional. This changes your code to:

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
//                                     ^ Added

Which means you would need to unwrap an optional every time you iterated through an array, or did anything else with a known index, just because rarely you might access an out of bounds index. The Swift designers opted for less unwrapping of optionals, at the expense of a runtime exception when accessing out of bounds indexes. And a crash is preferable to a logic error caused by a nil you didn't expect in your data somewhere.

And I agree with them. So you won't be changing the default Array implementation because you would break all the code that expects a non-optional values from arrays.

Instead, you could subclass Array, and override subscript to return an optional. Or, more practically, you could extend Array with a non-subscript method that does this.

extension Array {

    // Safely lookup an index that might be out of bounds,
    // returning nil if it does not exist
    func get(index: Int) -> T? {
        if 0 <= index && index < count {
            return self[index]
        } else {
            return nil
        }
    }
}

var fruits: [String] = ["Apple", "Banana", "Coconut"]
if let fruit = fruits.get(1) {
    print("I ate a \( fruit )")
    // I ate a Banana
}

if let fruit = fruits.get(3) {
    print("I ate a \( fruit )")
    // never runs, get returned nil
}

###Swift 3 Update

func get(index: Int) -> T? needs to be replaced by func get(index: Int) -> Element?

Solution 3 - Xcode

To build on Nikita Kukushkin's answer, sometimes you need to safely assign to array indexes as well as read from them, i.e.

myArray[safe: badIndex] = newValue

So here is an update to Nikita's answer (Swift 3.2) that also allows safely writing to mutable array indexes, by adding the safe: parameter name.

extension Collection {
    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript(safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

extension MutableCollection {
    subscript(safe index: Index) -> Element? {
        get {
            return indices.contains(index) ? self[index] : nil
        }

        set(newValue) {
            if let newValue = newValue, indices.contains(index) {
                self[index] = newValue
            }
        }
    }
}

Solution 4 - Xcode

extension Array {
    subscript (safe index: Index) -> Element? {
        0 <= index && index < count ? self[index] : nil
    }
}
  • O(1) performance
  • type safe
  • correctly deals with Optionals for [MyType?] (returns MyType??, that can be unwrapped on both levels)
  • does not lead to problems for Sets
  • concise code

Here are some tests I ran for you:

let itms: [Int?] = [0, nil]
let a = itms[safe: 0] // 0 : Int??
a ?? 5 // 0 : Int?
let b = itms[safe: 1] // nil : Int??
b ?? 5 // nil : Int? (`b` contains a value and that value is `nil`)
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?

Solution 5 - Xcode

Swift 4

An extension for those who prefer a more traditional syntax:

extension Array {
    func item(at index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Solution 6 - Xcode

Valid in Swift 2

Even though this has been answered plenty of times already, I'd like to present an answer more in line in where the fashion of Swift programming is going, which in Crusty's words¹ is: "Think protocols first"

• What do we want to do?

  • Get an Element of an Array given an Index only when it's safe, and nil otherwise
    • What should this functionality base it's implementation on?

  • Array subscripting
    • Where does it get this feature from?

  • Its definition of struct Array in the Swift module has it
    • Nothing more generic/abstract?

  • It adopts protocol CollectionType which ensures it as well
    • Nothing more generic/abstract?

  • It adopts protocol Indexable as well...
    • Yup, sounds like the best we can do. Can we then extend it to have this feature we want?

  • But we have very limited types (no Int) and properties (no count) to work with now!
    • It will be enough. Swift's stdlib is done pretty well ;)

    extension Indexable { public subscript(safe safeIndex: Index) -> _Element? { return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil } }

¹: not true, but it gives the idea

Solution 7 - Xcode

  • Because arrays may store nil values, it does not make sense to return a nil if an array[index] call is out of bounds.
  • Because we do not know how a user would like to handle out of bounds problems, it does not make sense to use custom operators.
  • In contrast, use traditional control flow for unwrapping objects and ensure type safety.

> if let index = array.checkIndexForSafety(index:Int)

> let item = array[safeIndex: index]

> if let index = array.checkIndexForSafety(index:Int)

> array[safeIndex: safeIndex] = myObject

extension Array {

    @warn_unused_result public func checkIndexForSafety(index: Int) -> SafeIndex? {

        if indices.contains(index) {

            // wrap index number in object, so can ensure type safety
            return SafeIndex(indexNumber: index)

        } else {
            return nil
        }
    }

    subscript(index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

    // second version of same subscript, but with different method signature, allowing user to highlight using safe index
    subscript(safeIndex index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

}

public class SafeIndex {

    var indexNumber:Int

    init(indexNumber:Int){
        self.indexNumber = indexNumber
    }
}

Solution 8 - Xcode

I realize this is an old question. I'm using Swift5.1 at this point, the OP was for Swift 1 or 2?

I needed something like this today, but I didn't want to add a full scale extension for just the one place and wanted something more functional (more thread safe?). I also didn't need to protect against negative indices, just those that might be past the end of an array:

let fruit = ["Apple", "Banana", "Coconut"]

let a = fruit.dropFirst(2).first // -> "Coconut"
let b = fruit.dropFirst(0).first // -> "Apple"
let c = fruit.dropFirst(10).first // -> nil

For those arguing about Sequences with nil's, what do you do about the first and last properties that return nil for empty collections?

I liked this because I could just grab at existing stuff and use it to get the result I wanted. I also know that dropFirst(n) is not a whole collection copy, just a slice. And then the already existent behavior of first takes over for me.

Solution 9 - Xcode

I found safe array get, set, insert, remove very useful. I prefer to log and ignore the errors as all else soon gets hard to manage. Full code bellow

/**
 Safe array get, set, insert and delete.
 All action that would cause an error are ignored.
 */
extension Array {

    /**
     Removes element at index.
     Action that would cause an error are ignored.
     */
    mutating func remove(safeAt index: Index) {
        guard index >= 0 && index < count else {
            print("Index out of bounds while deleting item at index \(index) in \(self). This action is ignored.")
            return
        }

        remove(at: index)
    }

    /**
     Inserts element at index.
     Action that would cause an error are ignored.
     */
    mutating func insert(_ element: Element, safeAt index: Index) {
        guard index >= 0 && index <= count else {
            print("Index out of bounds while inserting item at index \(index) in \(self). This action is ignored")
            return
        }

        insert(element, at: index)
    }

    /**
     Safe get set subscript.
     Action that would cause an error are ignored.
     */
    subscript (safe index: Index) -> Element? {
        get {
            return indices.contains(index) ? self[index] : nil
        }
        set {
            remove(safeAt: index)

            if let element = newValue {
                insert(element, safeAt: index)
            }
        }
    }
}

Tests

import XCTest

class SafeArrayTest: XCTestCase {
    func testRemove_Successful() {
        var array = [1, 2, 3]

        array.remove(safeAt: 1)

        XCTAssert(array == [1, 3])
    }

    func testRemove_Failure() {
        var array = [1, 2, 3]

        array.remove(safeAt: 3)

        XCTAssert(array == [1, 2, 3])
    }

    func testInsert_Successful() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 1)

        XCTAssert(array == [1, 4, 2, 3])
    }

    func testInsert_Successful_AtEnd() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 3)

        XCTAssert(array == [1, 2, 3, 4])
    }

    func testInsert_Failure() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 5)

        XCTAssert(array == [1, 2, 3])
    }

    func testGet_Successful() {
        var array = [1, 2, 3]

        let element = array[safe: 1]

        XCTAssert(element == 2)
    }

    func testGet_Failure() {
        var array = [1, 2, 3]

        let element = array[safe: 4]

        XCTAssert(element == nil)
    }

    func testSet_Successful() {
        var array = [1, 2, 3]

        array[safe: 1] = 4

        XCTAssert(array == [1, 4, 3])
    }

    func testSet_Successful_AtEnd() {
        var array = [1, 2, 3]

        array[safe: 3] = 4

        XCTAssert(array == [1, 2, 3, 4])
    }

    func testSet_Failure() {
        var array = [1, 2, 3]

        array[safe: 4] = 4

        XCTAssert(array == [1, 2, 3])
    }
}

Solution 10 - Xcode

extension Array {
  subscript (safe index: UInt) -> Element? {
    return Int(index) < count ? self[Int(index)] : nil
  }
}

Using Above mention extension return nil if anytime index goes out of bound.

let fruits = ["apple","banana"]
print("result-\(fruits[safe : 2])")

result - nil

Solution 11 - Xcode

I have padded the array with nils in my use case:

let components = [1, 2]
var nilComponents = components.map { $0 as Int? }
nilComponents += [nil, nil, nil]

switch (nilComponents[0], nilComponents[1], nilComponents[2]) {
case (_, _, .Some(5)):
    // process last component with 5
default:
    break
}

Also check the subscript extension with safe: label by Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/

Solution 12 - Xcode

The "Commonly Rejected Changes" for Swift list contains a mention of changing Array subscript access to return an optional rather than crashing:

> Make Array<T> subscript access return T? or T! instead of T: The current array behavior is intentional, as it accurately reflects the fact that out-of-bounds array access is a logic error. Changing the current behavior would slow Array accesses to an unacceptable degree. This topic has come up multiple times before but is very unlikely to be accepted. > > https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md#strings-characters-and-collection-types

So the basic subscript access will not be changing to return an optional.

However, the Swift team/community does seem open to adding a new optional-returning access pattern to Arrays, either via a function or subscript.

This has been proposed and discussed on the Swift Evolution forum here:

https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871

Notably, Chris Lattner gave the idea a "+1":

> Agreed, the most frequently suggested spelling for this is: yourArray[safe: idx], which seems great to me. I am very +1 for adding this. > > https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871/13

So this may be possible out of the box in some future version of Swift. I'd encourage anyone who wants it to contribute to that Swift Evolution thread.

Solution 13 - Xcode

To propagate why operations fail, errors are better than optionals. Subscripts can't throw errors, so it's got to be a method.

public extension Collection {
  /// - Returns: same as subscript, if index is in bounds
  /// - Throws: CollectionIndexingError
  func element(at index: Index) throws -> Element {
    guard indices.contains(index)
    else { throw CollectionIndexingError() }

    return self[index]
  }
}

/// Thrown when `element(at:)` is called with an invalid index.
public struct CollectionIndexingError: Error { }
XCTAssertThrowsError( try ["🐾", "🥝"].element(at: 2) )

let optionals = [1, 2, nil]
XCTAssertEqual(try optionals.element(at: 0), 1)

XCTAssertThrowsError( try optionals.element(at: optionals.endIndex) )
{ XCTAssert($0 is CollectionIndexingError) }

Solution 14 - Xcode

Not sure why no one, has put up an extension that also has a setter to automatically grow the array

extension Array where Element: ExpressibleByNilLiteral {
	public subscript(safe index: Int) -> Element? {
		get {
			guard index >= 0, index < endIndex else {
				return nil
			}

			return self[index]
		}
		
		set(newValue) {
			if index >= endIndex {
				self.append(contentsOf: Array(repeating: nil, count: index - endIndex + 1))
			}

			self[index] = newValue ?? nil
		}
	}
}

Usage is easy and works as of Swift 5.1

var arr:[String?] = ["A","B","C"]

print(arr) // Output: [Optional("A"), Optional("B"), Optional("C")]

arr[safe:10] = "Z"

print(arr) // [Optional("A"), Optional("B"), Optional("C"), nil, nil, nil, nil, nil, nil, nil, Optional("Z")]

Note: You should understand the performance cost (both in time/space) when growing an array in swift - but for small problems sometimes you just need to get Swift to stop Swifting itself in the foot

Solution 15 - Xcode

Swift 5.x

An extension on RandomAccessCollection means that this can also work for ArraySlice from a single implementation. We use startIndex and endIndex as array slices use the indexes from the underlying parent Array.

public extension RandomAccessCollection {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    /// - complexity: O(1)
    subscript (safe index: Index) -> Element? {
        guard index >= startIndex, index < endIndex else {
            return nil
        }
        return self[index]
    }
    
}

Solution 16 - Xcode

I think this is not a good idea. It seems preferable to build solid code that does not result in trying to apply out-of-bounds indexes.

Please consider that having such an error fail silently (as suggested by your code above) by returning nil is prone to producing even more complex, more intractable errors.

You could do your override in a similar fashion you used and just write the subscripts in your own way. Only drawback is that existing code will not be compatible. I think to find a hook to override the generic x[i] (also without a text preprocessor as in C) will be challenging.

The closest I can think of is

// compile error:
if theIndex < str.count && let existing = str[theIndex]

EDIT: This actually works. One-liner!!

func ifInBounds(array: [AnyObject], idx: Int) -> AnyObject? {
    return idx < array.count ? array[idx] : nil
}

if let x: AnyObject = ifInBounds(swiftarray, 3) {
    println(x)
}
else {
    println("Out of bounds")
}

Solution 17 - Xcode

I have made a simple extension for array

extension Array where Iterator.Element : AnyObject {
    func iof (_ i : Int ) -> Iterator.Element? {
        if self.count > i {
            return self[i] as Iterator.Element
        }
        else {
            return nil
        }
    }
    
}

it works perfectly as designed

Example

   if let firstElemntToLoad = roots.iof(0)?.children?.iof(0)?.cNode, 

Solution 18 - Xcode

You can try

if index >= 0 && index < array.count {
    print(array[index]) 
}

Solution 19 - Xcode

To be honest I faced this issue too. And from performance point of view a Swift array should be able to throw. let x = try a[y] This would be nice and understandable.

Solution 20 - Xcode

When you only need to get values from an array and you don't mind a small performance penalty (i.e. if your collection isn't huge), there is a Dictionary-based alternative that doesn't involve (a too generic, for my taste) collection extension:

// Assuming you have a collection named array:
let safeArray = Dictionary(uniqueKeysWithValues: zip(0..., array))
let value = safeArray[index] ?? defaultValue;

Solution 21 - Xcode

Swift 5 Usage

extension WKNavigationType {
    var name : String {
        get {
            let names = ["linkAct","formSubm","backForw","reload","formRelo"]
            return names.indices.contains(self.rawValue) ? names[self.rawValue] : "other"
        }
    }
}

ended up with but really wanted to do generally like

[<collection>][<index>] ?? <default>

but as the collection is contextual I guess it's proper.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCraig OtisView Question on Stackoverflow
Solution 1 - XcodeNikita KukushkinView Answer on Stackoverflow
Solution 2 - XcodeAlex WayneView Answer on Stackoverflow
Solution 3 - XcodeSafeFastExpressiveView Answer on Stackoverflow
Solution 4 - XcodethetrutzView Answer on Stackoverflow
Solution 5 - XcodeMatjanView Answer on Stackoverflow
Solution 6 - XcodeDeFrenZView Answer on Stackoverflow
Solution 7 - XcodeM.S. ClineView Answer on Stackoverflow
Solution 8 - XcodeTravis GriggsView Answer on Stackoverflow
Solution 9 - XcodeIvan RepView Answer on Stackoverflow
Solution 10 - XcodePaul.VView Answer on Stackoverflow
Solution 11 - XcodeMarián ČernýView Answer on Stackoverflow
Solution 12 - XcodepkambView Answer on Stackoverflow
Solution 13 - XcodeJessyView Answer on Stackoverflow
Solution 14 - XcodeShaheen GhiassyView Answer on Stackoverflow
Solution 15 - XcodeBradley MackeyView Answer on Stackoverflow
Solution 16 - XcodeMundiView Answer on Stackoverflow
Solution 17 - XcodeMohamed DeuxView Answer on Stackoverflow
Solution 18 - XcodeJabbarView Answer on Stackoverflow
Solution 19 - XcodeJürgen SauerzapfView Answer on Stackoverflow
Solution 20 - XcodeSorin DolhaView Answer on Stackoverflow
Solution 21 - XcodeslashlosView Answer on Stackoverflow