Skip to content

Rust - Array

In this chapter, we will learn about Rust’s primary collection types for sequences of data. We’ll start with the array, a simple, fixed-size collection, and then introduce more flexible and commonly used types: vectors and slices.

A simple variable can only hold one value at a time. To store a list of values, like the top 10 scores in a game, we need a data structure that can hold multiple values. Arrays are the most basic way to do this.

An array in Rust is a collection of multiple values of the same type (T) with a fixed length (N).

  • Fixed Size: Once an array is declared, its size can never be changed. This is a key difference from more flexible types like Vectors.
  • Homogeneous: All elements in an array must have the same data type.
  • Stack Allocated: Arrays are allocated on the stack, which makes them very fast to access. This is ideal when you know the exact number of elements you need at compile time.
  • Sequential Memory: The elements of an array are stored next to each other in memory, allowing for efficient, predictable access.
  • Indexed Access: Elements are accessed by their index, starting from 0.

Here are the common ways to declare and initialize an array in Rust.

// Syntax 1: Type inference
// The compiler infers the type and size.
let my_array = [1, 2, 3, 4, 5];
// Syntax 2: Explicit type and size annotation
// [type; size]
let my_array_typed: [i32; 5] = [1, 2, 3, 4, 5];
// Syntax 3: Initialize all elements to the same value
// [default_value; size]
let all_zeros = [0; 10]; // Creates an array of 10 zeros.

This example shows how to create an array, access an element, and get its length. We use the debug format {:?} to print the entire array.

fn main() {
// Declare an array of 4 integers.
let numbers: [i32; 4] = [10, 20, 30, 40];
// Print the entire array.
println!("Array: {:?}", numbers);
// Get the length of the array.
println!("Array size: {}", numbers.len());
// Access a single element by index.
let first_element = numbers[0];
println!("The first element is: {}", first_element);
}
Array: [10, 20, 30, 40]
Array size: 4
The first element is: 10

It’s common to perform an operation on every element in an array. Idiomatic Rust uses iterators for this, which are safer and more expressive than manual index-based loops.

The .iter() method creates an iterator that lets you loop over each element. To get both the index and the value, use .enumerate().

fn main() {
let weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
println!("Iterating over values:");
for day in weekdays.iter() {
println!("Day: {}", day);
}
println!("\nIterating with index and value:");
for (index, day) in weekdays.iter().enumerate() {
println!("Day {}: {}", index + 1, day);
}
}
Iterating over values:
Day: Monday
Day: Tuesday
Day: Wednesday
Day: Thursday
Day: Friday
Iterating with index and value:
Day 1: Monday
Day 2: Tuesday
Day 3: Wednesday
Day 4: Thursday
Day 5: Friday

By default, variables in Rust are immutable. To modify an array’s elements, you must declare it with the mut keyword.

fn main() {
let mut scores: [i32; 5] = [88, 92, 79, 100, 95];
println!("Original scores: {:?}", scores);
// Update the score at index 2
scores[2] = 85;
println!("Updated scores: {:?}", scores);
}
Original scores: [88, 92, 79, 100, 95]
Updated scores: [88, 92, 85, 100, 95]

Arrays are useful, but their fixed size is limiting. For most real-world scenarios, you’ll use Vectors and Slices.

  • Vector (Vec<T>): A growable, resizable list. It’s stored on the heap. This is the type you’ll use most often when you need a list of items.
  • Slice (&[T]): A ‘view’ or ‘reference’ into a contiguous sequence of elements. Slices allow you to safely and efficiently access a portion of an array or a vector without copying data. They are incredibly powerful for writing flexible functions.

Instead of writing a function that only accepts an array of a specific size (like [i32; 3]), it’s a best practice to accept a slice (&[i32]). This makes your function more versatile, as it can now work with arrays of any size, and also with vectors.

// This function takes a slice of integers, making it very flexible.
fn sum_slice(numbers: &[i32]) -> i32 {
let mut total = 0;
for &num in numbers {
total += num;
}
total
}
fn main() {
let arr1: [i32; 5] = [1, 2, 3, 4, 5];
let arr2: [i32; 3] = [10, 20, 30];
let vec1: Vec<i32> = vec![100, 200, 300];
// We can pass a reference to the whole array, which is coerced into a slice.
println!("Sum of arr1: {}", sum_slice(&arr1));
// Or a slice of a portion of the array.
println!("Sum of part of arr1: {}", sum_slice(&arr1[1..4])); // Elements at index 1, 2, 3
// The same function works for arr2!
println!("Sum of arr2: {}", sum_slice(&arr2));
// And it works for vectors too!
println!("Sum of vec1: {}", sum_slice(&vec1));
}
Sum of arr1: 15
Sum of part of arr1: 9
Sum of arr2: 60
Sum of vec1: 600
  • Out-of-Bounds Access: Accessing an array with an index that doesn’t exist will cause your program to panic. For example, numbers[10] on an array of size 5. Always check lengths or use iterators to avoid this.
  • Choosing the Right Type: Use an array [T; N] when you know the number of elements will not change and you want stack performance. Use a vector Vec<T> for everything else. Use slices &[T] in function arguments to make them more flexible.
  • Constants for Size: An array’s size must be known at compile time. If you need to use a named value for the size, it must be a const.
fn main() {
const ARRAY_SIZE: usize = 20;
// This is valid because ARRAY_SIZE is a constant.
let my_array = [0; ARRAY_SIZE];
println!("Element 10 is {}", my_array[10]);
}