RustArrays

Arrays in Rust

An array is a fixed-size collection of elements that all have the same type. Unlike a Vec, an array's length is known at compile time and the data lives on the stack, not the heap.

Declaring Arrays

The array type is written as [T; N] where T is the element type and N is the number of elements. Both are fixed parts of the type — [i32; 5] and [i32; 6] are two distinct types.

RUST
fn main() {
    // Explicit type annotation
    let a: [i32; 5] = [1, 2, 3, 4, 5];

    // Inferred type (compiler figures out [i32; 4])
    let b = [10, 20, 30, 40];

    println!("{:?}", a);
    println!("{:?}", b);
}
Repeat Initializer

Use [value; N] to create an array where every element starts with the same value. This is the most concise way to zero-initialize or fill an array.

RUST
fn main() {
    let zeros = [0; 5];     // [0, 0, 0, 0, 0]
    let ones  = [1u8; 8];   // [1, 1, 1, 1, 1, 1, 1, 1]
    let trues = [true; 3];  // [true, true, true]

    println!("{:?}", zeros);
    println!("{:?}", ones);
    println!("{:?}", trues);
}
Note
The repeat initializer requires the element type to implement `Copy`. Since primitive types like `i32`, `bool`, and `u8` all implement `Copy`, this works seamlessly for them.
Accessing Elements

Array elements are accessed with zero-based index notation a[index]. Rust always performs bounds checking at runtime — accessing an out-of-bounds index causes a panic rather than undefined behaviour.

RUST
fn main() {
    let a = [10, 20, 30, 40, 50];

    println!("first: {}", a[0]);
    println!("last:  {}", a[4]);
    println!("len:   {}", a.len());
}
Warning
Accessing an out-of-bounds index panics: `a[10]` on a length-5 array will crash with "index out of bounds: the len is 5 but the index is 10". Use `.get(i)` to receive an `Option<&T>` instead of panicking.

RUST
fn main() {
    let a = [1, 2, 3];

    // Safe access — returns None instead of panicking
    match a.get(5) {
        Some(val) => println!("value: {}", val),
        None      => println!("index out of bounds"),
    }
}
Arrays Live on the Stack

Because the size of an array is known at compile time, the compiler reserves exactly the right amount of stack space. No heap allocation occurs. This makes arrays very fast to create and access, but also means they cannot grow or shrink at runtime.

Note
Very large arrays (millions of elements) can overflow the stack. For large collections, use `Vec<T>` which allocates on the heap.
Array Length

.len() returns the number of elements. Because the length is part of the type, the compiler knows it at compile time — .len() is a zero-cost method that reads a constant baked in during compilation.

RUST
fn main() {
    let a = [1, 2, 3, 4, 5];
    println!("length: {}", a.len()); // 5

    // Length can be a compile-time constant
    const N: usize = 5;
    let b: [i32; N] = [0; N];
    println!("b has {} elements", b.len());
}
Iterating Over Arrays

You can iterate over an array with a for loop. Use for x in &a to borrow elements, or for x in a to consume (move or copy) them.

RUST
fn main() {
    let a = [10, 20, 30, 40, 50];

    // Borrowing iteration — 'a' is still usable after the loop
    for x in &a {
        print!("{} ", x);
    }
    println!();

    // Moving / copying iteration (works safely for Copy types)
    for x in a {
        print!("{} ", x);
    }
    println!();

    // Mutable iteration — modify elements in place
    let mut b = [1, 2, 3];
    for x in b.iter_mut() {
        *x *= 10;
    }
    println!("{:?}", b);
}
Tip
For arrays of `Copy` types, `for x in a` copies each element, so `a` remains valid afterward. For non-`Copy` types, the loop moves each element out and `a` is no longer usable.
The Copy Trait and Arrays

An array implements Copy if and only if its element type implements Copy. For Copy arrays, assignment duplicates the data on the stack rather than moving ownership.

