
Improved Error Handling in Rust - steveklabnik
http://lucumr.pocoo.org/2014/11/6/error-handling-in-rust/
======
NateDad
As a gopher who has written an errors package for Go, I find Rust's journey
into figuring out errors to be very interesting. A lot of the stuff in this
article is similar to many of the error patterns in Go. Of course, with Go,
it's a lot more ad-hoc and less boilerplate, but also obviously fewer compile-
time guarantees.

The try! macro is something I know a lot of people would love to have in Go
(those pesky if err != nil { return err } lines really bother people for some
reason).

I think it's interesting that the built-in error type has a cause, that seems
to be one of the most common patterns in Go error packages as well. Is there
any automatic stack tracing? That seems to be one other common thing in Go
error packages.

~~~
Dewie
> Of course, with Go, it's a lot more ad-hoc and less boilerplate,

How is it less boilerplate?

~~~
NateDad
Many errors in go are simply:

    
    
        var ErrFoo = errors.New("error doing Foo")
    

Sometimes if you want an error to have a specific type, but have a custom
error message with context in it (like the name of the file that was missing,
for ErrFileNotFound) you can do something like this:

    
    
        type ErrBar struct {
            error
        }
    

Then you can return errors of that type thusly:

    
    
        func Do(s string) error {
            // ...
            return ErrBar{ fmt.Errorf("failed to use %q", s) }
        }
    

Finally, a lot of the time you just want to give the caller some information
about an error you received from a function you called, so you just wrap it
thusly:

    
    
        func DoIt() error {
            // ...
            if err := parseConfig(); err != nil {
                return fmt.Errorf("can't parse foo config: %s", err)
            }
        }
    

That'll give you an error with a useful message (adding context to the error
from getConfig), but with an anonymous type.

And since errors are just regular values, you can check for them in all the
same ways you would for any other value (== for errors that are static values,
type assertions to check for the type, etc).

It's not super different from Rust's errors, except that with embedding and
the implicit interfaces, you don't have to explicitly tell the compiler quite
so much to make your error a valid error.

Rust's main difference is the ability to define some automatic conversions and
the try! macro that will do an automatic return if the call fails... which are
definitely cool.

~~~
masklinn
> Many errors in go are simply:

> var ErrFoo = errors.New("error doing Foo")

That's just a matter of having a built-in struct for generic errors no? Rust
could probably provide a trivial error type (implementing the relevant traits)
for this case.

~~~
NateDad
Yeah,that's a good point. Certainly Rust or anyone else could implement a
simple function that returns a populated error struct with the given string
message.

I guess my initial reaction of a lot of boilerplate was mainly due to the
default Rust error having a message, description, and a cause, instead of Go's
default error that just has the message. Adding a description and cause is
easy enough, and if you defined them for every error in Go, it would probably
look pretty similar. My apologies.

~~~
burntsushi
> I guess my initial reaction of a lot of boilerplate was mainly due to the
> default Rust error having a message, description, and a cause, instead of
> Go's default error that just has the message.

The `Error` trait in Rust only demands that you define the `description`
method.[1] The other two are optional.

[1] - [http://doc.rust-lang.org/std/error/trait.Error.html](http://doc.rust-
lang.org/std/error/trait.Error.html)

------
mercurial
So it means the client of your library can simply pattern-match on the result
returned by cause() to find out if it was caused by, say,
FileNotFoundException? Nice.

~~~
masklinn
Nope, cause() returns an &Error, there's nothing you can pattern match there.
And despite the documentation's claim, as far as I can see Error does not
extend Any, which would be necessary for dynamic typechecking.

Also remember that cause() is recursive, you may need to traverse the whole
chain before finding what you're looking for (not that you can find it at the
moment)

~~~
mercurial
I don't think that cause() being recursive is an issue, but I'm not sure why
you'd want the cause if you can't do anything useful like attempt to downcast
it.

~~~
comex
You don't need to use cause(). For the LibError example, I think you'd instead
want to pattern match against the actual variants defined in the enum, one of
which is IoError(io::IoError), at which point you could match the IoError's
kind against FileNotFound. This way you're depending on the published contract
of the libraries in question, rather than the fact that you'll be given an
error whose eventual cause is some file somewhere not being found, which is
more of an implementation detail.

~~~
mercurial
That's a good idea in general, but there are times where you do need
downcasting. Maybe your generic DB framework can throw a specific error on
MySQL that you known how to recover from, but which is only available from
cause() but not from the library's contract.

------
arthursilva
I like it. Simple and extensible, if you want to.

