rustseverity: can-fix
index out of bounds: the len is N but the index is M

Rust index out of bounds — the len is N but the index is M

index out of bounds: len is N, index is M

97% fixable~10 mindifficulty: beginner

Verified against The Rust Reference: Index expressions, Rust std docs: slice::get, The Rust Book: Chapter 8 (Common Collections) · Updated June 2026

> quick_fix

You accessed a Vec or slice at an index >= its length. Use `.get(index)` which returns `Option<&T>` instead of panicking. Always check bounds before direct indexing, or use iterators which handle bounds automatically.

let v = vec![1, 2, 3];

// Panics if index >= v.len()
let x = v[5];  // thread 'main' panicked: index out of bounds: the len is 3 but the index is 5

// Safe — returns Option<&T>
match v.get(5) {
    Some(x) => println!("got: {}", x),
    None    => println!("index 5 is out of bounds"),
}

What causes this error

Rust's `[]` operator on slices and Vecs performs a bounds check at runtime and panics if the index is out of range. Unlike C/C++ which allow undefined behavior (buffer overflow), Rust always panics cleanly. Common causes: off-by-one in index calculation, assuming a Vec has a certain length without checking, using a hardcoded index on data with variable length, or accessing `last()` element via length minus one on an empty collection.

> advertisementAdSense placeholder

How to fix it

  1. 01

    step 1

    Use .get() for safe access that returns Option

    `.get(index)` returns `Some(&T)` if the index is valid, `None` if out of bounds. Never panics. Use it whenever the index might be invalid.

    let items: Vec<String> = get_items();
    
    // Safe access
    if let Some(first) = items.get(0) {
        println!("First item: {}", first);
    } else {
        println!("No items");
    }
    
    // With a default
    let first = items.get(0).map(|s| s.as_str()).unwrap_or("default");
  2. 02

    step 2

    Use iterators to avoid indexing entirely

    Most operations that use indexing can be rewritten with iterators (`iter()`, `enumerate()`, `zip()`, `windows()`, `chunks()`). Iterators never go out of bounds.

    let numbers = vec![10, 20, 30, 40, 50];
    
    // BAD — manual indexing risks off-by-one
    for i in 0..=numbers.len() {  // off-by-one: should be 0..numbers.len()
        println!("{}", numbers[i]);
    }
    
    // GOOD — iterator, no indexing
    for n in &numbers {
        println!("{}", n);
    }
    
    // With index
    for (i, n) in numbers.iter().enumerate() {
        println!("[{}] = {}", i, n);
    }
  3. 03

    step 3

    Check length before indexing

    When you need direct indexing, check the length first. Rust's bounds check will still catch bugs at runtime, but explicit checks make the intent clear and allow graceful error handling.

    fn get_third(items: &[i32]) -> Option<i32> {
        if items.len() < 3 {
            return None;
        }
        Some(items[2])  // safe — we've verified len >= 3
    }
    
    // Or use first(), last(), split_first(), split_last()
    let v = vec![1, 2, 3];
    if let Some(&last) = v.last() {
        println!("last: {}", last);  // never panics on empty
    }
  4. 04

    step 4

    Use .first() and .last() instead of indexing for edge elements

    `.first()` and `.last()` return `Option<&T>` and never panic on empty slices. Prefer them over `v[0]` and `v[v.len()-1]`.

    let v: Vec<i32> = vec![];
    
    // BAD — panics on empty Vec
    let first = v[0];            // panic!
    let last = v[v.len() - 1];   // panic! (len() is 0, 0-1 wraps to usize::MAX)
    
    // GOOD
    let first = v.first();  // None
    let last = v.last();    // None

How to verify the fix

  • No direct `v[i]` indexing where `i` is not verified to be < `v.len()`.
  • All index access uses `.get()` or iterators in code handling user input or variable-length data.
  • `cargo test` passes all test cases including empty-input cases.

Why index out of bounds: the len is N but the index is M happens at the runtime level

Rust's `Index` trait implementation for slices calls `slice::index` which performs a runtime bounds check: if `index >= self.len()`, it calls `panic!` with the specific index and length. This is deliberate — Rust's safety guarantee means no undefined behavior in safe code. Buffer overflows (the C equivalent) allow arbitrary memory reads/writes; Rust's bounds check prevents this class of vulnerability entirely. In release builds, bounds checks are retained (unlike some other languages' 'checked only in debug' approach). The check is one comparison instruction — the performance overhead is negligible for most workloads.

