Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

🦀 The Land of Rust: Ferris the Crab’s Space Adventures

Chapter 6: The Smart Wardrobe Machine (Enums, Option & Match)

📋 Chapter Outline:


6.1. How’s the Weather Today? (enum)

6.1.1. The Story: Ferris’s Smart Wardrobe

Ferris has a magical wardrobe that tells him what to wear based on the weather. But weather isn’t just one thing: it can be sunny, rainy, snowy, or cloudy. Ferris wants to store these four states in his program. Instead of using plain numbers or text (which are easy to mistype or mess up), Rust has a fantastic tool called an enum (short for enumeration).

6.1.2. The Problem: Numbers or Text?

Imagine we used numbers:

#![allow(unused)]
fn main() {
let weather = 1; // 1 = sunny, 2 = rainy, ...
}

But what if we accidentally type 5? The program won’t know what to do! What about text?

#![allow(unused)]
fn main() {
let weather = "sunny";
}

If we mistype "suny" (missing an n), the program gets confused! An enum solves this perfectly. It only lets you pick from a fixed list. Nothing else is allowed. 🛡️

6.1.3. Defining an enum with Different States

We use the enum keyword to create a new type that can only be one of several specific values:

#![allow(unused)]
fn main() {
enum Weather {
    Sunny,
    Rainy,
    Snowy,
    Cloudy,
}
}

Now Weather is a brand new data type, just like i32 or String. But it only has four valid values.

6.1.4. Creating Values from an enum

To create a value from this enum, we write the enum name, then two colons (::), and finally the variant we want:

#![allow(unused)]
fn main() {
let today = Weather::Sunny;
let tomorrow = Weather::Rainy;
}

The :: means: “From inside this enum, pick this specific option.”

[Illustration: Ferris the crab stands in front of a magical weather map. Four glowing icons float above: a sun, a rain cloud, a snowflake, and a gray cloud. A friendly selector tool connects them to a dropdown menu. Style: vibrant children’s book illustration, clear educational metaphor, soft lighting, 16:9.]


6.2. A Wardrobe Full of Clothes (Enum with Data)

6.2.1. Not All Sunny Days Are the Same

Some sunny days are 20°C, others are 35°C. On rainy days, we might want to know the umbrella color. In Rust, each enum variant can hold its own extra data!

6.2.2. Defining an enum with Data

#![allow(unused)]
fn main() {
enum WeatherInfo {
    Sunny { temperature: u8 },
    Rainy { umbrella_color: String },
    Snowy { scarf_material: String },
    Cloudy,
}
}

🔹 Sunny holds a temperature field (u8). 🔹 Rainy holds an umbrella_color field (String). 🔹 Snowy holds a scarf_material field (String). 🔹 Cloudy needs no extra data.

6.2.3. Creating Instances of an enum with Data

#![allow(unused)]
fn main() {
let sunny_day = WeatherInfo::Sunny { temperature: 32 };
let rainy_day = WeatherInfo::Rainy { umbrella_color: String::from("red") };
let snowy_day = WeatherInfo::Snowy { scarf_material: String::from("wool") };
let cloudy_day = WeatherInfo::Cloudy;
}

6.2.4. enum vs struct

🔹 struct: All fields are always present. A Monster always has a name, color, legs, and power. 🔹 enum: Only one variant is active at a time. A WeatherInfo is either Sunny, Rainy, Snowy, or Cloudy. It can’t have a temperature AND an umbrella color simultaneously!

[Illustration: Split educational graphic. Left: a “struct” box showing all four slots filled at once. Right: an “enum” wardrobe with four drawers, but only ONE drawer is open at a time. Ferris points to the open drawer explaining the difference. Style: clean, cartoon, bright colors, clear visual metaphor, 16:9.]


6.3. The Lost Bag (Option)

6.3.1. The Story: Searching for the Spaceship Key

Ferris always keeps his spaceship key in his backpack. But some days, he forgets, and the bag is empty! In programming, we call this “something might exist, or it might not.” Many languages use null for this, but null is dangerous and causes programs to crash. Rust has no null. Instead, it uses Option.

6.3.2. Introducing Option<T>

Option is a super useful enum built right into Rust:

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),
    None,
}
}

<T> means “this can hold any type you want.” Some(T) means “we have a value of type T,” and None means “there’s nothing here.”

