Skip to editor content
learningrust.orglesson 6 of 26

Structs & Enums

Structs and enums are Rust's primary tools for creating custom data types. They allow you to group related data together and create meaningful types for your domain.

Defining Structs

A struct (short for "structure") is a custom data type that groups related values together:

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: String::from("user@example.com"),
        username: String::from("rustacean"),
        active: true,
        sign_in_count: 1,
    };

    println!("Username: {}", user1.username);
}

Mutable Structs

To modify a struct, the entire instance must be mutable:

struct User {
    username: String,
    email: String,
    active: bool,
}

fn main() {
    let mut user1 = User {
        email: String::from("user@example.com"),
        username: String::from("rustacean"),
        active: true,
    };

    user1.email = String::from("new@example.com");
    println!("New email: {}", user1.email);
}

Struct Update Syntax

Create a new instance from an existing one:

struct User {
    username: String,
    email: String,
    active: bool,
}

fn main() {
    let user1 = User {
        email: String::from("user@example.com"),
        username: String::from("rustacean"),
        active: true,
    };

    // Create user2 with some values from user1
    let user2 = User {
        email: String::from("another@example.com"),
        ..user1  // Take remaining fields from user1
    };

    println!("User2: {}", user2.username);
}

Tuple Structs

Structs that look like tuples, useful for giving meaning to tuples:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);

    println!("Red value: {}", black.0);
    println!("X coordinate: {}", origin.0);
}

Methods on Structs

Use impl blocks to define methods:

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // Method that borrows self
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // Method with additional parameters
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }

    // Associated function (no self) - often used as constructors
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };
    let square = Rectangle::square(25);

    println!("Area of rect1: {}", rect1.area());
    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Square area: {}", square.area());
}

Defining Enums

Enums allow you to define a type by enumerating its possible variants:

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

fn main() {
    let heading = Direction::North;

    match heading {
        Direction::North => println!("Going north!"),
        Direction::South => println!("Going south!"),
        Direction::East => println!("Going east!"),
        Direction::West => println!("Going west!"),
    }
}

Enums with Data

Enum variants can hold data of different types:

enum Message {
    Quit,                       // No data
    Move { x: i32, y: i32 },   // Named fields (like struct)
    Write(String),              // Single value
    ChangeColor(i32, i32, i32), // Multiple values
}

fn main() {
    let msg1 = Message::Quit;
    let msg2 = Message::Move { x: 10, y: 20 };
    let msg3 = Message::Write(String::from("Hello"));
    let msg4 = Message::ChangeColor(255, 0, 0);

    process_message(msg3);
}

fn process_message(msg: Message) {
    match msg {
        Message::Quit => println!("Quitting"),
        Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
        Message::Write(text) => println!("Writing: {}", text),
        Message::ChangeColor(r, g, b) => println!("Color: RGB({}, {}, {})", r, g, b),
    }
}

The Option Enum

Rust's Option type handles the absence of a value (no null!):

fn main() {
    let some_number: Option<i32> = Some(5);
    let no_number: Option<i32> = None;

    // Using match
    match some_number {
        Some(n) => println!("Got number: {}", n),
        None => println!("No number"),
    }

    // Using if let for simpler cases
    if let Some(n) = some_number {
        println!("The number is: {}", n);
    }
}

Methods on Enums

Enums can have methods too:

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

impl Coin {
    fn value_in_cents(&self) -> u32 {
        match self {
            Coin::Penny => 1,
            Coin::Nickel => 5,
            Coin::Dime => 10,
            Coin::Quarter => 25,
        }
    }
}

fn main() {
    let coin = Coin::Quarter;
    println!("Value: {} cents", coin.value_in_cents());
}

Practice Exercise

Try this in the playground:

struct Person {
    name: String,
    age: u32,
}

impl Person {
    fn new(name: &str, age: u32) -> Person {
        Person {
            name: String::from(name),
            age,
        }
    }

    fn greet(&self) {
        println!("Hi, I'm {} and I'm {} years old!", self.name, self.age);
    }

    fn have_birthday(&mut self) {
        self.age += 1;
        println!("Happy birthday! Now I'm {}!", self.age);
    }
}

fn main() {
    let mut person = Person::new("Alice", 30);
    person.greet();
    person.have_birthday();
}

Key Takeaways

  • Structs group related data with named fields
  • Use impl blocks to add methods to structs
  • Enums define types with multiple variants
  • Enum variants can hold different types of data
  • Option<T> replaces null with Some(T) or None
  • match exhaustively handles all enum variants
  • if let provides a simpler syntax for single-variant matching

Structs and enums are foundational to writing idiomatic Rust code!

Next Steps

With structs and enums in your toolkit, you're ready for pattern matching — Rust's powerful way to destructure those types and branch on their variants. match and if let turn the enums you just defined into expressive, exhaustive control flow.

Next lesson

Pattern Matching

Master Rust's powerful pattern matching with match expressions, if let, and destructuring

25 min