Introduction to Rust Arrays
Arrays in Rust are fixed-size collections of elements of the same type. They are stack-allocated and provide fast, contiguous memory access. Unlike vectors, arrays have a fixed length known at compile time, making them ideal for scenarios where you know the exact number of elements you need.
Key Concepts
- Fixed Size: Length is part of the type and known at compile time
- Stack Allocated: Stored on the stack, not heap
- Homogeneous: All elements must be the same type
- Zero-Cost: No runtime overhead compared to C arrays
- Type Safety: Bounds checking at runtime (in debug builds)
1. Array Basics
Declaration and Initialization
fn main() {
// Explicit type annotation
let arr1: [i32; 5] = [1, 2, 3, 4, 5];
// Type inference
let arr2 = [1, 2, 3, 4, 5];
// Array with default values
let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let threes = [3; 5]; // [3, 3, 3, 3, 3]
// Mutable array
let mut mutable_arr = [1, 2, 3, 4, 5];
mutable_arr[0] = 10;
println!("arr1: {:?}", arr1);
println!("zeros: {:?}", zeros);
println!("mutable_arr: {:?}", mutable_arr);
// Array with different initialization patterns
let squares = [1, 4, 9, 16, 25];
let evens = [2, 4, 6, 8, 10];
}
Array Type Signature
fn main() {
// Array type is [T; N] where T is element type and N is length
let arr: [i32; 5] = [1, 2, 3, 4, 5];
// Different sizes are different types
let arr3: [i32; 3] = [1, 2, 3];
let arr5: [i32; 5] = [1, 2, 3, 4, 5];
// arr3 = arr5; // Error: mismatched types
// Size is part of the type
println!("Size of [i32; 5]: {} bytes", std::mem::size_of::<[i32; 5]>());
println!("Size of [i32; 10]: {} bytes", std::mem::size_of::<[i32; 10]>());
// Arrays implement common traits
fn print_array<T: std::fmt::Debug>(arr: &[T]) {
println!("{:?}", arr);
}
print_array(&arr);
}
2. Accessing Array Elements
Indexing
fn main() {
let arr = [10, 20, 30, 40, 50];
// Direct indexing
let first = arr[0];
let third = arr[2];
let last = arr[4];
println!("First: {}, Third: {}, Last: {}", first, third, last);
// Bounds checking (panics in debug, wraps in release)
// let out_of_bounds = arr[10]; // This would panic
// Safe access with get()
match arr.get(2) {
Some(value) => println!("Element at index 2: {}", value),
None => println!("Index out of bounds"),
}
match arr.get(10) {
Some(value) => println!("Element at index 10: {}", value),
None => println!("Index 10 out of bounds"), // This prints
}
// Get with default
let value = arr.get(2).unwrap_or(&-1);
println!("Value: {}", value);
// First and last elements
if let Some(first) = arr.first() {
println!("First: {}", first);
}
if let Some(last) = arr.last() {
println!("Last: {}", last);
}
}
Slicing Arrays
fn main() {
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Full slice
let full_slice = &arr[..];
println!("Full slice: {:?}", full_slice);
// Partial slices
let slice1 = &arr[2..5]; // [3, 4, 5]
let slice2 = &arr[..4]; // [1, 2, 3, 4]
let slice3 = &arr[6..]; // [7, 8, 9, 10]
let slice4 = &arr[2..=5]; // [3, 4, 5, 6] (inclusive)
println!("slice1: {:?}", slice1);
println!("slice2: {:?}", slice2);
println!("slice3: {:?}", slice3);
println!("slice4: {:?}", slice4);
// Mutable slices
let mut mut_arr = [1, 2, 3, 4, 5];
let mut_slice = &mut mut_arr[1..4];
mut_slice[0] = 20;
mut_slice[1] = 30;
mut_slice[2] = 40;
println!("Modified array: {:?}", mut_arr); // [1, 20, 30, 40, 5]
// Slice from mutable array
let slice = &mut mut_arr[..];
slice[0] = 100;
println!("All modified: {:?}", mut_arr); // [100, 20, 30, 40, 5]
}
3. Array Iteration
Basic Iteration
fn main() {
let arr = [10, 20, 30, 40, 50];
// For loop over references (doesn't consume)
for element in &arr {
println!("Element: {}", element);
}
// For loop over values (consumes if not Copy)
for element in arr {
println!("Element: {}", element);
}
// println!("{:?}", arr); // Still works because i32 is Copy
// Iterate with index
for i in 0..arr.len() {
println!("arr[{}] = {}", i, arr[i]);
}
// Using iter() method
for element in arr.iter() {
println!("iter(): {}", element);
}
// Mutable iteration
let mut mut_arr = [1, 2, 3, 4, 5];
for element in mut_arr.iter_mut() {
*element *= 2;
}
println!("After doubling: {:?}", mut_arr);
}
Advanced Iteration Techniques
fn main() {
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Enumerate - get index and value
for (i, value) in arr.iter().enumerate() {
println!("arr[{}] = {}", i, value);
}
// Filter
let evens: Vec<_> = arr.iter()
.filter(|&&x| x % 2 == 0)
.collect();
println!("Evens: {:?}", evens);
// Map
let doubled: Vec<_> = arr.iter()
.map(|&x| x * 2)
.collect();
println!("Doubled: {:?}", doubled);
// Fold (reduce)
let sum = arr.iter().fold(0, |acc, &x| acc + x);
println!("Sum: {}", sum);
// Any and All
let has_negative = arr.iter().any(|&x| x < 0);
let all_positive = arr.iter().all(|&x| x > 0);
println!("Has negative: {}, All positive: {}", has_negative, all_positive);
// Find
if let Some(value) = arr.iter().find(|&&x| x == 5) {
println!("Found: {}", value);
}
// Position
if let Some(pos) = arr.iter().position(|&x| x == 7) {
println!("7 found at index: {}", pos);
}
}
4. Array Methods and Operations
Basic Methods
fn main() {
let arr = [1, 2, 3, 4, 5];
// Length
println!("Length: {}", arr.len());
// Is empty
println!("Is empty: {}", arr.is_empty());
// Check if contains
println!("Contains 3: {}", arr.contains(&3));
println!("Contains 10: {}", arr.contains(&10));
// First and last
println!("First: {:?}", arr.first());
println!("Last: {:?}", arr.last());
// Get specific element
println!("Element at 2: {:?}", arr.get(2));
println!("Element at 10: {:?}", arr.get(10));
// Split array
let (left, right) = arr.split_at(3);
println!("Left: {:?}, Right: {:?}", left, right);
// Windows
for window in arr.windows(3) {
println!("Window: {:?}", window);
}
// Chunks
for chunk in arr.chunks(2) {
println!("Chunk: {:?}", chunk);
}
// Chunks with remainder
for chunk in arr.chunks(3) {
println!("Chunk of 3: {:?}", chunk);
}
}
Searching and Sorting
fn main() {
let mut arr = [5, 2, 8, 1, 9, 3, 7, 4, 6];
// Sort
arr.sort();
println!("Sorted: {:?}", arr);
// Sort in reverse
arr.sort_by(|a, b| b.cmp(a));
println!("Reverse sorted: {:?}", arr);
// Binary search (requires sorted array)
arr.sort();
match arr.binary_search(&5) {
Ok(index) => println!("Found 5 at index {}", index),
Err(index) => println!("5 not found, could be inserted at {}", index),
}
// Search with custom comparator
let mut pairs = [(1, "one"), (3, "three"), (2, "two")];
pairs.sort_by(|a, b| a.0.cmp(&b.0));
println!("Sorted pairs: {:?}", pairs);
// Check if sorted
println!("Is sorted: {}", arr.is_sorted());
// Dedup (remove consecutive duplicates)
let mut dup_arr = [1, 1, 2, 2, 3, 3, 4];
dup_arr.sort();
dup_arr.dedup();
println!("After dedup: {:?}", dup_arr);
}
Array Transformations
fn main() {
let arr = [1, 2, 3, 4, 5];
// Reverse
let reversed: Vec<_> = arr.iter().rev().collect();
println!("Reversed: {:?}", reversed);
// Rotate left
let rotate_left = [&arr[1..], &arr[..1]].concat();
println!("Rotate left: {:?}", rotate_left);
// Rotate right
let rotate_right = [&arr[arr.len()-1..], &arr[..arr.len()-1]].concat();
println!("Rotate right: {:?}", rotate_right);
// Fill with value
let mut fill_arr = [0; 5];
fill_arr.fill(42);
println!("Filled: {:?}", fill_arr);
// Fill with range
for (i, val) in fill_arr.iter_mut().enumerate() {
*val = i * 10;
}
println!("Range fill: {:?}", fill_arr);
// Map to new array (using const generics)
fn map_array<T, U, const N: usize>(arr: [T; N], mut f: impl FnMut(T) -> U) -> [U; N] {
let mut result: [std::mem::MaybeUninit<U>; N] = unsafe {
std::mem::MaybeUninit::uninit().assume_init()
};
for (i, x) in arr.into_iter().enumerate() {
result[i].write(f(x));
}
unsafe { std::mem::transmute_copy(&result) }
}
let doubled = map_array(arr, |x| x * 2);
println!("Doubled array: {:?}", doubled);
}
5. Multidimensional Arrays
2D Arrays
fn main() {
// 2D array declaration
let matrix: [[i32; 3]; 3] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
// Access elements
println!("matrix[1][1] = {}", matrix[1][1]); // 5
// Iterate over 2D array
for row in &matrix {
for element in row {
print!("{} ", element);
}
println!();
}
// Mutable 2D array
let mut mut_matrix = [[0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
mut_matrix[i][j] = (i * 3 + j + 1) as i32;
}
}
println!("Mut matrix: {:?}", mut_matrix);
// Jagged arrays (using Vec for different lengths)
let jagged: [Vec<i32>; 3] = [
vec![1, 2],
vec![3, 4, 5],
vec![6, 7, 8, 9],
];
for (i, row) in jagged.iter().enumerate() {
println!("Row {}: {:?}", i, row);
}
}
3D Arrays
fn main() {
// 3D array
let cube: [[[i32; 2]; 2]; 2] = [
[[1, 2], [3, 4]],
[[5, 6], [7, 8]],
];
println!("cube[0][0][0] = {}", cube[0][0][0]); // 1
println!("cube[1][1][1] = {}", cube[1][1][1]); // 8
// Iterate through 3D
for (i, layer) in cube.iter().enumerate() {
println!("Layer {}:", i);
for (j, row) in layer.iter().enumerate() {
print!(" Row {}: ", j);
for (k, &val) in row.iter().enumerate() {
print!("{} ", val);
}
println!();
}
}
// Create 3D array dynamically
let mut dynamic_cube = [[[0; 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
dynamic_cube[i][j][k] = (i * 9 + j * 3 + k) as i32;
}
}
}
println!("Dynamic cube: {:?}", dynamic_cube);
}
Matrix Operations
fn main() {
// Matrix addition
let a = [[1, 2], [3, 4]];
let b = [[5, 6], [7, 8]];
let mut c = [[0; 2]; 2];
for i in 0..2 {
for j in 0..2 {
c[i][j] = a[i][j] + b[i][j];
}
}
println!("Matrix A + B = {:?}", c);
// Matrix multiplication
let a = [[1, 2], [3, 4]];
let b = [[5, 6], [7, 8]];
let mut d = [[0; 2]; 2];
for i in 0..2 {
for j in 0..2 {
for k in 0..2 {
d[i][j] += a[i][k] * b[k][j];
}
}
}
println!("Matrix A * B = {:?}", d);
// Transpose
let mut transpose = [[0; 2]; 2];
for i in 0..2 {
for j in 0..2 {
transpose[j][i] = a[i][j];
}
}
println!("Transpose of A = {:?}", transpose);
}
6. Arrays and Functions
Passing Arrays to Functions
// Pass by value (copy for Copy types, move otherwise)
fn process_array(mut arr: [i32; 5]) -> [i32; 5] {
for i in 0..arr.len() {
arr[i] *= 2;
}
arr
}
// Pass by reference (immutable)
fn print_array(arr: &[i32; 5]) {
println!("Array: {:?}", arr);
}
// Pass by mutable reference
fn modify_array(arr: &mut [i32; 5]) {
for i in 0..arr.len() {
arr[i] += 10;
}
}
// Pass slice (most flexible for functions)
fn sum_slice(arr: &[i32]) -> i32 {
arr.iter().sum()
}
// Generic array function
fn print_any_array<T: std::fmt::Debug, const N: usize>(arr: &[T; N]) {
println!("Array of length {}: {:?}", N, arr);
}
fn main() {
let arr = [1, 2, 3, 4, 5];
// Pass by reference
print_array(&arr);
// Pass by value (returns modified copy)
let doubled = process_array(arr);
println!("Original: {:?}, Doubled: {:?}", arr, doubled);
// Pass by mutable reference
let mut mut_arr = [1, 2, 3, 4, 5];
modify_array(&mut mut_arr);
println!("Modified: {:?}", mut_arr);
// Pass as slice
let sum = sum_slice(&arr);
println!("Sum: {}", sum);
// Generic function
print_any_array(&arr);
print_any_array(&['a', 'b', 'c']);
}
Returning Arrays from Functions
// Return fixed-size array
fn create_array() -> [i32; 5] {
[1, 2, 3, 4, 5]
}
// Return array from computation
fn generate_squares(n: usize) -> [i32; 5] {
let mut arr = [0; 5];
for i in 0..5 {
arr[i] = ((i + 1) * (i + 1)) as i32;
}
arr
}
// Using const generics to return arrays of any size
fn fill_array<const N: usize>(value: i32) -> [i32; N] {
[value; N]
} // Return array from Option fn maybe_array(flag: bool) -> Option<[i32; 3]> { if flag { Some([1, 2, 3]) } else { None } } fn main() { let arr1 = create_array(); println!("Created: {:?}", arr1); let squares = generate_squares(5); println!("Squares: {:?}", squares); let tens: [i32; 10] = fill_array(10); println!("Tens: {:?}", tens); match maybe_array(true) { Some(arr) => println!("Got array: {:?}", arr), None => println!("No array"), } }
7. Arrays and Generics
Const Generics
// Function that works with arrays of any size
fn sum_array<T: std::ops::Add<Output = T> + Default + Copy, const N: usize>(arr: [T; N]) -> T {
let mut sum = T::default();
for i in 0..N {
sum = sum + arr[i];
}
sum
}
// Generic array struct
struct ArrayWrapper<T, const N: usize> {
data: [T; N],
}
impl<T: std::fmt::Debug, const N: usize> ArrayWrapper<T, N> {
fn new(data: [T; N]) -> Self {
ArrayWrapper { data }
}
fn get(&self, index: usize) -> Option<&T> {
self.data.get(index)
}
fn len(&self) -> usize {
N
}
}
// Const generic with bounds
fn compare_arrays<T: PartialEq, const N: usize>(a: &[T; N], b: &[T; N]) -> bool {
a == b
}
fn main() {
let arr1 = [1, 2, 3, 4, 5];
let arr2 = [1, 2, 3, 4, 5];
let arr3 = [1, 2, 3, 4, 6];
println!("Sum of arr1: {}", sum_array(arr1));
let sum_float = sum_array([1.1, 2.2, 3.3]);
println!("Sum of floats: {}", sum_float);
let wrapper = ArrayWrapper::new([10, 20, 30, 40]);
println!("Wrapper length: {}", wrapper.len());
println!("Element at 2: {:?}", wrapper.get(2));
println!("arr1 == arr2: {}", compare_arrays(&arr1, &arr2));
println!("arr1 == arr3: {}", compare_arrays(&arr1, &arr3));
}
Arrays in Traits
trait ArrayOperations<T, const N: usize> {
fn sum(&self) -> T;
fn product(&self) -> T;
fn average(&self) -> f64;
}
impl<T, const N: usize> ArrayOperations<T, N> for [T; N]
where
T: std::ops::Add<Output = T> + std::ops::Mul<Output = T> + Default + Copy,
f64: From<T>,
{
fn sum(&self) -> T {
let mut sum = T::default();
for i in 0..N {
sum = sum + self[i];
}
sum
}
fn product(&self) -> T {
let mut product = self[0];
for i in 1..N {
product = product * self[i];
}
product
}
fn average(&self) -> f64 {
let sum: f64 = self.sum().into();
sum / (N as f64)
}
}
fn main() {
let numbers = [1, 2, 3, 4, 5];
println!("Sum: {}", numbers.sum());
println!("Product: {}", numbers.product());
println!("Average: {}", numbers.average());
let floats = [1.5, 2.5, 3.5, 4.5];
println!("Float sum: {}", floats.sum());
println!("Float average: {}", floats.average());
}
8. Array Performance
Stack vs Heap
fn main() {
// Stack allocation (fast)
let stack_array = [0; 1000];
// Heap allocation via Box (still need to copy)
let heap_array = Box::new([0; 1000]);
// Vector (heap allocated, dynamic)
let vec = vec![0; 1000];
println!("Stack array size: {} bytes", std::mem::size_of_val(&stack_array));
println!("Boxed array size: {} bytes", std::mem::size_of_val(&heap_array));
println!("Vector size: {} bytes", std::mem::size_of_val(&vec));
// Performance comparison
use std::time::Instant;
// Stack array access
let start = Instant::now();
let mut sum_stack = 0;
for i in 0..1000 {
sum_stack += stack_array[i];
}
println!("Stack access time: {:?}", start.elapsed());
// Heap array access
let start = Instant::now();
let mut sum_heap = 0;
for i in 0..1000 {
sum_heap += heap_array[i];
}
println!("Heap access time: {:?}", start.elapsed());
}
Cache Efficiency
fn main() {
// Cache-friendly sequential access
let arr = [0; 10000];
let start = std::time::Instant::now();
for i in 0..10000 {
let _ = arr[i];
}
println!("Sequential access: {:?}", start.elapsed());
// Cache-unfriendly random access (simulated)
let start = std::time::Instant::now();
for i in 0..10000 {
let idx = (i * 997) % 10000; // Non-sequential pattern
let _ = arr[idx];
}
println!("Random access: {:?}", start.elapsed());
// Row-major vs column-major for 2D
let matrix = [[0; 1000]; 1000];
// Row-major access (cache-friendly)
let start = std::time::Instant::now();
for i in 0..1000 {
for j in 0..1000 {
let _ = matrix[i][j];
}
}
println!("Row-major: {:?}", start.elapsed());
// Column-major access (cache-unfriendly)
let start = std::time::Instant::now();
for j in 0..1000 {
for i in 0..1000 {
let _ = matrix[i][j];
}
}
println!("Column-major: {:?}", start.elapsed());
}
9. Common Array Patterns
Sliding Window
fn main() {
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Simple sliding window
for window in arr.windows(3) {
println!("Window: {:?}", window);
}
// Moving average
let moving_avg: Vec<f64> = arr.windows(3)
.map(|w| w.iter().sum::<i32>() as f64 / 3.0)
.collect();
println!("Moving average: {:?}", moving_avg);
// Max in each window
let max_in_window: Vec<_> = arr.windows(4)
.map(|w| w.iter().max().unwrap())
.collect();
println!("Max in window: {:?}", max_in_window);
// Overlapping windows with stride
for chunk in arr.chunks(3) {
println!("Chunk: {:?}", chunk);
}
// Custom sliding window with step
let step = 2;
for i in (0..arr.len()).step_by(step) {
if i + 3 <= arr.len() {
println!("Window at {}: {:?}", i, &arr[i..i+3]);
}
}
}
Two-Pointer Technique
fn main() {
// Find pair that sums to target
let mut arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.sort();
let target = 10;
let mut left = 0;
let mut right = arr.len() - 1;
while left < right {
let sum = arr[left] + arr[right];
if sum == target {
println!("Found pair: {} + {} = {}", arr[left], arr[right], target);
left += 1;
right -= 1;
} else if sum < target {
left += 1;
} else {
right -= 1;
}
}
// Reverse array in place
let mut arr = [1, 2, 3, 4, 5];
let mut left = 0;
let mut right = arr.len() - 1;
while left < right {
arr.swap(left, right);
left += 1;
right -= 1;
}
println!("Reversed: {:?}", arr);
// Remove duplicates from sorted array
let mut sorted = [1, 1, 2, 2, 3, 3, 4, 4, 5];
let mut write_pos = 1;
for read_pos in 1..sorted.len() {
if sorted[read_pos] != sorted[write_pos - 1] {
sorted[write_pos] = sorted[read_pos];
write_pos += 1;
}
}
println!("After removing duplicates: {:?}", &sorted[..write_pos]);
}
Binary Search
fn main() {
let arr = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19];
// Binary search implementation
fn binary_search<T: Ord>(arr: &[T], target: &T) -> Option<usize> {
let mut left = 0;
let mut right = arr.len();
while left < right {
let mid = left + (right - left) / 2;
match arr[mid].cmp(target) {
std::cmp::Ordering::Equal => return Some(mid),
std::cmp::Ordering::Less => left = mid + 1,
std::cmp::Ordering::Greater => right = mid,
}
}
None
}
for &target in &[5, 10, 15] {
match binary_search(&arr, &target) {
Some(pos) => println!("Found {} at index {}", target, pos),
None => println!("{} not found", target),
}
}
// Find first element greater than target
let target = 8;
let result = arr.iter().position(|&x| x > target);
println!("First element > {} is at index {:?}", target, result);
// Find range of equal elements
let duplicates = [1, 2, 2, 2, 3, 4, 4, 5];
let target = 2;
let start = duplicates.iter().position(|&x| x == target);
let end = duplicates.iter().rposition(|&x| x == target);
match (start, end) {
(Some(s), Some(e)) => println!("Range of {}: [{}, {}]", target, s, e),
_ => println!("{} not found", target),
}
}
10. Arrays and Pattern Matching
Destructuring Arrays
fn main() {
// Basic destructuring
let arr = [1, 2, 3, 4, 5];
let [a, b, c, d, e] = arr;
println!("a: {}, b: {}, c: {}, d: {}, e: {}", a, b, c, d, e);
// Ignoring elements with ..
let [first, second, ..] = arr;
println!("First: {}, Second: {}", first, second);
let [.., last] = arr;
println!("Last: {}", last);
let [first, .., last] = arr;
println!("First: {}, Last: {}", first, last);
let [first, second, middle @ .., fourth, fifth] = arr;
println!("First: {}, Second: {}, Middle: {:?}, Fourth: {}, Fifth: {}",
first, second, middle, fourth, fifth);
// Destructuring in match
match arr {
[1, 2, ..] => println!("Starts with 1,2"),
[x, y, z, ..] if x < y && y < z => println!("Increasing start"),
[a, b, c, d, e] if a + e == b + d => println!("Symmetrical"),
_ => println!("Other pattern"),
}
}
Pattern Matching with Arrays
fn main() {
// Match on array length and content
fn analyze_array(arr: &[i32]) {
match arr {
[] => println!("Empty array"),
[x] => println!("Single element: {}", x),
[x, y] => println!("Two elements: {}, {}", x, y),
[x, y, z] => println!("Three elements: {}, {}, {}", x, y, z),
[x, .., y] => println!("First: {}, Last: {}, and {} in between", x, y, arr.len() - 2),
_ => println!("Array with {} elements", arr.len()),
}
}
analyze_array(&[]);
analyze_array(&[1]);
analyze_array(&[1, 2]);
analyze_array(&[1, 2, 3]);
analyze_array(&[1, 2, 3, 4, 5]);
// Pattern matching with guards
let arr = [1, 3, 5, 7, 9];
match arr {
[x, y, ..] if x < y => println!("Increasing start"),
[x, y, ..] if x > y => println!("Decreasing start"),
_ => println!("Other pattern"),
}
// Extract based on pattern
let arr = [Some(1), Some(2), Some(3), None, Some(5)];
if let [Some(x), Some(y), ..] = arr {
println!("First two are Some: {}, {}", x, y);
}
}
11. Arrays and Serialization
JSON Serialization
use serde::{Serialize, Deserialize};
use serde_json;
#[derive(Debug, Serialize, Deserialize)]
struct Data {
name: String,
values: [i32; 5],
matrix: [[i32; 2]; 2],
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let data = Data {
name: "Test".to_string(),
values: [1, 2, 3, 4, 5],
matrix: [[1, 2], [3, 4]],
};
// Serialize to JSON
let json = serde_json::to_string_pretty(&data)?;
println!("Serialized: {}", json);
// Deserialize from JSON
let deserialized: Data = serde_json::from_str(&json)?;
println!("Deserialized: {:?}", deserialized);
// Working with raw JSON arrays
let json_array = "[1, 2, 3, 4, 5]";
let array: [i32; 5] = serde_json::from_str(json_array)?;
println!("Array from JSON: {:?}", array);
// 2D array from JSON
let json_matrix = "[[1,2],[3,4]]";
let matrix: [[i32; 2]; 2] = serde_json::from_str(json_matrix)?;
println!("Matrix from JSON: {:?}", matrix);
Ok(())
}
Binary Serialization
use byteorder::{LittleEndian, WriteBytesExt, ReadBytesExt};
use std::io::Cursor;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let arr = [1, 2, 3, 4, 5];
// Write array to bytes
let mut bytes = Vec::new();
for &num in &arr {
bytes.write_i32::<LittleEndian>(num)?;
}
println!("Binary representation: {:?}", bytes);
// Read array from bytes
let mut cursor = Cursor::new(bytes);
let mut read_arr = [0; 5];
for i in 0..5 {
read_arr[i] = cursor.read_i32::<LittleEndian>()?;
}
println!("Read back: {:?}", read_arr);
// Using bytemuck for zero-copy
use bytemuck::{Pod, Zeroable, cast_slice, from_bytes};
#[repr(C)]
#[derive(Debug, Copy, Clone, Zeroable, Pod)]
struct Color {
r: u8,
g: u8,
b: u8,
}
let colors = [
Color { r: 255, g: 0, b: 0 },
Color { r: 0, g: 255, b: 0 },
Color { r: 0, g: 0, b: 255 },
];
// Cast to byte slice
let bytes = cast_slice::<Color, u8>(&colors);
println!("Colors as bytes: {:?}", bytes);
// Cast back
let colors2: &[Color; 3] = from_bytes(&bytes[..std::mem::size_of::<[Color; 3]>()]);
println!("Colors back: {:?}", colors2);
Ok(())
}
12. Advanced Array Techniques
Zero-Copy Views
use std::mem;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct Pixel {
r: u8,
g: u8,
b: u8,
a: u8,
}
fn main() {
// Raw bytes
let raw_data: [u8; 16] = [
255, 0, 0, 255, // Red pixel
0, 255, 0, 255, // Green pixel
0, 0, 255, 255, // Blue pixel
255, 255, 255, 255, // White pixel
];
// Zero-cast to Pixel array
let pixels: &[Pixel; 4] = unsafe {
&*(&raw_data as *const _ as *const [Pixel; 4])
};
println!("Pixels: {:?}", pixels);
// Work with pixels directly
for (i, pixel) in pixels.iter().enumerate() {
println!("Pixel {}: RGB({}, {}, {})", i, pixel.r, pixel.g, pixel.b);
}
// Convert back
let back_to_bytes: &[u8; 16] = unsafe {
&*(pixels as *const _ as *const [u8; 16])
};
println!("Back to bytes: {:?}", back_to_bytes);
}
SIMD Operations
// Requires nightly Rust and feature flags
#![cfg_attr(feature = "nightly", feature(portable_simd))]
#[cfg(feature = "nightly")]
use std::simd::{f32x4, SimdFloat};
#[cfg(feature = "nightly")]
fn simd_example() {
let a = [1.0, 2.0, 3.0, 4.0];
let b = [5.0, 6.0, 7.0, 8.0];
// Load into SIMD vectors
let a_vec = f32x4::from_array(a);
let b_vec = f32x4::from_array(b);
// SIMD operations
let sum = a_vec + b_vec;
let product = a_vec * b_vec;
println!("SIMD sum: {:?}", sum.to_array());
println!("SIMD product: {:?}", product.to_array());
// Dot product
let dot = (a_vec * b_vec).reduce_sum();
println!("Dot product: {}", dot);
}
#[cfg(not(feature = "nightly"))]
fn simd_example() {
println!("SIMD requires nightly Rust");
}
fn main() {
simd_example();
}
Array Pools and Reuse
struct ArrayPool<T, const N: usize> {
pools: Vec<[T; N]>,
}
impl<T: Default + Copy, const N: usize> ArrayPool<T, N> {
fn new() -> Self {
ArrayPool { pools: Vec::new() }
}
fn acquire(&mut self) -> [T; N] {
self.pools.pop().unwrap_or([T::default(); N])
}
fn release(&mut self, mut arr: [T; N]) {
// Reset before returning to pool
for elem in &mut arr {
*elem = T::default();
}
self.pools.push(arr);
}
}
fn main() {
let mut pool = ArrayPool::<i32, 1024>::new();
// Get arrays from pool
let mut arr1 = pool.acquire();
let mut arr2 = pool.acquire();
// Use them
arr1[0] = 42;
arr2[511] = 100;
println!("arr1[0]: {}, arr2[511]: {}", arr1[0], arr2[511]);
// Return to pool
pool.release(arr1);
pool.release(arr2);
println!("Pool size: {}", pool.pools.len());
// Reuse
let arr3 = pool.acquire();
println!("arr3[0] after reuse: {}", arr3[0]); // 0 (reset)
}
Conclusion
Rust arrays provide a powerful, efficient way to work with fixed-size collections:
Key Takeaways
- Fixed Size: Length is part of the type system
- Stack Allocation: Fast access, no heap overhead
- Type Safety: Bounds checking prevents buffer overflows
- Pattern Matching: Destructuring and matching support
- Const Generics: Generic code for arrays of any size
- Zero-Cost: Compiles to efficient machine code
Array vs Vector Comparison
| Feature | Array | Vector |
|---|---|---|
| Size | Fixed at compile time | Dynamic |
| Allocation | Stack | Heap |
| Type | [T; N] | Vec<T> |
| Resizable | No | Yes |
| Performance | Faster access | Slightly slower |
| Use case | Known size | Unknown/Changing size |
Best Practices
- Use arrays when you know the size at compile time
- Prefer slices for function parameters
- Use const generics for generic array code
- Be careful with bounds - use
get()for safe access - Consider cache efficiency for large arrays
- Profile before optimizing array access patterns
Common Operations Cheat Sheet
// Creation
let arr = [1, 2, 3, 4, 5];
let zeros = [0; 100];
let mut mut_arr = [1, 2, 3];
// Access
let first = arr[0];
let safe = arr.get(2);
let slice = &arr[1..4];
// Information
let len = arr.len();
let is_empty = arr.is_empty();
// Iteration
for item in &arr { }
for item in arr.iter() { }
for item in arr.iter_mut() { }
for (i, item) in arr.iter().enumerate() { }
// Search
let contains = arr.contains(&3);
let position = arr.iter().position(|&x| x == 3);
// Transformation
let doubled: Vec<_> = arr.iter().map(|&x| x * 2).collect();
let sum: i32 = arr.iter().sum();
// Modification
let mut arr = [1, 2, 3];
arr[0] = 10;
arr.swap(1, 2);
arr.reverse();
arr.sort();
Rust arrays combine the performance of C arrays with the safety of Rust's type system, making them ideal for systems programming, embedded development, and any scenario where fixed-size collections are needed.