6.3.3. Using Some and None

#![allow(unused)]
fn main() {
let key = Some(String::from("golden key")); // The key exists
let no_key: Option<String> = None;          // No key
}

If you just write None, you usually need to tell the compiler what type of Option it is (like Option<String>).

6.3.4. Why Option is Safer Than null

In languages with null, if you forget to check whether a value is null before using it, your program might suddenly crash. In Rust, the compiler forces you to handle both Some and None. This makes your programs incredibly safe! 🛡️

6.3.5. Useful Methods on Option

🔹 .unwrap() → Gives you the value if it’s Some. Crashes if it’s None. (Only use when you’re 100% sure it’s there!) 🔹 .unwrap_or(default) → Returns a fallback value if it’s None. 🔹 .is_some() / .is_none() → Checks which state it’s in.

#![allow(unused)]
fn main() {
let key = Some(String::from("abc"));
let value = key.unwrap_or(String::from("Key not found"));
println!("{}", value); // Prints: abc
}

6.3.6. Exercise: safe_divide Function

Write a function called safe_divide that takes two f64 numbers. If the second number isn’t zero, return the result inside Some. Otherwise, return None.

💡 Sample Answer:

fn safe_divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

fn main() {
    let result = safe_divide(10.0, 2.0);
    match result {
        Some(r) => println!("Result: {}", r),
        None => println!("Error: Cannot divide by zero!"),
    }
}

[Illustration: Ferris opening two floating treasure chests. One chest contains a glowing key labeled “Some(key)”. The other chest is empty with a gentle “None” tag. A friendly compiler robot holds a checklist saying “Always check both!”. Style: playful, educational, warm lighting, children’s book, 16:9.]


6.4. The Smart Remote Control (match)

6.4.1. The Problem: How to Decide Based on an enum?

Now that we know the weather is Sunny or Rainy, how do we tell the user what to wear? We could use many if statements, but Rust has a much cleaner tool: match.

6.4.2. Introducing match

match is like a smart sorting machine: it takes a value, compares it against several patterns, and runs the code for the first pattern that fits.

#![allow(unused)]
fn main() {
let weather = Weather::Sunny;

match weather {
    Weather::Sunny => println!("Wear a sun hat!"),
    Weather::Rainy => println!("Take an umbrella!"),
    Weather::Snowy => println!("Wrap up in a scarf!"),
    Weather::Cloudy => println!("Perfect weather, wear what you like!"),
}
}

6.4.3. The Exhaustive Rule

With match, you must cover every possible variant. If you forget even one, the compiler will complain: “You didn’t handle the Snowy case!” This is a great feature because forgotten cases never turn into hidden bugs! ✅

6.4.4. Extracting Data from an enum with match

If our enum holds extra data, we can pull it out right inside match:

#![allow(unused)]
fn main() {
let info = WeatherInfo::Sunny { temperature: 35 };

match info {
    WeatherInfo::Sunny { temperature } => {
        println!("Sunny at {} degrees. Don't forget sunscreen!", temperature);
    }
    WeatherInfo::Rainy { umbrella_color } => {
        println!("It's raining. Grab the {} umbrella!", umbrella_color);
    }
    // ... other variants
}
}

6.4.5. The Catch-All Pattern with _

Sometimes we only care about one or two variants and want to group everything else. We use _ (underscore) to mean “anything else”.

#![allow(unused)]
fn main() {
match weather {
    Weather::Sunny => println!("Let's go to the park!"),
    _ => println!("Better stay indoors today."),
}
}

6.4.6. match with Option

match works beautifully with Option:

#![allow(unused)]
fn main() {
let key = Some(String::from("golden key"));

match key {
    Some(k) => println!("Found the key: {}", k),
    None => println!("Key is missing. Let's search!"),
}
}

6.4.7. if let for Simpler Cases

If we only care about one specific variant and want to ignore the rest, if let is a shorter alternative:

#![allow(unused)]
fn main() {
let weather = Weather::Sunny;

if let Weather::Sunny = weather {
    println!("What a beautiful sunny day!");
} else {
    println!("Not sunny today.");
}
}

6.4.8. Exercise: Coin Values

Create a Coin enum with Penny, Nickel, Dime, and Quarter. Write a function that takes a coin and returns its value in cents (1, 5, 10, 25).

💡 Sample Answer:

