- All posts
- When to use a safe collection subscript
When to use a safe collection subscript
Should an out-of-bounds index crash, or come back nil? It turns on whether you produced the index or someone else did — plus one performance trap on collections that are not arrays.
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 whose indices use Sequence.contains, membership requires a
linear scan. Restricting the extension to RandomAccessCollection does not
by itself guarantee a constant-time indices.contains implementation. Check
the concrete collection if this subscript sits on a hot path.
Use it where the index is untrusted. Elsewhere, the trap is more useful than the optional.
Where do you draw the line between returning nil and letting an invalid
index fail? Send me an example from your own code. If this
distinction comes up in a code review, share this note with the team.