Valid and invalid indexes
A vector with n elements numbers them 0 to n - 1. Those are the valid indexes. Every other index is invalid: n is one past the end, and so is everything larger. Exceeding the bounds is the most common mistake when reading vectors by index, and it is worth understanding exactly what Rust does when it happens.
What Rust does
Rust checks the index on every v[i] access. If the index is valid, you get the element. If it isn't, the program panics: it prints an error message and stops. It does not return garbage, a default value or a silent None.
let our_array = vec![50, 40, 30];
println!("{}", our_array[0]);
println!("{}", our_array[2]);
50
30
Indexing with our_array[3] or our_array[30] would panic with a message of the form index out of bounds: the len is 3 but the index is 3 (or 30).
A panic is a runtime failure, not a compile error. The compiler builds the program happily, because the length of a Vec is only known when the program runs. Negative indexes are different: since an index must be a usize, a negative literal such as our_array[-1] is rejected at compile time.
How to read the message
The panic text has two numbers, and both are useful. "The len" is how many elements the vector had at that moment. "The index" is the position you asked for. If the index equals the length, you are exactly one past the end, which points to an off-by-one mistake. If the length is 0, the vector was empty. If the index is far larger than the length, a calculation upstream produced the wrong position. The message also gives the file and line of the access.
Typical causes
- Using
len()as the index.v[v.len()]always panics. The last element is atv.len() - 1. - Loop conditions that go one step too far. A
while i <= v.len()loop reads indexv.len()on its last turn. Usei < v.len(), or iterate withfor x in &v, which cannot go out of range. - Empty vectors. Index 0 is invalid when nothing is in the vector, and
v.len() - 1underflows becauseusizecan't hold -1. - A vector that shrank. Calling
popreduces the length, so an index that was valid earlier may not be valid now.
Writing code that stays in bounds
Three habits keep indexing safe. Prefer iterating over the elements directly when you don't need the position. Compare an index with len() before using it when you do. And use get(i), which returns Some(&value) for a valid index and None otherwise, when you want to handle the missing case yourself instead of crashing.
let v = vec![50, 40, 30];
let mut sum = 0;
let mut i = 0;
while i < v.len() {
sum += v[i];
i += 1;
}
println!("{}", sum);
120
The condition i < v.len() stops before the invalid index, so the loop reads indexes 0, 1 and 2 only.
The lesson's exercise
The program is supposed to print the last element of ages, which is 21. Instead it panics:
let ages = vec![14, 32, 50, 21];
println!("{}", ages[ages.len()]);
The vector has four elements, so ages.len() is 4, but the valid indexes are 0 to 3. The code asks for index 4, and the panic message says exactly that: the len is 4 but the index is 4. The last element is one position earlier, at ages.len() - 1:
let ages = vec![14, 32, 50, 21];
println!("{}", ages[ages.len() - 1]);
This prints 21. Unlike a hard-coded 3, the expression keeps working if the vector gets longer or shorter, as long as it isn't empty.
Key takeaways
- Valid indexes for a vector of length
nare0ton - 1. - An invalid index makes the program panic with a message giving the length and the index.
- The last element is
v[v.len() - 1], provided the vector isn't empty. - Use
getor a length check when the index might be invalid.