Common debug mistakes for index out of bounds: the len is N but the index is M

  • Using `v.len() - 1` as an index without checking that `v` is non-empty — panics with usize underflow when empty.
  • Splitting a slice with `split_at(n)` where n > slice.len() — panics.
  • Index calculation using i32 arithmetic then casting to usize — negative values cast to huge usize values.
  • Off-by-one: `0..=v.len()` in a range instead of `0..v.len()` — the last iteration accesses v[v.len()].
  • Accessing a 2D Vec as `v[row][col]` where col >= `v[row].len()` — each inner Vec may have a different length.

When index out of bounds: the len is N but the index is M signals a deeper problem

Persistent index-out-of-bounds panics in Rust code processing user data indicate that the data shape assumptions are embedded in the code rather than validated at the input boundary. A CSV parser that assumes column 5 exists in every row will panic on malformed input. The architectural fix is a typed parsing layer at the input boundary: define a struct with named fields, deserialize into it with `serde`, and let serde validate the shape. If a row is missing a column, serde returns an error that propagates cleanly via `?`. Zero direct indexing in the application logic means zero index-out-of-bounds panics from data shape mismatches.

Editor's take

The index-out-of-bounds panic in Rust is almost always a sign that the code was written thinking in C or Python, where you routinely use integer indices. Idiomatic Rust almost never uses direct indexing — it uses iterators, adapters, and the `.get()` method for anything that might fail. The transformation from index-based to iterator-based code is usually straightforward and produces cleaner, more readable code as a side effect.

The subtle version that trips up Rust developers specifically: `usize` underflow when computing `len - 1` on an empty collection. In C, you'd segfault or get garbage. In Python, you'd get -1 and wrap around (also wrong). In Rust debug mode, you get a clear panic: 'attempt to subtract with overflow'. In release mode with overflow-checks disabled (which isn't the default), you silently get `usize::MAX` — an enormous index that panics immediately on the next access. Rust's default of panicking on integer overflow in debug mode catches this class of bug early.

The pattern I see most in production Rust code reviews: accessing the 'first' and 'last' elements of a collection via `v[0]` and `v[v.len()-1]`. These both panic on empty collections. The idiomatic Rust is `v.first()` and `v.last()`, which return `Option` and compose cleanly with `?` or `unwrap_or`. This is a one-line change that makes the code correct for all inputs, not just non-empty ones. A clippy lint (`clippy::get_first`) flags `v.get(0)` but not `v[0]` directly — so the review has to catch the direct indexing case manually.

By Bikram Nath · Curator · Updated June 2026

Frequently asked questions

Does Rust compile-time catch index out of bounds?

Only for constant indices on fixed-size arrays: `let a = [1, 2, 3]; let x = a[5];` is a compile error. For Vec and slice with runtime indices, bounds checking happens at runtime with a panic. The compiler performs bounds-check elimination (BCE) for indices it can statically prove are in range — for example, `for i in 0..v.len() { v[i] }`. Use `cargo rustc -- -Z mir-opt-level=3` to see BCE in MIR output.

What is unsafe indexing and when should I use it?

`slice.get_unchecked(index)` skips the bounds check. It's an `unsafe` operation — if the index is out of bounds, the behavior is undefined (memory corruption, not a panic). Use it only in performance-critical hot loops where you've proven the index is valid. Never use it in application code. Profile first — the overhead of bounds checks is typically negligible compared to cache misses and branch mispredictions.

Why does subtracting from usize panic with 'attempt to subtract with overflow'?

In Rust debug builds, integer arithmetic panics on overflow. `v.len() - 1` panics when `v.len()` is 0 because usize (an unsigned integer) can't represent -1 — it wraps to `usize::MAX` in release mode, causing an enormous index. Use `v.last()` instead of `v[v.len()-1]`. For safe subtraction, use `v.len().checked_sub(1)` which returns `Option<usize>`.

disclosure:Errordex runs AdSense, has zero third-party affiliate or sponsored links, and occasionally links to the editor’s own paid digital products (clearly labelled). Every fix is cross-referenced against the official sources listed in the “sources” sidebar before it ships. If a fix here didn’t work for you, please email so we can update the page.