Skip to Content
Course content

23: The Option and Result Types

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're using a vending machine. You press the button for a bag of chips. Two things can happen: either the chips fall down, or the slot is empty. In other languages, an empty slot might be represented by a null value, which is basically a landmine waiting to blow up your program the moment you try to "eat" it. In Rust, we don't do that. We use an Option.

Now, imagine a different scenario: you're trying to pay for those chips. You slide a bill into the machine. Either the machine accepts the money (success), or it spits it back out because it's a counterfeit bill or the wrong currency (failure). This isn't just about "nothing" being there; it's about an operation that explicitly failed for a specific reason. That's where Result comes in.

Handling Absence with Option

In Rust, Option<T> is an enum. It tells the compiler (and you) that a value might be missing. It has two variants: Some(T), which holds the data, and None, which represents the absence of data. I've spent years debugging NullPointerExceptions in Java; Rust's approach forces you to deal with the "None" case before the code will even compile.

struct User {
    username: String,
    email: Option<String>, // Not every user provides an email
}

fn main() {
    let user = User {
        username: String::from("rustacean_88"),
        email: None,
    };

    match user.email {
        Some(mail) => println!("Sending email to {}!", mail),
        None => println!("This user has no email on file."),
    }
}

Notice how I used a match statement. You can't just call a method on user.email as if it were a string because it isn'tβ€”it's a wrapper. You have to "unwrap" it first.

Handling Failure with Result

While Option is for when something is missing, Result<T, E> is for when something goes wrong. It has two variants: Ok(T) for success and Err(E) for the error. The E part is crucial because it allows you to pass back a specific error message or an error type, rather than just a generic "it didn't work."

I usually use Result whenever I'm doing I/O, like reading a file or hitting an API, because those things fail all the time.

use std::fs::File;
use std::io::{self, Read};

fn read_config_file() -> Result<String, io::Error> {
    let mut file = File::open("config.txt")?; // The '?' operator is a lifesaver
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() {
    match read_config_file() {
        Ok(text) => println!("Config loaded: {}", text),
        Err(e) => println!("Failed to load config: {}", e),
    }
}

You'll see that ? operator in my code. It's a shorthand that says: "If this result is Ok, give me the value. If it's an Err, return that error from the whole function immediately." It keeps the code from becoming a nested mess of match blocks.

Unwrapping and the Danger of Panic

Sometimes, you're writing a quick prototype or you're 100% certain a value exists. In those cases, you can use .unwrap() or .expect("Message"). This tells Rust: "Just give me the value; if it's None or Err, just crash the program."

I'll be honest: .unwrap() is tempting, but it's a dangerous habit. If your program crashes in production because of a None value you thought was impossible, you're the one getting the 3 AM phone call. Use match, if let, or the ? operator whenever possible.




πŸ“‹ Practical Task

Exercise: The Secure Vault Access Checker

You are building a security system for a digital vault. You need to implement a function that checks if a user has access to a specific vault key.

Requirements:

  • Create a Vault struct that contains a HashMap where the keys are vault IDs (strings) and the values are access codes (integers).
  • Implement a method called get_access_code that takes a vault ID. It should return an Option<i32> (because the vault ID might not exist).
  • Implement a function called verify_access that takes the Option<i32> returned from the vault and a "provided code" (i32).
    • If the Option is None, return a Result::Err with the string "Vault not found".
    • If the Option is Some but the codes don't match, return a Result::Err with the string "Invalid access code".
    • If they match, return a Result::Ok with the string "Access Granted".

Your main function should test three cases: a non-existent vault, a vault with the wrong code, and a vault with the correct code, printing the result of each attempt.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.