
Error handling in Rust - steveklabnik
http://blog.burntsushi.net/rust-error-handling/
======
Animats
Yes, it really is that complicated. It's quite elegant in its way. If you
start from the proposition that you don't want exceptions, you don't want
Null, everything is strongly typed, error types are not hierarchical, and
functional programming with lambdas is reasonable within sequential code, this
is where you end up. It's logical, but not fun.

I wrote an RSS reader in Rust, and all the code required to compose all the
error types (I/O, HTTP, XML, RSS, date formats) into a custom error type is as
bulky as the modest amount of code that does the useful work. That's not a
good thing. Having to write all that error handling code up front is going to
be a big problem for the Agile crowd. This may be a major drag on Rust
adoption.

All this error machinery is in Rust to avoid exceptions. That may have been a
mistake. Exceptions have the good property that they can't be ignored - if
they're not handled, you get a reasonable program termination. If there's a
reasonable exception hierarchy, you can catch an exception near the root of
the tree and get all subsidiary exceptions. (It took three major revisions of
exception handling before Python got that right, but it finally did.) Rust
doesn't have an error hierarchy like that; you can't just check for
"RuntimeError" or "EnvironmentError" and get all the more detailed errors. You
have to combine error types yourself, as the article shows.

I really want Rust to succeed, but I think they took a wrong turn with error
handling.

~~~
pcwalton
Some people need to use Rust in places where exceptions aren't allowed
(because the unwind tables and cleanup code are too big). Those people include
virtually all browser vendors and game developers.

Furthermore, exceptions have this nasty codegen tradeoff. Either you make them
zero-cost (as C++, Obj-C, and Swift compilers typically do), in which case
throwing an exception is very expensive at runtime, or you make them non-zero-
cost (as Java HotSpot and Go 6g/8g do), in which case you eat a performance
penalty for every single try block (in Go, defer) _even if no exception is
thrown_. For a language with RAII, every single stack object with a destructor
forms an implicit try block, so this is impractical in practice.

The performance overhead of zero-cost exceptions is not a theoretical issue. I
remember stories of Eclipse taking 30 seconds to start up when compiled with
GCJ (which used zero-cost exceptions) because it throws thousands of
exceptions while starting.

The C approach to error handling has a great performance and code size story
relative to exceptions when you consider both the error and success paths,
which is why systems code overwhelmingly prefers it. It has poor ergonomics
and safety, however, which Rust addresses with Result. Rust's approach forms a
hybrid that's designed to achieve the performance of C error handling while
eliminating its gotchas.

~~~
saurik
The argument made by others--that you are working from a mental model of
exceptions as made popular by Java as opposed to as a generic language feature
for removing boilerplate (essentially a syntax sugar for a specific kind of
error monad)--should be well examined (and I don't want to detract from it, as
it is really important and seems to have colored your perception quite badly
:( enough that it seems to have led you astray even in this comment... one of
the big issues with Java exception performance is the required reification and
storage of stacktraces, a language quirk that gcj must honor), but I am going
to bring up a related one of error handling performance: C's error handling is
_expensive_ (in addition to being verbose and unsafe, Rust sadly only trying
to work on the latter) because it requires functions that could possibly
return an error but normally don't (which certainly should be "all functions
that claim to return errors", though I will argue is actually the set of "all
functions", but this is a longer argument) to have to go out of their way,
moving around values in registers at minimum, to indicate "success".

C++ zero-cost exceptions make successful code run fast--faster than the
equivalent successful C code--and in the cases where the compiler
infrastructure (or more likely in 2015, cultural baggage, as it really doesn't
take much effort to bootstrap exceptions) or memory constraints prevents them
from being used, the great thing about the C++ mindset is that they don't cost
you anything to ignore: even in a program that has exceptions elsewhere, if
your code doesn't need unwind semantics (and a lot of code doesn't), you don't
take any overhead; and, as kernel and game developers also often build their
own data structures--not to avoid exceptions, but for other important
pragmatic reasons (including but not limited to virtual memory restrictions,
control over non-determinism, and extreme case-specific optimization)--it is
easy (and, I would argue, strongly preferable) to free most developers from
frustrating error handling boilerplate while still seeing (as we do) the
developers who somehow need to avoid these features having no issues doing so
(as we do in the C++ community).

