Rust - Arrays

Overview

Estimated time: 45–60 minutes

Master Rust's fixed-size array type for compile-time known collections. Learn initialization patterns, safe access methods, and when to choose arrays over vectors.

Learning Objectives

Prerequisites

What are Arrays?

Arrays in Rust are fixed-size collections where the size is known at compile time. Unlike vectors, arrays are allocated on the stack and have a fixed length that cannot change.

Array Type Syntax


fn main() {
    // Array of 5 integers
    let numbers: [i32; 5] = [1, 2, 3, 4, 5];
    
    // Array of 3 strings
    let words: [&str; 3] = ["hello", "world", "rust"];
    
    // Array with repeated values
    let zeros: [i32; 10] = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    
    println!("Numbers: {:?}", numbers);
    println!("Words: {:?}", words);
    println!("Zeros: {:?}", zeros);
}

Expected output:

Numbers: [1, 2, 3, 4, 5]
Words: ["hello", "world", "rust"]
Zeros: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Array Initialization Patterns

Direct Initialization


fn main() {
    // Explicit type annotation
    let explicit: [i32; 4] = [10, 20, 30, 40];
    
    // Type inference
    let inferred = [1.1, 2.2, 3.3]; // [f64; 3]
    
    // Mixed initialization
    let mixed: [i32; 5] = [1, 2, 3, 4, 5];
    
    println!("Explicit: {:?}", explicit);
    println!("Inferred: {:?}", inferred);
    println!("Mixed: {:?}", mixed);
}

Repeated Value Initialization


fn main() {
    // All elements the same value
    let ones = [1; 8]; // [1, 1, 1, 1, 1, 1, 1, 1]
    let text = ["default"; 5]; // ["default", "default", "default", "default", "default"]
    
    // Useful for buffers
    let buffer: [u8; 1024] = [0; 1024];
    
    println!("Ones: {:?}", ones);
    println!("Text: {:?}", text);
    println!("Buffer length: {}", buffer.len());
}

Accessing Array Elements

Indexing with Bounds Checking


fn main() {
    let colors = ["red", "green", "blue", "yellow"];
    
    // Safe indexing - panics if out of bounds
    println!("First color: {}", colors[0]);
    println!("Last color: {}", colors[3]);
    
    // Using get() for safe access
    match colors.get(2) {
        Some(color) => println!("Third color: {}", color),
        None => println!("Index out of bounds"),
    }
    
    // Safe access to potentially invalid index
    match colors.get(10) {
        Some(color) => println!("Color at 10: {}", color),
        None => println!("No color at index 10"),
    }
}

Expected output:

First color: red
Last color: yellow
Third color: blue
No color at index 10

Mutable Arrays


fn main() {
    let mut scores = [85, 90, 78, 92, 88];
    
    println!("Original scores: {:?}", scores);
    
    // Modify individual elements
    scores[1] = 95;
    scores[4] = 90;
    
    println!("Updated scores: {:?}", scores);
    
    // Using get_mut for safe mutable access
    if let Some(score) = scores.get_mut(2) {
        *score += 5; // Bonus points
    }
    
    println!("Final scores: {:?}", scores);
}

Expected output:

Original scores: [85, 90, 78, 92, 88]
Updated scores: [85, 95, 78, 92, 90]
Final scores: [85, 95, 83, 92, 90]

Array Iteration

Basic Iteration Patterns


fn main() {
    let numbers = [1, 2, 3, 4, 5];
    
    // Iterate by value
    println!("By value:");
    for num in numbers {
        println!("  {}", num);
    }
    
    // Iterate by reference
    println!("By reference:");
    for num in &numbers {
        println!("  {}", num);
    }
    
    // Iterate with index
    println!("With index:");
    for (i, num) in numbers.iter().enumerate() {
        println!("  Index {}: {}", i, num);
    }
}

Iterator Methods


fn main() {
    let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // Filter and collect
    let evens: Vec = numbers.iter()
        .filter(|&&n| n % 2 == 0)
        .cloned()
        .collect();
    
    // Map and sum
    let sum_of_squares: i32 = numbers.iter()
        .map(|&n| n * n)
        .sum();
    
    // Find element
    let first_big = numbers.iter()
        .find(|&&n| n > 5);
    
    println!("Original: {:?}", numbers);
    println!("Evens: {:?}", evens);
    println!("Sum of squares: {}", sum_of_squares);
    println!("First > 5: {:?}", first_big);
}

