Array Types
An array ([T; N]) is a fixed-size collection of N elements of the exact same type T stored contiguously inline in memory. Because an array's length N is part of its compile-time type signature, arrays live directly on the stack with zero heap allocation overhead.
Array Type and Literal Syntax
Rust provides two ways to write an array literal:
- Comma-separated list
[e1, e2, ..., eN]: Lists each element explicitly. - Repeat expression
[value; N]: Creates an array of lengthNfilled withNcopies ofvalue(wherevalueimplementsCopyor is aconstexpression).
The type annotation for an array is written [ElementType; Length], using a semicolon (;) between the type and the compile-time usize length:
fn main() {
// 1. Explicit element list with type annotation `[i32; 5]`
let primes: [i32; 5] = [2, 3, 5, 7, 11];
// 2. Repeat expression: 8 zeros of type `u8` (`[u8; 8]`)
let buffer = [0_u8; 8];
// 3. Inferred element type and length (`[&str; 3]`)
let days = ["Mon", "Tue", "Wed"];
println!("primes: {primes:?}");
println!("buffer: {buffer:?}");
println!("days: {days:?}");
}
primes: [2, 3, 5, 7, 11]
buffer: [0, 0, 0, 0, 0, 0, 0, 0]
days: ["Mon", "Tue", "Wed"]
Initializing Non-Copy Arrays (std::array::from_fn and const)
In [value; N], if value is a runtime variable of a non-Copy type (such as String), the compiler cannot copy it N times implicitly. However, you can either use an inline const expression (such as [const { String::new() }; 4]) or compute each element from its index 0..N using std::array::from_fn:
fn main() {
let empty_strings: [String; 3] = [const { String::new() }; 3];
let labels: [String; 4] = std::array::from_fn(|i| format!("item_{i}"));
println!("empty_strings: {empty_strings:?}");
println!("labels: {labels:?}");
}
empty_strings: ["", "", ""]
labels: ["item_0", "item_1", "item_2", "item_3"]
Memory Layout (size_of::<T>() * N)
An array [T; N] is a single flat block of size_of::<T>() * N bytes with no length header, capacity field, or pointer indirection:
Stack Layout of `let a: [i32; 4] = [10, 20, 30, 40];` (16 bytes total):
+-------------+-------------+-------------+-------------+
| 10 | 20 | 30 | 40 |
| (4 bytes) | (4 bytes) | (4 bytes) | (4 bytes) |
+-------------+-------------+-------------+-------------+
a[0] a[1] a[2] a[3]
Because N is part of the type itself, [i32; 3] and [i32; 4] are two completely distinct, incompatible types. A function expecting [i32; 3] will reject [i32; 4] (to accept arrays of any length, take a slice &[i32] or use a const generic parameter fn process<const N: usize>(arr: [i32; N])).
Indexing and Runtime Bounds Checking
Array elements are accessed with 0-based usize indices inside square brackets (arr[index]):
arr[index]: Returns the element atindex, or panics at runtime ifindex >= arr.len(). (Ifindexis a compile-time constant out of bounds,rustccatches it at compile time!)arr.get(index): Safely returnsSome(&element)ifindex < arr.len(), orNoneifindexis out of bounds, without ever panicking.
fn main() {
let mut scores = [85, 90, 95];
// Read and modify via `usize` index
scores[1] = 92;
println!("First: {}, Second: {}, Len: {}", scores[0], scores[1], scores.len());
// Safe bounds-checked access with `.get()`
println!("scores.get(2): {:?}", scores.get(2));
println!("scores.get(99): {:?}", scores.get(99));
}
First: 85, Second: 92, Len: 3
scores.get(2): Some(95)
scores.get(99): None
Compile-Time vs Runtime Out-of-Bounds Detection
When a literal index is out of bounds, rustc refuses to compile the program (unconditional_panic):
// ❌ does not compile
fn main() {
let data = [10, 20, 30];
println!("{}", data[5]);
}
error: this operation will panic at runtime
--> src/main.rs:3:20
|
3 | println!("{}", data[5]);
| ^^^^^^^ index out of bounds: the length is 3 but the index is 5
|
= note: `#[deny(unconditional_panic)]` on by default
error: aborting due to 1 previous error
If the index comes from a runtime variable and exceeds len - 1, Rust panics cleanly at runtime instead of reading out-of-bounds memory as C would:
thread 'main' panicked at src/main.rs:4:20:
index out of bounds: the len is 3 but the index is 5
Useful Array and Slice Methods
Arrays automatically coerce to slices (&[T] and &mut [T]), giving every array access to all slice methods as well as native value-transforming array methods (.map(), .each_ref()):
| Method | Example on arr = [3, 1, 2] |
Result / Behavior |
|---|---|---|
arr.len() |
arr.len() |
3 (number of elements) |
arr.is_empty() |
arr.is_empty() |
false (true only for [T; 0]) |
arr.first() / arr.last() |
arr.first() |
Some(&3) / Some(&2) |
arr.contains(&x) |
arr.contains(&1) |
true |
arr.map(f) |
arr.map(|x| x * 10) |
[30, 10, 20] (transforms [T; N] into [U; N] on the stack!) |
arr.each_ref() |
arr.each_ref() |
[&3, &1, &2] ([&T; N]) |
arr.sort() |
arr.sort() |
Sorts elements in place ([1, 2, 3]) |
arr.swap(i, j) |
arr.swap(0, 2) |
Swaps arr[i] and arr[j] in place |
fn main() {
let numbers: [i32; 4] = [1, 2, 3, 4];
// `.map()` transforms a fixed-size array `[i32; 4]` directly into `[i32; 4]` without heap allocation!
let squares: [i32; 4] = numbers.map(|n| n * n);
println!("numbers: {numbers:?}");
println!("squares: {squares:?}");
}
numbers: [1, 2, 3, 4]
squares: [1, 4, 9, 16]
Multidimensional Arrays
A 2D array in Rust is an array whose elements are themselves arrays: [[ElementType; Columns]; Rows]. Note that in the type signature [[T; COLS]; ROWS], the inner array length (COLS) comes first and the outer array length (ROWS) comes second:
fn main() {
// 2 rows, each containing 3 `i32` columns -> type is `[[i32; 3]; 2]`
let grid: [[i32; 3]; 2] = [
[10, 20, 30],
[40, 50, 60],
];
println!("Row 0, Col 2 = {}", grid[0][2]);
println!("Row 1, Col 1 = {}", grid[1][1]);
}
Row 0, Col 2 = 30
Row 1, Col 1 = 50