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

🦀 Tip of the day 03: Banning Null

First, a confession: We were supposed to cover Procedural Macros today. But explaining how to write a custom compiler plugin from scratch to manipulate Abstract Syntax Trees requires a 10-part blog post, a whiteboard, and way too much caffeine. We are skipping it! Instead, let’s look at a feature you will actually use every single day: The death of null.

If you are coming from Java, Python, or C#, you probably live in constant fear of the NullPointerException. In most languages, almost any variable can secretly be empty, and you usually only find out when your program crashes in production. Rust doesn’t have null. At all.

How it works in practice: Like many modern languages, Rust uses an Option type (Some or None) to represent values that might be missing. The difference is that the Rust compiler forces you to handle the None case. You cannot accidentally use the inner data without explicitly unpacking it first. What you write:

#![allow(unused)]
fn main() {
// The return type 'Option<String>' explicitly warns you this might be empty
fn get_user_name(id: u32) -> Option<String> {
    if id == 1 {
        Some("Alice".to_string())
    } else {
        None
    }
}

let user = get_user_name(2);

// If you try to print or use `user` directly here, the code will NOT compile!
// The compiler FORCES you to handle the empty case:
match user {
    Some(name) => println!("Welcome back, {}!", name),
    None => println!("User not found."),
}
}

Wait, do I really have to write a match block in every single function? Thankfully, no! While the compiler forces you to acknowledge the Option, it gives you ergonomic shortcuts so you don’t spend half your day writing boilerplate: The “Pass the Buck” operator (?): If you put a question mark at the end of a function call (like let name = get_user_name(2)?;), Rust will automatically extract the data if it exists. If it’s None, it immediately aborts the current function and returns None to whoever called you. It’s a clean way to bubble up missing data without nesting. The “I Live Dangerously” method (.unwrap()): You can tell the compiler, “Trust me, I am absolutely certain this is not empty.” It bypasses the safety checks and gives you the data instantly.

🔇 Rust gracefully eliminates the unpredictable, billion-dollar mistake of the classic null pointer crash you’d get in Java, Python, or C#. Instead, it provides you with the incredibly convenient .unwrap() method, which allows your program to violently crash with a highly predictable panic! on line 42 instead. So much safer!