~~~
Jweb_Guru
Zero-cost exceptions are occasionally required for performance, yes. They
usually aren't--I think the strongest argument for them is in parsers, where
you may be performing _tons_ of I/O that could potentially error and don't
want to bloat the icache with result handling. Either way, for two reasons
(performance, and making sure you can catch exceptions at C FFI boundaries)
Rust provides a `catch` in unsafe code. What it discourages is using them for
semantic reasons.

I wish you would be more realistic about the tradeoffs of exceptions, though.
The win in compile time alone from not having landing pads everywhere is
substantial, and the "error handling boilerplate" you discuss isn't generally
agreed to be boilerplate.

~~~
saurik
A big argument for using systems programming languages is thay they are fast:
you are writing code that is compiled once and run thousands of times per
second on millions of computers. Why would I care so much about compile-time
performance? If I need a faster compiler mode during development (though in my
experience, the pain of C++ compile times is due to its muti-stage
transformation and lack of separate compilation for templates, not its
exception handling... I don't find my projects that turn off exceptions to
compile much faster), the compiler can also implement a really slow mechanism
for exceptions as an alternative.

I also maintain that requiring the developer to do manual error propagation to
get errors from the point of failure to the point of reporting to the
administrator or the operator (which is what happens to almost all errors,
whether you are writing a command line tool, a graphical application, or a
website) is "boilerplate". At the simplest, without changing the error model,
littering the code with calls to try! that push the error upwards is
automatable (and I have a friend working on a compiler plugin to accomplish
exactly that).

An example: I like statically-typed languages, but as demonstrated by
languages with type infererence, having to declare every type every time it is
used is "boilerplate", especially if types are long and unwieldy. C++98 has
typedef, which lets me take C++'s long and unwieldy type definitions and make
them shorter. If you are used to typing them out manually, every single time,
some might even claim this is "ergonomic". However, C++11's auto is much
better, and as demonstrated by languages like OCaml and Haskell even that
isn't as good as it can get. C and Go have long and unwieldy error propagation
(checking flags and manually returning new ones). Rust provides macros and
tooling for this that make it safer (no more "goto fail;" or "Rage Against the
Cage" exploits) and easier (like typedef!), but that doesn't mean it isn't
still "boilerplate".

~~~
Jweb_Guru
I mean, sure. But I've personally benchmarked a lot of code that uses Result
and noted where it caused performance issues, if it did, fixed the code so it
didn't anymore, and moved on with my life. Most of the time, it wasn't even
close to being a bottleneck--most of the time the branch is correctly
predicted and the time is just spent copying bytes for the `Ok` value, which
is basically a nonissue if you can keep your error word-sized unless you're
calling a tiny function in a tight loop that LLVM can't inline for some
reason. OTOH, landing pad generation in Rust probably doubles its compile
time, which is already pretty slow (which is one reason I personally will
probably just compile with panic! -> abort, when that option becomes
available).

I don't consider the error handling boilerplate because I do frequently want
to recover, even from supposedly fatal errors. For example, if I'm handling an
iterator of things, and some of them fail, I still usually want to keep
processing them before I return the error. Exceptions lock you into a very
rigid pattern of error reporting that make that much more difficult to deal
with. Because libraries don't _assume_ that I want to fail when they do, it
can be easy and fast for me to do this, even for pathological inputs; that
wouldn't be the case if everyone used exceptions.

I agree with you that Rust could do better at providing options for error
reporting with panic. I believe there's an RFC about that right now, please
comment on it!

~~~
saurik
I have been a games developer in a past life (my primary focus for over three
years), and I am unconvinced I or any of the other people I worked with would
have been willing to tolerate a low-grade performance degradation distributed
around all of our code if we had access to programming languages that did not
have that issue and weren't infinitely worse (and while I really wish I could
use Rust, it just isn't infinitely better in a way that seems relevant for
either the hardcore or casual gaming markets). Also, it sounds like there is
something wrong with the Rust compiler if this landing pad generation really
_doubles_ the compile time... a poor compiler should not influence the
language design in such a negative direction.

