- All posts
- When to use a safe collection subscript
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.