Rustlings Session 4: Error Handling and Existential Crisis

Well, here we are again. Session 4 of my Rustlings journey, and honestly? What a mess.

I've been putting off writing about this one because, quite frankly, it was a bit of a disaster. Not because the exercises were particularly difficult (though they certainly weren't trivial), but because I was so bloody distracted the entire time. Two hours to get through Section 13—error handling—when the previous session took me 45 minutes for three sections. Mathematics, as they say.

The Distraction Problem

I mentioned in my session notes that I was "very distracted with other stuff" and "restless." That's putting it mildly. You know that feeling when your brain decides it wants to be anywhere except where it currently is? That was me, the entire time. Every five minutes I'd find myself scrolling through something completely unrelated, or suddenly deciding that now was the perfect time to reorganise my desktop icons.

It's frustrating because error handling is actually quite important—arguably one of the most crucial concepts in Rust. The whole point of Rust's Result<T, E> type is to make error handling explicit and safe, forcing you to deal with potential failures rather than just hoping they won't happen (looking at you, null pointer exceptions).

What I Actually Learned (When I Was Paying Attention)

Despite my wandering attention, I did manage to absorb some things:

Results vs Options

The first exercise was straightforward enough—converting from Option<String> to Result<String, String>. It's a simple concept: Option is for "something or nothing," while Result is for "success or failure with context." The syntax is pretty clean too:

// Instead of this
if name.is_empty() {
None
} else {
Some(format!("Hi! My name is {name}"))
}

// You do this
if name.is_empty() {
Err("Empty names aren't allowed".to_string())
} else {
Ok(format!("Hi! My name is {name}"))
}

The Question Mark Operator

Exercise 2 introduced the ? operator, which is genuinely one of Rust's more elegant features. Instead of manually checking every Result and returning early on errors, you can just slap a ? on the end and let Rust handle the error propagation:

let qty = item_quantity.parse::<i32>()?;

If the parse fails, it immediately returns the error. If it succeeds, it unwraps the value. Brilliant, really. Much cleaner than the nested match statements you'd otherwise need.

Error Types in Function Signatures

Exercise 3 was where things got properly confusing (and where my attention started wandering most). The issue was that main() was trying to use the ? operator, but main() didn't return a Result. The solution was to change the function signature:

fn main() -> Result<(), ParseIntError> {
// ... code that can fail ...
Ok(())
}

This took me ages to figure out, partly because I kept getting distracted, and partly because the compiler errors weren't immediately obvious to me. "Why is main() complaining about this?" I kept muttering to myself.

Custom Error Types

The later exercises dealt with creating custom error enums and implementing traits like Display and Error. This is where Rust's error handling really shines—you can create rich, descriptive error types that carry exactly the information you need:

#[derive(PartialEq, Debug)]
enum CreationError {
Negative,
Zero,
}

Then you can match on these specific error conditions and handle them appropriately. It's much more civilised than just throwing generic exceptions and hoping someone catches them properly.

Boxing Errors

Exercise 5 introduced Box<dyn std::error::Error>, which is essentially a catch-all error type. When you have functions that might return different kinds of errors, you can box them all up into a single error type. It's a bit of a compromise between type safety and convenience.

The Compiler: My Frenemy

I stand by my session notes: "I got 99 problems and all of them are the compiler screaming at me." The Rust compiler is simultaneously the most helpful and most infuriating thing I've encountered. It catches so many potential bugs that would silently explode at runtime in other languages, but good God does it have opinions about everything you do.

Half the time I'd fix one error only to discover three more that were hiding behind it. It's like that game where you hit one mole and two more pop up. Except the moles are compiler errors and they're all judging your life choices.

Reflecting on Session 4

Looking back, I think the distraction issue says more about my mental state at the time than about the exercises themselves. Error handling is conceptually straightforward—it's just about being explicit about what can go wrong and how to handle it. But when your brain is trying to be in six different places at once, even straightforward concepts become frustrating slogs.

I probably should have just stopped and come back when I was in a better headspace. But there's something to be said for pushing through, even when you're not at your best. Sometimes you learn things despite yourself.

What's Next?

Session 5 is looming, and I'm hoping I can maintain better focus next time. Though knowing my luck, I'll probably get hyperfixated on something completely unrelated right before I start. Maybe I should just embrace the chaos—after all, dealing with unpredictable behaviour is what error handling is all about, isn't it?

At least I can say I understand Result<T, E> now, even if it took me two hours and a minor existential crisis to get there.