There's No Such Thing as an Error in Rust
Colleagues have heard me say this. Usually when we’re debugging something, or discussing error handling approaches, I’ll pipe up with:
“Well, there’s no such thing as an error in Rust.”
It tends to get a raised eyebrow or two. So I’ll explain.
How Rust Handles Errors
If you’re not familiar with Rust, here’s the quick version: Rust doesn’t have
exceptions. Instead, operations that can fail return a Result type.
enum Result<T, E> {
Ok(T),
Err(E),
}
When you call a function that might fail, you get back either Ok containing
your value, or Err containing some error information. You then have to
handle both cases.
fn read_config() -> Result<Config, ConfigError> {
// ...
}
fn main() {
match read_config() {
Ok(config) => println!("Got config: {:?}", config),
Err(e) => println!("Failed to read config: {:?}", e),
}
}
The Result type is what’s called an “algebraic data type”. It’s a type
that can be one of several variants, each potentially carrying different data.
Rust calls these enums which is a bit annoying as they’re not “enums”, they’re
sum types that contain data. But I guess it’s too late for that to change…
Errors Are Just Data
To Rust, there’s nothing special about that Err variant.
It’s just data. The compiler doesn’t treat it differently. There’s no hidden
machinery. It’s the same as any other value you might pass around your
program.
Contrast this with Python. When the code encounters data it doens’t expect or
know how to handle an exception is raised, and then the code execution and data
flow disappears from the page and is maybe picked up somewhere else in the
codebase, up the call stack, if that type of exception is except’d'. It’s
like the data gets thrown into this hidden subway running underneath the code.
There’s nothing explicit in the code that might raise the exception whether it
would, or what it might raise.
And when you’re writing code, you might forget to catch the right exception. Or any exception at all. By default, exceptions propagate silently up the call stack until they hit something that catches them - or they don’t, and your program crashes. You’re not being made to handle error data, you have to opt into doing it.
In Rust, everything is explicit. The function signature tells you it returns
a Result. You have to acknowledge the possibility of failure to get at the
success value. You’re exposed to the full complexity of what you’re doing,
always. Even when you don’t really care about the details and it’s
excruciatingly painful to do so…
Result Is Just a Convention
The Result type is arbitrary. It’s just
a convention that the standard library and ecosystem have agreed upon. Other
than the ? operator (which I’ll get to), there’s nothing special about it.
You could define your own:
enum Either<L, R> {
Left(L),
Right(R),
}
And decide that Left is where you put your error information. The compiler
wouldn’t care. Your code would work just fine. You’d lose the ? operator
and interop with the standard library, but structurally there’s nothing
magical about Result.
The ? operator is syntactic sugar that makes working with Result more
ergonomic:
fn do_stuff() -> Result<Value, Error> {
let config = read_config()?; // Returns early if Err
let data = fetch_data(&config)?;
Ok(process(data))
}
But even ? is just desugaring to a match expression. It’s not some deep
language primitive for error handling. Rust doesn’t have a concept of
“errors” built in. It has sum types, and one of them is conventionally used
for representing fallible operations.
Errors Are Just Data
I keep coming back to this phrase: “errors are just data”. And here’s the philosophical bit.
It’s not just in Rust that errors are just data. To computers, errors are just data. Always have been. The whole concept of an “error” is a human one that we’ve imposed over patterns of bits. The computer just sees ones and zeros. It doesn’t know that this particular combination means “file not found” and should make the human sad. We decided that…
The difference is that Rust surfaces this reality to us, while other languages try to hide it behind special mechanisms.
Spare a Thought for Your Error Data
Given that our errors are data, and Rust makes us handle that data explicitly, perhaps we should care about it - really care about it - and put just as much thought and attention into how error information flows through our system as we do for the happy path data.
Look after your error data… it’ll look after you.
A note on how this post was written
Disclaimer: I hadn’t posted in this blog for many years mostly because I didn’t get round to it. I had ideas for blog posts, but didn’t find the time to write them out. For a while I also was doing technical blogging on the website for the company I worked for, so got my “technical writing fix” from that. Despite that, I’ve had years at a time where I didn’t write any technical blog, which I regret, as I think it is good for the mind and good for the soul. To kickstart me getting some of my thoughts down on paper again, I’m experimenting with using Claude code to assist me. I’ve tried developing a “skill” by getting it to process all my previous blog and all my writings in Slack to try to capture my “voice”. I’m giving it a stream of conciousness notes and asking it to write that into prose in my voice. So these posts are “written by AI”, but only with ideas I’ve told it to write, and hopefully in my voice.
Raw notes this post was derived from:
# "There's No Such Things as an Error in Rust"
colleagues have heard me say this so I'll explain.
start with a brief explainer on how errors are handled in rust, summarising
that they are done so through an algebraic data type.
Go off on a tagent about how rust calls them "enums" when they are not
actually enums but instead "sum types".
point out that to rust the "error" is "just data".
contrast to languages like java and python that have "execptions" that need
to be caught. Describe this as a hidden subsystem / subway of information
flow that you can't see on the page in front of you when you read the code.
whereas in rust everything is explicit, things aren't hidden from you. you
are exposed to the full complexity of what you're doing, always... even when
that really hurts...
revisit the point that in rust your error is "just data", and expand to point
out how the `Result` enum is arbitrary and "just a convention". Other than
the `?` operator there's nothing special about it. you could define your own
enum Either<L, R> {
Left(L),
Right(R),
}
and choose a convention that "left" is where you put your error information.
ultimately error data is almost a first class citizen.
the point I'm trying to make with "in rust errors are just data" is that it's
not just in rust... to computers "errors are just data"... "errors are just data".
The whole concept of an error is a human one that we've imposed over data, the
computer just sees 1s and 0s.
(as a final remark) given that our errors are data, and rust surfaces that to
us and makes us handle it and its flow through our application, perhaps we should
care about that data, and put just as much care an attention into how it flows
through our system as the "Ok" variant data. Look after your error data.