This introduces a very interesting addition data point I had missed: ?? [] is a significant performance and correctness trap.
Not because [] creates an array unnecessarily (it doesn't, the empty array is a static singleton in the standard library via a performance hack that gives me heartburn).
It's because when the array isn't nil, the presence of ?? [] affects the type checker in ways you don't expect:
let maybeHugeRange: Range<Int>? = 0..<Int.max
if let hugeRange = maybeHugeRange {
for i in hugeRange.reversed() { // fine
print(i) // prints Int.max
break // and then breaks
}
}
for i in maybeHugeRange?.reversed() ?? [] { // doom occurs here
print(i) // your app will jetsam without ever reaching here
break
}
What's happening here? Well, Range is a collection that cannot be created via []. It's a very low-cost collection: it just stores the upper and lower bound, and iteration is just incrementing from one to the other. On bidirectional collections, reversed() just returns a lightweight wrapper over the base collection, that forwards calls on to the base collection but reversing the direction of traversal. So in this case, a ReversedCollection<Range<Int>>, just 16 bytes in size. It is also not creatable via []. All nice and efficient.
So what does maybeHugeRange?.reversed() ?? [] do? The ReversedCollection answer won't type check, because the rhs of ?? can't be one. So instead it falls back to the version on forward-only collections. That returns an array. So now, just because of that ?? [], we are attempting to allocate and fill an array of size Int.max. Which blows up.
Obviously this is an extreme example that crashes. But in a more likely case, if the optional reversed collection is only of modest size but rarely nil, you could be causing huge numbers of array allocations without realizing it.