#![allow(unused)]
fn main() {
enum Coin { Penny, Nickel, Dime, Quarter }

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
}

[Illustration: A cartoon remote control with four glowing buttons labeled with enum variants (Sunny, Rainy, Snowy, Cloudy). A hand presses “Rainy” and a speech bubble pops up saying “Take an umbrella!”. Ferris watches from the side holding a checklist. Style: dynamic, educational vector illustration, bright colors, 16:9.]


6.5. Project: The Smart Shirt Recommender

Let’s build a small program that asks the user for the weather and suggests a shirt color!

6.5.1. Define the Weather enum

#![allow(unused)]
fn main() {
enum Weather {
    Sunny,
    Rainy,
    Snowy,
    Cloudy,
}
}

6.5.2. The Shirt Recommendation Function

#![allow(unused)]
fn main() {
fn recommend_shirt(weather: Weather) -> &'static str {
    match weather {
        Weather::Sunny => "white (cool and bright)",
        Weather::Rainy => "blue (matches the rainy sky)",
        Weather::Snowy => "red (warm and cheerful)",
        Weather::Cloudy => "gray (perfect for cloudy days)",
    }
}
}

💡 Note on &'static str: This is a special type of text that lives for the entire lifetime of the program. It’s perfect for fixed, unchanging messages like these!

6.5.3. Getting User Input

use std::io;

fn main() {
    println!("Choose the weather:");
    println!("1: Sunny | 2: Rainy | 3: Snowy | 4: Cloudy");

    let mut input = String::new();
    io::stdin().read_line(&mut input).expect("Failed to read input");

    let choice: u32 = input.trim().parse().expect("Please enter a valid number!");

    let weather = match choice {
        1 => Weather::Sunny,
        2 => Weather::Rainy,
        3 => Weather::Snowy,
        4 => Weather::Cloudy,
        _ => {
            println!("Invalid number. Defaulting to Sunny.");
            Weather::Sunny
        }
    };

    let shirt = recommend_shirt(weather);
    println!("My recommendation: wear a {} shirt!", shirt);
}

6.5.4. Adding a Preferred Color with Option

What if the user has a favorite color? If they provide one, use it. Otherwise, stick with our recommendation:

#![allow(unused)]
fn main() {
let preferred_color: Option<String> = None; // We could ask the user later

let final_color = match preferred_color {
    Some(color) => color,
    None => recommend_shirt(weather).to_string(),
};

println!("Final color choice: {}", final_color);
}

[Illustration: A friendly cartoon robot display panel showing a weather input screen. A dropdown enum selector points to a suggested shirt popping out of a slot. Ferris stands beside holding a rainbow-colored shirt. Style: cozy tech, children’s book illustration, bright and inviting, 16:9.]


6.6. Summary & Challenge

6.6.1. What You Learned

In this chapter, you discovered: ✅ enum is a type that can only be one of several predefined variants. ✅ Each variant can hold its own unique data. ✅ Option<T> is Rust’s safe way to say “we have something (Some) or nothing (None).”match is a powerful tool to make decisions based on enum values and extract inner data. ✅ if let is a handy shortcut when you only care about one case.

6.6.2. Challenge: Calculator with Result

Instead of Option, let’s peek at another famous enum: Result. It’s perfect for error handling:

#![allow(unused)]
fn main() {
enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

Write a divide function that takes two f64 numbers. If the second isn’t zero, return Ok(result). Otherwise, return Err("Cannot divide by zero"). Handle it in main using match.

💡 Sample Answer:

fn divide(a: f64, b: f64) -> Result<f64, &'static str> {
    if b == 0.0 {
        Err("Division by zero is impossible!")
    } else {
        Ok(a / b)
    }
}

fn main() {
    let result = divide(10.0, 2.0);
    match result {
        Ok(r) => println!("Result: {}", r),
        Err(e) => println!("Error: {}", e),
    }
}

Now you know how to manage different states beautifully with enum and match. In the next chapter, we’ll learn how to organize our code into files and modules, turning our program into a tidy, professional library! 📚✨

[Illustration: Ferris wearing a graduation cap, holding a glowing “Chapter 6 Complete” badge. Floating around him are colorful enum tags, match arms, Option/Result boxes, and a small weather remote. Encouraging, bright lighting, children’s book style, 16:9.]