RUST
fn main() {
    // [i32; 3] is Copy
    let a = [1, 2, 3];
    let b = a;           // a is copied, not moved
    println!("{:?}", a); // still valid
    println!("{:?}", b);

    // [String; 2] is NOT Copy — String is not Copy
    // let s = [String::from("x"), String::from("y")];
    // let t = s; // would move s, making it unusable
}
Sorting and Searching

RUST
fn main() {
    let mut nums = [5, 3, 8, 1, 9, 2];

    // In-place ascending sort
    nums.sort();
    println!("sorted: {:?}", nums);

    // Membership check
    println!("contains 8: {}", nums.contains(&8));

    // Binary search on a sorted array
    match nums.binary_search(&8) {
        Ok(i)  => println!("found 8 at index {}", i),
        Err(i) => println!("8 not found; insert position {}", i),
    }

    // Sort with a custom comparator (descending)
    nums.sort_by(|a, b| b.cmp(a));
    println!("descending: {:?}", nums);
}
2D Arrays

A two-dimensional array is an array of arrays. The type is written as [[T; cols]; rows]. Elements are stored in row-major order, which matches how most algorithms naturally iterate.

RUST
fn main() {
    // 3x3 matrix
    let matrix: [[i32; 3]; 3] = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9],
    ];

    // Access element at row 1, col 2
    println!("matrix[1][2] = {}", matrix[1][2]); // 6

    // Iterate over all elements
    for row in &matrix {
        for val in row {
            print!("{:3}", val);
        }
        println!();
    }
}
Slices from Arrays

You can borrow a portion of an array as a slice (&[T]) without copying the data. This is how arrays and slice-based APIs interoperate seamlessly.

RUST
fn print_slice(s: &[i32]) {
    println!("{:?}", s);
}

fn main() {
    let a = [1, 2, 3, 4, 5];

    let full   = &a[..];   // whole array as &[i32]
    let middle = &a[1..4]; // [2, 3, 4]

    print_slice(full);
    print_slice(middle);

    // Arrays coerce to &[T] automatically in function calls
    print_slice(&a);
}
Success
Arrays automatically coerce to `&[T]` when passed where a slice is expected. This means any function that accepts `&[T]` works with both arrays and `Vec<T>` — write slice-based APIs for maximum flexibility.
Array vs Vec

Feature

Array [T; N]

Vec Vec<T>

Size

Fixed at compile time

Dynamic at runtime

Storage

Stack

Heap

Allocation

None

Heap allocation

Resizable

No

Yes (push, pop, extend)

Performance

Slightly faster — no indirection

Slight overhead for heap pointer

Best for

Small, known-size collections

Unknown or variable-size data

When to Use Arrays vs `Vec`
  • Use an array when the number of elements is known at compile time and will never change.

  • Use a Vec when you need to add or remove elements at runtime, or when the size is only known at runtime.

  • Arrays are ideal for fixed-size buffers, lookup tables, day-of-week arrays, RGB triplets, and similar.

  • If unsure, start with a Vec — it is more flexible and the performance difference is usually negligible.

Practical Examples

RUST
fn main() {
    // Day name lookup table
    let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
    println!("Day 3 is {}", days[2]); // Wed

    // Compute the average of a fixed dataset
    let temps: [f64; 7] = [22.1, 19.5, 25.3, 21.0, 18.7, 23.4, 20.8];
    let avg = temps.iter().sum::<f64>() / temps.len() as f64;
    println!("Average temperature: {:.1}", avg);

    // Count even numbers
    let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let even_count = nums.iter().filter(|&&x| x % 2 == 0).count();
    println!("Even numbers: {}", even_count);
}
Summary
  • Arrays have type [T; N] — element type and length are both fixed at compile time.

  • Use [value; N] to initialize every element with the same value.

  • Array data lives on the stack — no heap allocation, very fast for small fixed-size collections.

  • Out-of-bounds access panics at runtime; use .get(i) for safe Option-returning access.

  • Arrays implement Copy when their element type implements Copy.

  • Use &a[start..end] to borrow a sub-range as a &[T] slice.

  • Arrays coerce to &[T] automatically, so slice-based functions accept both arrays and Vecs.

  • Choose arrays for fixed compile-time sizes; choose Vec for dynamic runtime sizes.