RS Rust

Rust: Structs, Enums & Pattern Matching

What you will learn

How to define custom data types with structs and enums, and how match makes exhaustive pattern checking natural.

Defining a struct

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

let user1 = User {
    username: String::from("alice"),
    email: String::from("[email protected]"),
    sign_in_count: 1,
    active: true,
};

println!("{}", user1.email);  // dot notation

Methods on structs

Methods are defined in an impl block:

impl User {
    fn is_active(&self) -> bool {
        self.active
    }

    fn update_email(&mut self, new_email: String) {
        self.email = new_email;
    }
}

&self borrows the struct immutably. &mut self borrows mutably. self takes ownership (rare, for destructors).

Enums

Enums can hold data:

enum Message {
    Quit,
    Move { x: i32, y: i32 },  // struct-like
    Write(String),              // tuple-like
    ChangeColor(i32, i32, i32),
}

let msg = Message::Move { x: 10, y: 20 };

Pattern matching with match

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

match is exhaustive -- you must handle every variant. The compiler enforces this.

The Option enum (no null)

Rust has no null. Instead:

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

fn maybe_length(s: Option<String>) -> usize {
    match s {
        Some(val) => val.len(),
        None => 0,
    }
}

Common mistakes

  • Forgetting to handle a variant in match -- the compiler catches this, which is a feature, not a bug
  • Using match when if let is simpler: if let Some(v) = val { ... } for single-variant matching
  • Not implementing Debug on structs (#[derive(Debug)]) before trying to print them

Quick check below!