RUST: Core Data Structures in Rust

Rust's core data structures — structs, enums, Option, arrays, and vectors — are the building blocks of every Rust program. Each is designed with Rust's ownership system in mind, providing both safety and performance without compromise.

Data structures are how you organize information in your program. Rust provides a rich set of built-in types: structs for grouping named fields, enums for types with multiple variants, Option for representing the presence or absence of a value, arrays for fixed-length collections, and vectors for dynamic collections. Each is designed to work seamlessly with Rust's ownership and type system.


Structs

A struct (structure) groups related data together under a single name. Structs in Rust are similar to structs in C, but with the addition of methods and trait implementations.

Defining a Struct

rust
struct Point {
    x: f64,
    y: f64,
}

Struct of Structs

Structs can contain other structs:

rust
struct Line {
    start: Point,
    end: Point,
}

Using Structs

rust
fn structures() {
    let p = Point { x: 3.0, y: 4.0 };
    println!("point p is at ({}, {})", p.x, p.y);
    // point p is at (3.0, 4.0)

    let p2 = Point { x: 5.0, y: 10.0 };
    let myline = Line { start: p, end: p2 };

    println!("Line from ({}, {}) to ({}, {})",
        myline.start.x, myline.start.y,
        myline.end.x, myline.end.y);
    // Line from (3.0, 4.0) to (5.0, 10.0)
}

Fields are accessed with dot notation. Note that once p is used as start: p, ownership moves to mylinep can no longer be used directly.

Struct Update Syntax

If you want to create a new struct based on an existing one, updating only some fields:

rust
let p3 = Point { x: 7.0, ..p2 }; // keeps p2.y = 10.0, changes x to 7.0

Tuple Structs

Rust also supports tuple structs, which are structs with unnamed fields:

rust
struct Color(u8, u8, u8); // RGB

let red = Color(255, 0, 0);
println!("Red channel: {}", red.0); // access by index


Enums

An enum (enumeration) defines a type that can be one of several variants. Unlike C enums which are just integers, Rust enums can carry data with each variant.

Basic Enum

rust
enum Direction {
    North,
    South,
    East,
    West,
}

let heading = Direction::North;

By default, enum variants start at 0 and increment, but Rust enums are far more powerful than this.

Enums with Data

Enum variants can carry data in tuple format or struct format:

rust
enum Color {
    Red,
    Green,
    Blue,
    RGBColor(u8, u8, u8),                           // tuple variant
    RGBAColor { red: u8, green: u8, blue: u8, alpha: u8 }, // struct variant
}

Matching on Enums

The match statement is the primary way to work with enums:

rust
fn enums() {
    let c: Color = Color::RGBColor(128, 0, 255);

    match c {
        Color::Red   => println!("Red"),
        Color::Green => println!("Green"),
        Color::Blue  => println!("Blue"),
        Color::RGBColor(0, 0, 0) => println!("Black"),
        Color::RGBColor(r, g, b) => println!("rgb({}, {}, {})", r, g, b),
        Color::RGBAColor { red: _, green: _, blue: _, alpha: 0 } => println!("transparent"),
        _ => ()  // catch remaining variants, do nothing
    }
    // rgb(128, 0, 255)
}

The compiler guarantees that every variant is handled. If you add a new variant to the enum, every match in your codebase that does not have a _ wildcard will produce a compile error, guiding you to update all the relevant code.

text
Enum Variant Memory Layout:
+----------------------------+
| Color::Red                 |  (just a tag)
| Color::RGBColor(r, g, b)   |  (tag + 3 bytes of data)
| Color::RGBAColor{...}      |  (tag + 4 bytes of data)
+----------------------------+
The enum takes the size of its largest variant.


Option``

Rust has no null. The absence of a value is represented by the Option<T> enum, defined in the standard library as:

rust
pub enum Option<T> {
    None,
    Some(T),
}

  • None: No value is present
  • Some(T): A value of type T is present

This forces you to handle the "no value" case explicitly, eliminating null pointer dereferences at compile time.

Using Option

rust
let a: Option<i32> = None;
let b: Option<i32> = Some(10);

println!("{:?}", a); // None
println!("{:?}", b); // Some(10)

Option in a Function

A function that might fail to produce a result returns Option<T>:

rust
fn divide(x: f64, y: f64) -> Option<f64> {
    if y != 0.0 {
        Some(x / y)
    } else {
        None
    }
}

fn option_example() {
    let x = 3.0;
    let y = 2.0;

    let result: Option<f64> = divide(x, y);

    // Using match to handle Option
    match result {
        Some(z) => println!("{} / {} = {}", x, y, z), // 3.0 / 2.0 = 1.5
        None    => println!("cannot divide {} by {}", x, y),
    }

    // Shorthand: if let
    if let Some(z) = result {
        println!("result = {}", z); // result = 1.5
    }
}