Solving problems like your iterator issue should not be difficult with
exceptions: in fact, frankly, I don't see why it is any harder than with
Result: your worst case is that you start with a primitive, "antitry!" (which
should be called "try!", as it makes more sense than what "try!" currently
does for that word) which performs an operation and returns a Result of either
its value or its failure. Even if you do this "frequently", I would be shocked
if this was remotely as common as the code that was assuming success and just
wanted to fail if something fails. And, as long as you aren't trying to
"handle" those errors (and it doesn't sound like you are, you just kind of
want to fail out with a list of failure reasons) there is no semantic
complexity either. Exceptions are the flexible primitive, not Result:
simulating Result from exceptions requires a tiny finite amount of code, while
simulating exceptions using Result requires modifications to an arbitrarily
large amount of code.

As for fixing panic, what really sucks is that so much library code is going
to be written using Result now when much of it should have been written with
panic, both in the core library and from third-party developers, meaning that
the boilerplate is going to be somewhat inescapable at the lowest levels of
the program... I think the thing I like most about programming in Python vs
C++ is that I don't have to wrap every single API I use to make it handle
errors in a sane way. I don't think that fixing panic at some point in the
future is going to work as there seems to be such awkward community momentum
against exceptions and behind Result, when the real RFC needs to be "when you
write your library, try to use panic, not Result".

~~~
Jweb_Guru
> Also, it sounds like there is something wrong with the Rust compiler if this
> landing pad generation really doubles the compile time... a poor compiler
> should not influence the language design in such a negative direction.

Um, actual, non-trollish question: do you know how exceptions are implemented?
Because I can't think of a way around this in a language with destructors,
short of some sort of "doesn't throw" effect (which Rust doesn't currently
have) or aborting on every exception. Do you have any pointers? Similarly, I
don't understand how your "antitry" solution would be efficient on
pathological input--"zero-cost" exceptions are far from that when the
exceptions actually happen (and can be made to happen by malicious users). You
also haven't addressed how I'm supposed to know to use antitry in the first
place, which is one of my biggest issues with unchecked exceptions--again, the
only alternative I see is a "throws" effect. All of these are language level
concerns, not implementation-level ones.

> Exceptions are the flexible primitive, not Result

Catchable exceptions require coding transactionally if you want to use them
everywhere and maintain strong exception safey. There's an enormous cost
associated with that too; and worse, it can't be checked by the compiler.

Again: I have actually benchmarked a lot of code that uses Result, explicitly
checking to see if it was a performance issue. It's almost never a performance
issue. So performance-related arguments for why exceptions should be used
everywhere are going to fall on deaf ears here. I am far more interested in
seeing whether we can eliminate panics altogether for most of the common cases
(e.g. with compile-time checked range types).

------
steveklabnik
One of the reasons I'm really excited for stability is that people can start
to write things like this, knowing that the effort is worth it, as it won't
suddenly stop working tomorrow.

Next week, burntsushi and I are going to talk about rolling this into the
official documentation.

~~~
coldpie
Yes, I'm very glad to see Rust stabilizing. I wrote a few personal projects in
it earlier this year, and now none of it compiles. I'm very excited for Rust,
but the instability has put me off learning in-depth.

~~~
oinksoft
That sounds terrible, is Rust that unstable in practice today? I remember
hearing that the language had mostly stabilized at the end of 2014, but
anecdotes like yours are discouraging.

~~~
simcop2387
There had been some breaking changes going on towards the 1.0 release. The
first alpha to clean that up was in January of this year which really started
the move to stabilize a lot of things, but there were still some breaking
changes (I know there were a few around IO). It went beta back in April and
there haven't been any serious breaking changes since (that I know of, I'm
only following things not a rust developer). Tomorrow it'll be released as the
1.0 stable version that from then on you won't have any breaking changes
(until 2.0, or whatever the next version is). That's not too unusual for a
language that has been evolving the way rust has, you just can't avoid
breaking changes for a while until you actually figure out what works the way
you're after. But kudos to the rust team for getting it this far; I can't wait
to see all the celebration tomorrow.

------
extropy
How about separate the processing code from error handling code into a block
of it's own. Like this:

    
    
            fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, String> {
    		let mut file = File::open(file_path);
    		let mut contents = String::new();
    		let read_Result = file.read_to_string(&mut contents);
    		let n = contents.trim.parse::<i32>();
    		let result = 2 * n;
    
    		error_handling {
    			file: None => Err("Failed open file"),
    			read_Result: Err(err) => Err(err),
    			n: Err(err) => Err(err),
    			_success: Ok(result)
    		};
    	}
    
    
    

