Rust and Data Structures

Arrays and Slices

Arrays ([T; N]) and slices ([T], accessed via &[T] or &mut [T]) are Rust's foundational contiguous sequence types. Every element i sits at exact byte offset i * size_of::<T>() from the base address, giving $O(1)$ random access, zero per-element pointer overhead, and maximum CPU cache locality.

Fixed-Size Array `[u32; 4]` (16 Bytes Inline on Stack or inside Struct):
  +----------+----------+----------+----------+
  |   10     |   20     |   30     |   40     |
  +----------+----------+----------+----------+
  Offset 0   Offset 4   Offset 8   Offset 12

Borrowed Slice `&[u32]` (16-Byte Fat Pointer referencing any contiguous `[u32]`):
  +---------------------+---------------------+
  | data_ptr (*const T) | len: usize (e.g. 2) |
  +---------------------+---------------------+
             |
             +-----------> Points to `[20, 30]` sub-range above!

Comparing [T; N], &[T], and Box<[T]>

Type Size Known at Compile Time? Where Elements Live Stack Footprint (64-bit) Can Length Change?
[T; N] Yes (N is a const usize) Inline (stack or parent struct) N * size_of::<T>() No
&[T] / &mut [T] No (Unsized DST [T] behind ref) Borrowed from [T; N], Vec<T>, etc. 16 bytes (ptr + len) Re-sliceable (s = &s[1..])
Box<[T]> No Owned on the heap 16 bytes (ptr + len, drops cap word of Vec<T>) No (saves 8 bytes vs Vec<T>)

Splitting Mutable Slices Safely Without Aliasing (split_at_mut)

Because the borrow checker tracks borrows at the whole-slice variable level, writing &mut slice[0] and &mut slice[1] simultaneously is rejected as two mutable borrows of slice.

Standard library slice methods—such as split_at_mut(mid), split_first_mut(), chunks_exact_mut(k), and as_chunks_mut::<N>()—encapsulate the single internal unsafe pointer split and return disjoint mutable sub-slices that can be mutated concurrently across threads or passed to functions together:

use std::mem::size_of;

/// Performs an in-place partition step or parallel half-transform using `split_at_mut`.
pub fn mirror_add_halves(data: &mut [i32]) {
    let mid = data.len() / 2;
    // Splits `data` into two non-overlapping `&mut [i32]` slices safely!
    let (left, right) = data.split_at_mut(mid);

    for (l, r) in left.iter_mut().zip(right.iter_mut().rev()) {
        let sum = *l + *r;
        *l = sum;
        *r = sum;
    }
}

fn main() {
    let mut values: [i32; 6] = [1, 2, 3, 10, 20, 30];
    mirror_add_halves(&mut values);
    assert_eq!(values, [31, 22, 13, 13, 22, 31]);

    // Converting a finalized Vec<T> into Box<[T]> shrinks its header from 24B to 16B:
    let boxed_slice: Box<[i32]> = Vec::from(values).into_boxed_slice();
    assert_eq!(size_of::<Vec<i32>>(), 24);
    assert_eq!(size_of::<Box<[i32]>>(), 16);

    println!("Mirrored array: {values:?}, boxed slice len={}", boxed_slice.len());
}
Mirrored array: [31, 22, 13, 13, 22, 31], boxed slice len=6

High-Speed Algorithmic Operations Built into [T]

  • slice.binary_search(&target): $O(\log n)$ search on sorted slices returning Ok(index) or Err(insertion_index).
  • slice.partition_point(|x| pred(x)): $O(\log n)$ binary search for the exact boundary index where a monotonic predicate transitions from true to false.
  • slice.select_nth_unstable(k): $O(n)$ Quickselect (Introselect) algorithm that finds the $k$-th smallest element and partitions the slice around it in linear time without sorting the entire slice!
  • slice.rotate_left(k) / rotate_right(k): $O(n)$ in-place block rotation using $O(1)$ auxiliary memory.