Common Option Methods

MethodDescription
option.unwrap()Returns the value or panics if None
option.unwrap_or(default)Returns the value or a default
option.is_some()Returns true if Some
option.is_none()Returns true if None
option.map(f)Applies a function if Some

rust
let opt: Option<i32> = Some(5);
let doubled = opt.map(|x| x * 2); // Some(10)
let value = opt.unwrap_or(0);     // 5


Arrays

An array is a fixed-length, stack-allocated collection of elements of the same type. The size is part of the type and must be known at compile time:

rust
use std::mem;

fn arrays() {
    let mut a: [i32; 5] = [1, 2, 3, 4, 5];

    println!("a has {} elements, first is {}", a.len(), a[0]);
    a[0] = 321;
    println!("a[0] = {}", a[0]); // a[0] = 321
    println!("{:?}", a);          // [321, 2, 3, 4, 5]

    // Array comparison
    if a == [321, 2, 3, 4, 5] {
        println!("match!");
    }

    // Initialize all elements to the same value: [1u16; 10]
    let b = [1u16; 10];
    println!("b took up {} bytes", mem::size_of_val(&b)); // 20 bytes (10 * 2)

    // 2D array (matrix)
    let mtx: [[f32; 3]; 2] = [
        [1.0, 2.0, 3.0],
        [4.0, 5.0, 6.0],
    ];

    // Print diagonal elements
    for i in 0..mtx.len() {
        for j in 0..mtx[i].len() {
            if i == j {
                println!("mtx[{}][{}] = {}", i, j, mtx[i][j]);
                // mtx[0][0] = 1.0
                // mtx[1][1] = 5.0
            }
        }
    }
}

text
Array Memory Layout (stack):
[i32; 5] = 5 * 4 bytes = 20 bytes contiguous on the stack
+----+----+----+----+----+
| 1  | 2  | 3  | 4  | 5  |
+----+----+----+----+----+

Arrays are fast (contiguous memory, cache-friendly) but inflexible (fixed size). When you need a resizable collection, use a vector.


Vectors

A Vec<T> is a dynamically-sized, heap-allocated array. It is one of the most frequently used types in Rust. The vector manages its own memory: it allocates a buffer on the heap, grows it automatically as needed, and frees it when the vector is dropped.

rust
fn vectors() {
    let mut a: Vec<i32> = Vec::new();
    a.push(1);
    a.push(2);
    a.push(3);
    println!("a = {:?}", a); // a = [1, 2, 3]

    // Index access
    let idx: usize = 0;
    a[idx] = 312;
    println!("a[0] = {}", a[idx]); // a[0] = 312

    // Safe access with get() — returns Option<&T>
    match a.get(10) {  // index 10 doesn't exist
        Some(x) => println!("a[10] = {}", x),
        None    => println!("error: no such element"), // this branch runs
    }

    // Remove and return last element: pop() returns Option<T>
    let last = a.pop();
    println!("popped: {:?}, remaining: {:?}", last, a);
    // popped: Some(3), remaining: [312, 2]

    // Drain remaining elements
    while let Some(x) = a.pop() {
        println!("{}", x);
        // 2
        // 312
    }
}

Vector Initialization

rust
// Empty vector
let v: Vec<i32> = Vec::new();

// With initial elements (macro)
let v = vec![1, 2, 3, 4, 5];

// Filled with a value
let v = vec![0; 10]; // ten zeros

Vector vs. Array Comparison

PropertyArray [T; N]Vector Vec<T>
SizeFixed at compile timeDynamic at runtime
MemoryStackHeap
AllocationAutomaticManaged by Vec
PerformanceSlightly fasterMinor overhead
Use caseKnown, fixed sizeUnknown or growing size

Summary: Core Data Structures

text
Data Structure Overview:
+------------------+---------------------------------------------+
| Type             | Description                                 |
+------------------+---------------------------------------------+
| struct           | Named, grouped fields (like a record)       |
| enum             | One of several variants, can carry data     |
| Option<T>        | Some(value) or None (replaces null)         |
| [T; N]           | Fixed-length stack array                    |
| Vec<T>           | Dynamic heap-backed array                   |
+------------------+---------------------------------------------+


Conclusion

Rust's core data structures are designed to be both expressive and safe. Structs model real-world entities with named fields and ownership semantics. Enums model alternatives cleanly, and match ensures every variant is handled. Option<T> replaces null entirely, making "missing value" an explicit part of the type system. Arrays are fast, fixed, and stack-allocated. Vectors are flexible, heap-backed, and automatically managed.

Together, these types cover the vast majority of data modeling needs in systems programming, and they compose naturally with Rust's ownership and borrowing system to produce correct, efficient code.