Expected output:

Original: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Evens: [2, 4, 6, 8, 10]
Sum of squares: 385
First > 5: Some(6)

Array Slicing

Creating Slices from Arrays


fn main() {
    let data = [10, 20, 30, 40, 50, 60, 70, 80];
    
    // Full slice
    let full_slice: &[i32] = &data;
    
    // Partial slices
    let first_half = &data[0..4];
    let last_half = &data[4..];
    let middle = &data[2..6];
    
    println!("Full array: {:?}", data);
    println!("Full slice: {:?}", full_slice);
    println!("First half: {:?}", first_half);
    println!("Last half: {:?}", last_half);
    println!("Middle: {:?}", middle);
    
    // Slice methods work on arrays
    println!("Contains 30: {}", data.contains(&30));
    println!("Starts with [10, 20]: {}", data.starts_with(&[10, 20]));
}

Multi-dimensional Arrays

2D Arrays


fn main() {
    // 3x3 matrix
    let matrix: [[i32; 3]; 3] = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9],
    ];
    
    // Access elements
    println!("Element at (1,2): {}", matrix[1][2]);
    
    // Iterate through matrix
    println!("Matrix:");
    for row in &matrix {
        for &element in row {
            print!("{:3}", element);
        }
        println!();
    }
    
    // Initialize with same value
    let zeros: [[i32; 4]; 3] = [[0; 4]; 3];
    println!("Zeros matrix: {:?}", zeros);
}

Expected output:

Element at (1,2): 6
Matrix:
  1  2  3
  4  5  6
  7  8  9
Zeros matrix: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

Arrays vs Vectors

When to Use Arrays


fn main() {
    // Arrays: Fixed size, stack allocated, compile-time known size
    let coordinates: [f64; 3] = [1.0, 2.0, 3.0]; // 3D coordinate
    let rgb: [u8; 3] = [255, 128, 0]; // Color values
    let weekdays: [&str; 7] = [
        "Monday", "Tuesday", "Wednesday", "Thursday", 
        "Friday", "Saturday", "Sunday"
    ];
    
    // Vectors: Dynamic size, heap allocated, runtime flexibility
    let mut dynamic_list = Vec::new();
    dynamic_list.push(1);
    dynamic_list.push(2);
    
    println!("Coordinate: {:?}", coordinates);
    println!("RGB color: {:?}", rgb);
    println!("Weekdays: {:?}", weekdays);
    println!("Dynamic list: {:?}", dynamic_list);
    
    // Arrays have fixed size known at compile time
    println!("Array size is known: {}", coordinates.len());
    // This would not compile: coordinates.push(4.0);
}

Performance Characteristics

Memory Layout and Performance


use std::mem;

fn main() {
    let array: [i32; 5] = [1, 2, 3, 4, 5];
    let vector: Vec = vec![1, 2, 3, 4, 5];
    
    println!("Array size: {} bytes", mem::size_of_val(&array));
    println!("Vector size: {} bytes", mem::size_of_val(&vector));
    
    // Array elements are contiguous in memory
    println!("Array memory layout:");
    for (i, element) in array.iter().enumerate() {
        println!("  Element {}: {:p}", i, element);
    }
    
    // Arrays are always on the stack
    println!("Array is on stack: {}", is_on_stack(&array));
}

fn is_on_stack(_item: &T) -> bool {
    // This is a simplified check - in real code, determining
    // stack vs heap allocation is more complex
    true // Arrays are always on stack
}

Common Pitfalls

Array Size Mismatch


fn main() {
    // This will not compile - size mismatch
    // let wrong: [i32; 3] = [1, 2, 3, 4, 5];
    
    // Correct way
    let correct: [i32; 5] = [1, 2, 3, 4, 5];
    
    // Arrays of different sizes are different types
    let small: [i32; 3] = [1, 2, 3];
    let large: [i32; 5] = [1, 2, 3, 4, 5];
    
    // This won't compile - different types
    // let mixed = if true { small } else { large };
    
    println!("Small: {:?}", small);
    println!("Large: {:?}", large);
}

Index Out of Bounds


fn main() {
    let data = [1, 2, 3];
    
    // This will panic at runtime
    // println!("{}", data[5]);
    
    // Safe alternative
    match data.get(5) {
        Some(value) => println!("Value: {}", value),
        None => println!("Index 5 is out of bounds"),
    }
    
    // Or use unwrap_or for default values
    let value = data.get(5).unwrap_or(&-1);
    println!("Value at index 5 (or default): {}", value);
}

