Valid and invalid indexes
The items of an array are numbered from 0 to count - 1. Those are the array's bounds. Any other index is invalid, and using one is the most common mistake when working with arrays.
let ourArray = [50, 40, 30]
// Valid indexes: 0, 1, 2
print(ourArray[0]) // 50
print(ourArray[2]) // 30
Indexes 3, 30 and -1 are all invalid for this array: 3 and 30 are past the end, and -1 is before the start. Swift does not wrap around or count from the end the way some languages do with negative numbers.
What happens
Reading or writing an invalid index stops the program immediately with a fatal error, usually reported as "Index out of range". It is not an exception you can catch with do and catch. Swift deliberately halts, because continuing with a wrong index could read or overwrite unrelated memory. Anything the program would have printed after that line never runs.
Because the crash happens at run time, the compiler can't warn you in general. It doesn't know what a variable will hold:
let ages = [14, 32, 50, 21]
let position = ages.count
print(ages[position]) // crashes: valid indexes are 0 to 3
The exercise: fix the crash
The starter code is meant to print the last age, 21:
let ages = [14, 32, 50, 21]
print(ages[ages.count])
It crashes. Here is why, step by step.
- Find the count.
ages.countis 4, because the array has four elements. - Find the valid indexes. With four elements they are 0, 1, 2 and 3.
- Compare. The code reads index 4, one past the last element. That is outside the bounds.
- Fix the index. The last element is at
count - 1, so subtract 1.
let ages = [14, 32, 50, 21]
print(ages[ages.count - 1])
The program now prints 21.
Staying inside the bounds
When the index comes from a variable, test it before using it:
let index = 4
if index >= 0 && index < ages.count {
print(ages[index])
} else {
print("out of range") // this branch runs
}
ages.indices.contains(index) does the same check in one expression. For the first or last element, the optional properties first and last are safe: they give nil for an empty array instead of crashing. When you walk through all elements, use for i in 0..<ages.count, whose upper bound excludes the count, or for age in ages, which needs no index.
Reading a crash report
A crash like this is easy to fix once you know where to look. The failing line is the one that contains a subscript, so start with the number between the brackets and work out its value. If it comes from a variable or a calculation, evaluate it by hand with the array's real size. Then compare the result with the range 0 to count - 1. In most cases the index is off by one, or the array is shorter than the code assumed, for example because it is empty or because items were removed earlier.
Common mistakes
- Using
countas the last index. It is one too many. The last index iscount - 1. - Looping up to the count inclusive.
0...ages.countincludes the invalid index 4. Use0..<ages.count. - Using -1 for "the last item". Swift has no negative indexing.
- Reading from an empty array. There,
count - 1is -1, which is invalid, and so is index 0. - Writing past the end. Assigning to
ages[4]crashes too. It doesn't extend the array.
Key takeaways
- Valid indexes run from 0 to
count - 1. - An invalid index crashes the program at run time.
- The last element is
array[array.count - 1]. - Check an index that comes from a variable before using it.
