TL;DR
Your code is accessing a Vec or slice at an index that doesn't exist. The fastest safe fix is swapping vec[i] for vec.get(i), which returns an Option instead of blowing up.
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 5'
// This panics whenever i >= vec.len()
let val = vec[i];
// This never panics โ handle the None case yourself
if let Some(val) = vec.get(i) {
println!("Got: {}", val);
} else {
eprintln!("Index {} out of bounds (len = {})", i, vec.len());
}
Root Cause
Rust's [] operator on Vec and slices is strict by design. Out-of-range access? The runtime panics. No undefined behavior, no silent memory corruption like in C โ just an immediate, loud crash.
A few situations that reliably trigger this:
- Hard-coded index that worked in dev but breaks on real data (classic)
- Off-by-one in a loop โ
0..vec.len()is fine,0..=vec.len()goes one past the end - Computing an index from user input or config without validating it first
- Slicing a
Vecafter filtering or truncating it, but forgetting to update the old index - Multi-threaded code where another thread shrinks the
Vecbetween your index calculation and your access
The panic message is actually pretty helpful here:
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 5'
len is 3 means valid indices are 0, 1, 2. You tried 5. Two slots past the end.
Fix Approaches
Option 1: Use .get() for safe access
Nine times out of ten, this is what you want in production. .get(i) returns Option<&T> โ Some(&val) when in bounds, None when not. No panic, ever.
fn print_element(data: &[i32], index: usize) {
match data.get(index) {
Some(val) => println!("data[{}] = {}", index, val),
None => eprintln!("ERROR: index {} out of bounds (len = {})", index, data.len()),
}
}
This compiles on any Vec<T> or &[T] slice โ no changes needed depending on which you're using.
Option 2: Explicit bounds check before indexing
Sometimes you genuinely need [] โ mutable access, clarity, whatever. Just guard it:
let data = vec![10, 20, 30];
let i = 5;
if i Result {
data.get(index)
.copied()
.ok_or_else(|| format!("index {} out of bounds (len = {})", index, data.len()))
}
fn main() {
let data = vec![1, 2, 3];
match get_element(&data, 5) {
Ok(val) => println!("Got {}", val),
Err(e) => eprintln!("Error: {}", e),
}
}
Finding where the panic comes from
Before you can fix it, you need to find it. Set RUST_BACKTRACE=1 and rerun:
RUST_BACKTRACE=1 cargo run
For even more detail โ including inlined frames that sometimes hide the real culprit:
RUST_BACKTRACE=full cargo run
Scan the backtrace for your own crate name. Skip the std:: and core:: frames at the top โ those are Rust internals. Your bug is in the frames below them.
Slice ranges panic too
Worth knowing: index out of bounds also fires when slicing with an out-of-range end:
let v = vec![1, 2, 3];
let slice = &v[1..10]; // panics โ 10 > len (3)
// Clamp the end to a valid range first
let end = 10.min(v.len());
let slice = &v[1..end]; // safe
Verification
Run it again after your fix โ no panic output means you got it:
# Clean run
cargo run
# Full test suite
cargo test
Add a regression test so this doesn't sneak back in:
#[test]
fn test_out_of_bounds_handled() {
let data = vec![1, 2, 3];
assert!(data.get(5).is_none()); // out of bounds โ None, not panic
assert_eq!(data.get(2), Some(&3)); // last valid index โ Some
}
For deep validation โ especially in unsafe code โ Miri can catch memory issues that tests miss:
cargo +nightly miri test
Further Reading
- Rust Reference โ
SliceIndextrait and panicking behavior Vec::getandslice::getin the standard library docs- The Rustonomicon โ memory safety guarantees and why Rust panics instead of UB
RUST_BACKTRACEenvironment variable documentation