Real-World Examples

Coordinate System


#[derive(Debug)]
struct Point3D {
    coords: [f64; 3],
}

impl Point3D {
    fn new(x: f64, y: f64, z: f64) -> Self {
        Point3D {
            coords: [x, y, z],
        }
    }
    
    fn distance_from_origin(&self) -> f64 {
        self.coords.iter()
            .map(|&c| c * c)
            .sum::()
            .sqrt()
    }
    
    fn add(&self, other: &Point3D) -> Point3D {
        let mut result = [0.0; 3];
        for i in 0..3 {
            result[i] = self.coords[i] + other.coords[i];
        }
        Point3D { coords: result }
    }
}

fn main() {
    let p1 = Point3D::new(1.0, 2.0, 3.0);
    let p2 = Point3D::new(4.0, 5.0, 6.0);
    
    println!("Point 1: {:?}", p1);
    println!("Distance from origin: {:.2}", p1.distance_from_origin());
    
    let sum = p1.add(&p2);
    println!("Sum: {:?}", sum);
}

Game Board


#[derive(Debug, Clone, Copy, PartialEq)]
enum Cell {
    Empty,
    X,
    O,
}

struct TicTacToe {
    board: [[Cell; 3]; 3],
}

impl TicTacToe {
    fn new() -> Self {
        TicTacToe {
            board: [[Cell::Empty; 3]; 3],
        }
    }
    
    fn make_move(&mut self, row: usize, col: usize, player: Cell) -> bool {
        if row < 3 && col < 3 && self.board[row][col] == Cell::Empty {
            self.board[row][col] = player;
            true
        } else {
            false
        }
    }
    
    fn display(&self) {
        for row in &self.board {
            for &cell in row {
                let symbol = match cell {
                    Cell::Empty => ".",
                    Cell::X => "X",
                    Cell::O => "O",
                };
                print!("{} ", symbol);
            }
            println!();
        }
    }
    
    fn check_winner(&self) -> Option {
        // Check rows
        for row in &self.board {
            if row[0] != Cell::Empty && row[0] == row[1] && row[1] == row[2] {
                return Some(row[0]);
            }
        }
        
        // Check columns
        for col in 0..3 {
            if self.board[0][col] != Cell::Empty &&
               self.board[0][col] == self.board[1][col] &&
               self.board[1][col] == self.board[2][col] {
                return Some(self.board[0][col]);
            }
        }
        
        // Check diagonals
        if self.board[0][0] != Cell::Empty &&
           self.board[0][0] == self.board[1][1] &&
           self.board[1][1] == self.board[2][2] {
            return Some(self.board[0][0]);
        }
        
        if self.board[0][2] != Cell::Empty &&
           self.board[0][2] == self.board[1][1] &&
           self.board[1][1] == self.board[2][0] {
            return Some(self.board[0][2]);
        }
        
        None
    }
}

fn main() {
    let mut game = TicTacToe::new();
    
    game.make_move(0, 0, Cell::X);
    game.make_move(1, 1, Cell::O);
    game.make_move(0, 1, Cell::X);
    game.make_move(1, 0, Cell::O);
    game.make_move(0, 2, Cell::X);
    
    println!("Game board:");
    game.display();
    
    match game.check_winner() {
        Some(Cell::X) => println!("X wins!"),
        Some(Cell::O) => println!("O wins!"),
        _ => println!("Game continues..."),
    }
}

Expected output:

Game board:
X X X 
O O . 
. . . 
X wins!

Best Practices

Choosing Array Size

Safe Access Patterns

Checks for Understanding

Question 1: Array Initialization

Q: How would you create an array of 100 zeros of type f32?

Click to see answer

A: let zeros: [f32; 100] = [0.0; 100];

The syntax [value; size] creates an array where all elements are initialized to the same value.

Question 2: Safe Access

Q: What's the difference between array[index] and array.get(index)?

Click to see answer

A: array[index] panics if index is out of bounds, while array.get(index) returns Option<T> - Some(value) for valid indices or None for invalid ones.

Question 3: Type System

Q: Are [i32; 3] and [i32; 5] the same type?

Click to see answer

A: No, they are different types. Array types include both the element type and the size, so [i32; 3] and [i32; 5] are distinct types that cannot be used interchangeably.


← PreviousNext →