The compiler would then convert each error handling case into early return
statements in the places where assignment is made to the specified variable.

~~~
burntsushi
I'm not sure how that's supposed to work. For example, you have
`file.read_to_string`, but in your code, `file` is a `Result`. So you'll need
to use `map` I presume.

I personally like the style that `try!` encourages.

------
1_player
Awesome article, but I'm stuck on the "error-double-string" example:

    
    
        ...
        argv.nth(1)
            .ok_or("Please give at least one argument".into())
            .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string()))
        ...
    

What is str::into? I find the documentation is really hard to navigate for a
Rust newbie like me: here's what I've tried:

1) Search "into" on the API docs. I find ([https://doc.rust-
lang.org/std/convert/trait.Into.html#tymeth...](https://doc.rust-
lang.org/std/convert/trait.Into.html#tymethod.into)) and see that str
implements this trait to convert it to a Vec<u8>. Not seeing the need for a
conversion to Vec<u8> I...

2) Search "ok_or": I find ([https://doc.rust-
lang.org/core/option/enum.Option.html#metho...](https://doc.rust-
lang.org/core/option/enum.Option.html#method.ok_or)) but I still don't see why
the string has to be converted to Vec<u8>.

3) Maybe literal strings are Vec<u8> and into() just is used to convert a
Vec<u8> to str. Can't find any documentation on that, and seems fishy if it
were the case.

4) Try googling "rust str into" with useless results. Not Rust's fault here :)

I haven't used Rust yet but often try to read other people code and understand
what's going on, and everytime I hit the API docs I'm filled with despair that
I can't find what a function is supposed to do.

Again, I'm a noob. I must be doing something wrong..

~~~
steveklabnik
One thing you can do when you're confused about a type is to remove the call,
and see what happens:

    
    
        error: mismatched types:
         expected `core::result::Result<_, &str>`,
            found `core::result::Result<i32, collections::string::String>`
        (expected &-ptr,
            found struct `collections::string::String`) [E0308]
        hello.rs:6         .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string()))
                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        hello.rs:6:25: 6:74 help: pass `--explain E0308` to see a detailed explanation
    

In other words, without the call to `into()`, we expected a &str, but got a
String. This `into()` call is the first one that you found, but, through some
magic, it's not clearly obvious, it's true. The secret is Into's opposite,
From: [https://doc.rust-
lang.org/std/convert/trait.From.html](https://doc.rust-
lang.org/std/convert/trait.From.html)

