Note2 min read

When to use a safe collection subscript

The extension turns an out-of-bounds crash into nil. That is useful when the index comes from outside your own code, and hides a logic error when it does not.

The extension is four lines, and most Swift codebases have it somewhere:

extension Collection {
  subscript(safe index: Index) -> Element? {
    indices.contains(index) ? self[index] : nil
  }
}
let names = ["John", "Mike"]

names[2]        // crash
names[safe: 2]  // nil

An out-of-bounds subscript is one of the few things in Swift that terminates the process, and this converts it into an optional that if let can handle.

The tradeoff is where the failure surfaces. names[2] on a two-element array means some earlier code produced an index that does not exist. Swift traps at that subscript, so the offending index and the call stack that produced it are both in front of you. With [safe:] you get nil, the branch does nothing, and the visible symptom is an empty row or a total that is short by one — somewhere else, later.

So the question is whether you know the index is valid. If you computed it from count, or you are iterating indices, you do, and the optional adds a branch for a case that cannot happen. If the index came from elsewhere, you do not:

  • an index parsed from a payload or a URL
  • a selection captured before a reload and used after it
  • an index passed back by a framework callback for a collection you have since mutated

In all three the collection and the index come from different code paths, which is the thing to check for.

One implementation detail. indices.contains(index) is a range check on Array, where indices is a Range<Int> and contains is O(1). On a collection that is not random-access it resolves to Sequence.contains, which walks from the start index — so someString[safe: i] is O(n) in the length of the string, behind something that reads like a subscript. Constraining the extension to RandomAccessCollection avoids that, at the cost of not having it on String.

Use it where the index is untrusted. Elsewhere, the trap is more useful than the optional.

Topics