There's a generic implementation in the standard library:

    
    
        impl<T, U> Into<U> for T where U: From<T> {
    

In other words, anything that implements From, implements Into as well. So
generally, things just implement From, and take advantage of this blanket
implementation.

With this knowledge, this From implementation makes sense:

    
    
        impl<'a> From<&'a str> for String
    

We can convert From a String to a &str, and hence, we can convert a &str Into
a String.

This kind of type shenanigans is _very_ powerful, but admittedly, a bit
confusing.

~~~
1_player
So

    
    
        "foo".into()
    

could be expressed as

    
    
        String::from("foo")
    

I would personally use the latter form as it is easier to parse and reason
about. Thanks for the explanation!

~~~
steveklabnik
Yup! Yeah, it really depends on your style. Rust tends to prefer methods to
free functions, due to multi-dispatch, but there's a good argument for either
style.

Furthermore, there's a non-generic version of this particular conversion:

    
    
        "foo".to_string()
    

And, a generic version that expresses a different semantic:

    
    
        "foo".to_owned()
    

Strings are a very common type, and so implement a _lot_ of these things, even
when this kind of usecase may overlap.

~~~
Veedrac
> Strings are a very common type, and so implement a _lot_ of these things,
> even when this kind of usecase may overlap.

I can't say this is very helpful; it's hard enough learning the one way. Why
have `to_string()` if we have `into()`?

~~~
steveklabnik
Well, one reason is that to_string() is ancient, but into() is fairly new.
Another is that 'to' and 'into' are two different things: to_string() takes
its caller by reference, and produces a copy. into() takes its caller by
value, and so consumes it.

------
coldtea
Perhaps naive question: is there a language where errors are automatically
returned from a function (in lieu of exceptions)?

So if you have a function x that has some unhandled "exception" thrown, what
it does is it returns an error value at that point to the caller (instead of
actually throwing an Exception).

Does that exist?

~~~
tel
I believe you can't have that happen universally since then you don't get the
benefit of having exceptions bubble at all—you're just de facto operating in
an error monad.

On the other hand, if it's not universal then you have to set boundaries where
the exception to value transition occurs and this is "just" a catch block.

~~~
coldtea
> _I believe you can 't have that happen universally since then you don't get
> the benefit of having exceptions bubble at all—you're just de facto
> operating in an error monad._

I was thinking something like you get an Optional, and you check it.

If you use it without a check (e.g. something like "try" in Rust), and it's an
error value, then it "explodes" again and bubbles up.

~~~
tel
The mechanics of that are easily captured with a monad, but baking it into the
natural semantics of the language would be a neat experiment, I agree.

------
charlieflowers
> The power of Error comes from the fact that all error types impl Error,
> which means errors can be existentially quantified as a trait object. This
> manifests as either Box<Error> or &Error.

It took me a couple of minutes to figure out that this means: "you can have
heterogeneous collections of errors, and you can use them 'polymorphically'
without knowing their concrete type."

In other words, you can use Error as a "trait object." I don't think a newbie
to Rust would follow the language in the article here.

(Just a suggestion to help improve an outstanding article).

------
madez
The example

    
    
        io-basic-error-custom
    

and the first version of

    
    
        error-impl
    

have the same content but different names, while the reference just above the
first version of the latter points to the former.

Is this intentional? It confused me for a second.

I know this is nitpicking, just asking.

And, thank you steveklabnik, pcwalton, and other contributors of Rust for
working in the open and answering our questions here. Thank you burntsushi for
your great post.

~~~
burntsushi
Ah, hmm. They have different content.

error-impl: [https://github.com/BurntSushi/blog/blob/master/code/rust-
err...](https://github.com/BurntSushi/blog/blob/master/code/rust-error-
handling/src/bin/error-impl.rs)

io-basic-error-custom:
[https://github.com/BurntSushi/blog/blob/master/code/rust-
err...](https://github.com/BurntSushi/blog/blob/master/code/rust-error-
handling/src/bin/io-basic-error-custom.rs)

The key is that the code is spread out over multiple blocks in the article. I
need a better literate programming tool than hacky Python...

~~~
madez
I looked at it again and now I see that the code examples don't necessarily
constitute the whole file. And the parts in question happen to be the same in
both files. My fault.

 _Maybe_ it would be interesting for the reader to see the number of the lines
of the shown code, something like "error-impl.rs, lines 3-12 out of 51".

~~~
burntsushi
Yeah, that would be a nice improvement. Some day, I will need to improve my
literate programming tool:
[https://github.com/BurntSushi/blog/blob/master/scripts/rust-...](https://github.com/BurntSushi/blog/blob/master/scripts/rust-
from-blog)

:P

------
msie
Where does the n and err values come from?

    
    
        fn double_number(number_str: &str) -> Result<i32,     ParseIntError> {
            match number_str.parse::<i32>() {
                Ok(n) => Ok(2 * n),
                Err(err) => Err(err),
            }
        }

~~~
maxbrunsfeld
From the left-hand side of the `=>` arrows in the `match` expression.

~~~
msie
I know that. Where do they come from before that?

~~~
steveklabnik

        match number_str.parse::<i32>() {
    

This matches on the return value of parse, which is an Result<T>. It can have
two values: Err(E), or Ok(T).

    
    
            Ok(n) => Ok(2 * n),
    

This line matches an Ok(T), and binds the value of type T to n, which can then
be used on the right hand side.

    
    
            Err(err) => Err(err),
    

this line matches an Err(T), and binds the value of type T to err, which can
be used on the right hand side.

The right hand sides re-wrap these values into another Result.

~~~
phaylon
Nitpick: Result<T, E> instead of Result<T> assuming Result is
std::result::Result.

~~~
steveklabnik
Ahh yes, thank you.

