
On Error Handling in Rust - kibwen
http://lucumr.pocoo.org/2014/10/16/on-error-handling/
======
lmm
In some ways it's heartening to see Rust working everything out for itself -
but it's also painful to watch the language stumble on the same problems that
we've already solved.

You're going to hit the same problem again with async, with transactions, and
with resource management; indeed some of the stuff I've already seen about
borrowing and the like seems achingly close to the same pattern. Introducing
new sigils like ? for each use case is not going to be sustainable.

The nice way to solve this is higher kinded types, monads, and some kind of
concise notation for composing them (e.g. Haskell's do or Scala's for/yield).
Then you can do something like (Scala):

    
    
        def read_value(host, port) = for{
            sock ← TcpStream::connect(host, port)
            parser = Parser::new(&mut sock as &mut Reader)
            r ← parser.parse_value()
          } yield r
    

and this is generic and extensible; you avoid blowing your syntax budget
because the ← syntax is reusable for any "context" that behaves in the same
way (which turns out to be most of them), and also for user-defined types.
There's no need for macros or magic method names (If you want the FromError
functionality you can use a typeclass). Everything's implemented with ordinary
types and objects behaving in the ordinary way.

Of course none of this is perfect - in Scala the ← syntax is "magic",
implemented in the parser, and so the method names it invokes (map/flatMap)
are also magic, and different languages have to fight over the best
implementation for them. In Haskell the compiler knows about the Monad
typeclass specifically, and the do notation is linked to that; if you were to
write your own implementation of Monad, you wouldn't be able to use the
syntax. There's plenty of room for innovation here, and I hope Rust eventually
comes up with something better than either of those approaches. But an ad-hoc
? operator that calls a macro that calls a specially named method really isn't
the way forward. I'm sure Rust can come up with something more generic and
principled than this.

~~~
kibwen
HKT is indeed on the long-term roadmap, but it remains to be seen whether a
design can be devised that plays nicely with the fundamental features of Rust
(note that the language reserves the unused `do` keyword for just this
purpose). Doing this properly is very much research-project territory.

And unless I'm reading the RFC incorrectly, I think this is more principled
than you're making it out to be. `FromError` is not magic or specially-handled
by the compiler in any way, it's just a trait defined in the stdlib. The
`try!` macro isn't calling any magic methods, it's just expanding to a pattern
match that itself makes use of typeclasses. It's indeed true that this would
only work with variants of the `Result` type, but I don't think that's
especially heinous (users can easily supply their own specialized versions of
this type, and almost always do). And if the `?` syntax is accepted, it will
be able to be used with any type that implements the `Carrier` trait (which is
_sorta_ specially-treated by the compiler, though users can still override it
via lang items), and would replace the `try! macro entirely. Nothing here is
ad-hoc.

Finally, even if Rust had HKTs, I could be convinced that error handling in
particular is important enough to require a dedicated syntax to set it apart.

~~~
lmm
`Carrier` is still pretty parochial; it has this normal/exception distinction
hardwired into it, no? The RFC explicitly rules out using Vector with it, and
it doesn't look possible to implement for async constructs, or STM
transactions, or the like? I'd very much like to be wrong here.

FromError is not handled specially by the compiler, but it is handled
specially by the try macro; does the signature of try make the relationship
between the two more obvious? I haven't been following the state of Rust IDEs,
but I'd hope that they can make it more obvious which typeclasses are involved
than is clear from the text of the code (apologies for this awful sentence).

FWIW doing things this way doesn't rule out a dedicated syntax on top of it -
see the recent SIP for async/await in scala.

~~~
kibwen

      > `Carrier` is still pretty parochial; it has this 
      > normal/exception distinction hardwired into it, no?
    

To reiterate my earlier point, I'm personally fine with the existence of an
entirely separate mechanism for error handling. Mind you, not that this
invalidates your desire for a more general mechanism for async et al. Until/if
we get HKTs, we'll probably continue to achieve this with bespoke macros as
per today's `try!`.

    
    
      > FromError is not handled specially by the compiler, but it is handled specially by 
      > the try macro
    

The `try!` macro is just as non-special as `FromError` (and `Result` and
`Option`). You're free to recreate the whole ecosystem in your own libs if
you'd like (ignoring the `Carrier` proposal for the moment and its associated
syntax).

~~~
lmm
> The `try!` macro is just as non-special as `FromError` (and `Result` and
> `Option`). You're free to recreate the whole ecosystem in your own libs if
> you'd like (ignoring the `Carrier` proposal for the moment and its
> associated syntax).

Sure. I'm coming at this from a viewpoint of a) syntax is very important b)
user-defined macros are generally undesirable

------
zarvox
The nicest things about error handling in this manner are that any function
that has a error condition makes it clear in the function signature, and any
time you call a function that can fail, it's visible in the source where you
make the call.

This makes it very easy to tell whether code is handling potential failure
conditions or ignoring them - you're either ignoring the return value (which
is a compiler warning) or you're handling the failure condition. I'm reminded
of Raymond Chen's post from 2005 about handling failure conditions, and
recognizing code which handles all of its error conditions:

[http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/35294...](http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx)

I think this approach will pay great dividends in both understandability and
readability.

~~~
oldmanjay
It basically feels like Java's checked exceptions without stack unwinding and
with a nicer syntax - the possible errors are part of the signature, the
compiler insists you do something about them, and there is a wrapping facility
to cross system boundaries without leaking details.

In case my comment is taken as denigration, I'm saying this from a perspective
of admiration. I think this is a fairly elegant way to solve a difficult
problem, and learning Rust has been a true pleasure. I'm excited for the
future of the language.

------
buster
Relevant Reddit discussion (since the rust subreddit seems to be the most
active place of rustaceans):

[http://www.reddit.com/r/rust/comments/2jgck9/on_error_handli...](http://www.reddit.com/r/rust/comments/2jgck9/on_error_handling_in_rust_armin_ronachers/)

------
sergiosgc
This is a sugar-coated version of errors as return values. It fixes the most
glaring problem, which is code that does not check for errors, common in C:

    
    
         FILE* f = fopen("somefile", "r");
         fwrite("aha", 4, 1, f); // <-- Unchecked use of f here
    

In Rust (pardon my rust, I'm totally ignorant), it'd be:

    
    
        let f = fopen("somefile", "r");
        fwrite("aha", f)?;
    

The compiler would know that fopen may return an error, and forbid me from
running unchecked code. Nice!

I'd miss, however, the ability to handle all errors occurring from a segment
of code in the same place, stuff that exceptions allow for. Pythonish example

    
    
        try: 
            db = pgsql.connect(localhost)
            stmt = db.prepare('INSERT INTO log VALUES(?,?)')
            stmt.execute(('debug', 'Note to self: debug logs are noncritical'))
        except Exception,e:
            console.write('Could not write log to database %s' % (str(e)))
    

Sometimes error recovery is the same for all of the segment. I realize one
could extract a function for the commonly recovered code, but this may lead to
a too-many-small-functions-with-one-caller(tm) smell.

Exceptions aren't the only way to achieve this. If Rust wants to keep return
values as the error mechanism, perhaps it could find a way of allowing
recovery to happen in the same place for a segment of code.

~~~
krig
Maybe I am missing some subtleity here, but isn't the whole point of the
described proposed feature that you would be able to replicate the Python code
pretty much exactly?

    
    
      fn read_database() -> Result<Document, DatabaseError> {
        let db = pgsql.connect(localhost)?
        let stmt = db.prepare("INSERT INTO log VALUES(?,?)")?
        stmt.execute(("debug", "Note to self: debug logs are noncritical"))?
      }
    

So to have the handling code there as well, wrap it in an outer function (I
have no idea if this is anywhere close to actual Rust):

    
    
      fn read_database() {
        fn reader() -> Result<Document, DatabaseError>
          let db = pgsql.connect(localhost)?
          let stmt = db.prepare("INSERT INTO log VALUES(?,?)")?
          stmt.execute(("debug", "Note to self: debug logs are noncritical"))?
        }
        match reader() {
          Document(d) => d,
          DatabaseError(err) => println!("Could not write log to database {}", err)
        }
      }

~~~
sergiosgc
Note my mention of the too-many-small-functions-with-one-caller(tm) smell. It
refers to this solution.

~~~
adrusi
It would be inlined by the compiler, and the standard library could introduce
a macro to eliminate the awkward code.

    
    
        try!({
          let db = pgsql.connect(localhost)?
          let stmt = db.prepare("INSERT INTO log VALUES(?,?)")?
          stmt.execute(("debug", "Note to self: debug logs are noncritical"))?
        } catch err: DatabaseError {
          println!("Could not write log to database {}", err);
        })
    

===>

    
    
        match (|| {
          let db = pgsql.connect(localhost)?
          let stmt = db.prepare("INSERT INTO log VALUES(?,?)")?
          stmt.execute(("debug", "Note to self: debug logs are noncritical"))?
        }) {
          Document(d) => d,
          DatabaseError(err) => println!("Could not write log to database {}", err)
        }

~~~
ben0x539
Closures aren't "free", you need to structure your code around being unable to
return/break/continue across the closure boundary, and there's probably some
silly borrow checker errors involved too.

------
kibwen
For those who don't closely follow the topic of language design, error
handling is actually a fascinatingly diverse subject. Exceptions are not the
end of the story, and it's great to see languages explore this space.

Microsoft's upcoming M# language has also teased an interesting approach to
error-handling. I'm very curious to see what they come up with.

~~~
ufo
What is it? I never heard of M#

~~~
pjmlp
It was heavily discussed last year.

[http://lambda-the-ultimate.org/node/4862](http://lambda-the-
ultimate.org/node/4862)

[http://joeduffyblog.com/2013/12/27/csharp-for-systems-
progra...](http://joeduffyblog.com/2013/12/27/csharp-for-systems-programming/)

Is a variant of C# targeted for systems programming, developed at Microsoft
Research. It remains to be seen if it will ever be made public.

------
jeremyjh
I hope we get some sugar here, the ? operator is a great idea. I like the
trait implementation as well. I think Rust needs multiple dispatch though - a
given type needs to support conversions of IOError to different custom results
right? So you can use a struct in different library functions that may return
a different custom error. Or maybe I am missing something here; but I do
believe multiple dispatch is planned for 1.0.

I did this in Haskell using MPTC, I have used the Convertible class like:

instance Convertible IOError RedisError where safeConvert = <conversion
mapping here>

Then I have combinators like:

convEither :: Convertible e1 e2 => Either e1 a -> Either e2 a

and

convEitherT :: Convertible e1 e2 => m (Either e1 a) -> EitherT m e2 a

These are quite handy in close quarters combat. For example to open a file and
bail on a generic IOException with a custom application exception, I can have
something like:

instance Convertible IOException CustomError

getWords :: FilePath -> IO (Either CustomError [String])

getWords fileName = runEitherT $

    
    
        do file <- tryIO (openFile fileName) >>= convEitherT
    
           line <- readLine file 
           return (words line)

~~~
m0th87
Doesn't multiple dispatch require run-time type analysis? If so wouldn't that
go against Rust's philosophy of zero-cost abstractions?

~~~
carterschonwald
nope, its all compile time. its just that instead of deciding which impl to
pick based upon information X, youre using a pair of pieces of information
(X,Y)

Edit, also I think the "convertable" class idea is nearly expressible using
associated types in rust today,but im not 100% certain about that

~~~
m0th87
Wikipedia implies that multiple dispatch is done by run-time time analysis in
OO languages, which does not apply to Haskell or Rust:
[https://en.wikipedia.org/wiki/Multiple_dispatch](https://en.wikipedia.org/wiki/Multiple_dispatch)

Maybe it's being pedantic, but I can't tell. Are Haskell's type classes
strictly as flexible as multiple dispatch?

~~~
twic
The proposed Rust feature sounds like overloading rather than multiple
dispatch as it is usually understood.

------
BruceIV
The Rust RFC[1] linked in the article is also super neat; it looks basically
like a way to build a checked-exceptions like syntax in a way that meshes
naturally with Rust's existing semantics and types, as well as the ? operator
presented in the blog post.

[1] [https://github.com/glaebhoerl/rfcs/blob/trait-based-
exceptio...](https://github.com/glaebhoerl/rfcs/blob/trait-based-exception-
handling/active/0000-trait-based-exception-handling.md)

------
eximius
Wow, I really, really like that `?` operator idea. I haven't quite dived into
Rust yet (I'm watching and waiting for v1.0), but I would be really excited to
work with a language that makes error handing so easy and safe.

~~~
tome
You could try Haskell.

~~~
eximius
I have. I've learned a lot, but I just don't have any projects right now that
make Haskell a great. That isn't to say I couldn't write them in Haskell, but
my current projects are writing code I'm unfamiliar with, so I don't want the
double whammy of struggling with the logic and structure AND learning the
language.

------
echaozh
So it's a specialized mapping operator for the Result functor. Why restrict it
to Result only? What about using option to denote a failure condition without
a specific reason? Or other kinds of interesting functors?

~~~
masklinn
> What about using option to denote a failure condition without a specific
> reason?

Result is set up and marked to mandate its use, the compiler will complain if
you ignore the result of a function returning a Result, not so for an Option.
So idiomatically denoting a failure condition without a specific reason is
Result<T, ()> not Option<T>: [http://doc.rust-lang.org/std/result/#result-and-
option](http://doc.rust-lang.org/std/result/#result-and-option).

Option<T> is for what it's name denotes, a value which may or may not be
present (e.g. an optional field in a struct).

~~~
steveklabnik
> Result is set up and marked to mandate its use,

To elaborate on this slightly, it's not that Result is special cased by the
compiler, there's a 'must_use' attribute which ensures that you get a warning
if you ignore it, and the definition of Result has it.

------
jeffdavis
Wow, these things would be great! I have been working with database
connections, and they feel pretty awkward right now because there are so many
opportunities for errors.

Either or both of these would be very appealing.

I remember asking on IRC a few times to see if I could get some interest. One
of the things I proposed was a postfix operator in place of try, so I'm
pleased to see something like that appear.

For some reason it just really bothered me to see all of my statements begin
with "try!" rather than what they are actually intended to do. The "?" could
be a great help.

------
zubspace
I' m someone who hasn't tried rust, yet. Is FromError the same like inner
exceptions in C#? I'd like to dive into rust once, but somehow I do not see
the elegance in the code shown.

I mostly do _not_ care about the type of error condition and handle the error
somewhere really far up the stack (log or messagebox) and provide the ability
for a retry. Can i do something like a general try/catch far up the stack? in
my opinion, this neatly prevents code from beeing littered with error handling
which i very much prefer.

~~~
steveklabnik
> Can i do something like a general try/catch far up the stack?

Rust does not have exceptions, so not exactly. You _could_ spin up a new task
(thread) with the code, and then monitor that task for failure.

------
JoshTriplett
This seems quite nice. With traits, this could become fully generic, as well:
with a generic version of FromError, you could try! a function returning
Result<R, E> for any error E.

------
benjaminjackman
I am not that familiar with Rust, however every time I see it I am more and
more impressed.

One thing that I don't see here but which I think it might be important is to
grab the stack trace in the failure, or atleast the Filename / Line Number of
where each Err is allocated. It will be really useful to be able to see the
details for figuring out what went wrong, and following the stack trace is
very important. With that I think the abstraction is 99%+ superior to
exceptions, which is quite a step forward for the programming craft.

What really has to be guarded against though is that a lot of functions are
going either have function signature that is much more verbose which leads
programmers `cheating` and just `swallowing` the error by forcing the result
out -OR- Return type inference might save the day (not sure if Rust has return
type inference of not), however it doesn't solve everything, writing the
return types on public functions pretty quickly becomes standard operating
procedure, and then programmers are going to get lazy again and we are right
back at checked exceptions `polluting` their interfaces and they might not
want to deal with it.

    
    
      interface Perfect {
        def foo : Foo
        def bar : Bar
      }
    
      interface NotPerfect {
       def foo : Result[Foo, FooErr]
       def bar : Result[Bar, BarErr]
      }
    

Which of these do I implement first? Does the type system allow me to
substitute a Perfect when a NotPerfect is required, or do I have to re-teach
it every single time, that in fact, it's totally ok to do so (make a method
`perfectCanBeUsedForNotPerfect(p : Perfect) : NotPerfect = new NotPerfect
....)`? Can I fix the issue for a limited set of cases and declare that
Perfect implements NotPerfect and is therefore a subtype? Can I do similarly
if I implement NotPerfect after Perfect was implemented (add an implicit
conversion from Perfect to NotPerfect)? Can I just avoid the whole problem
all-together with by-name subtyping (aka Point3{x,y,z} is considered a subtype
of Point2{x,y} because Point3 has all the same named fields as Point2 with an
extra one added) and informing the type system of the fact that Foo is a valid
substitution for Result[Foo,_] because Result[A,B <: Err] is a type-
disjunction of A | B and type-disjunction allows for substitution by the
disjoint types? Where is Rust going to fall on this continuum and how many
programmer hours is going to waste for each and every one of them to teach the
its type-system over and over and over these simple patterns via boilerplate
code?

We static programmers will grumble on regardless, we pay our dues to the type
checker because we believe its gives us structure and forces failure when
while we write the code, not because we want to write boilerplate to teach it
simple facts.

...

All in all this is really cool. From a Scala perspective it's a much improved
version of Either (and now Try), and it's going to be supported by the
standard library in Rust. Which is awesome.

Either itself basically is Scala saying, we don't have type disjuction, so
here is a disjunction of 2 types which we will call Left and Right. And by
convention Left is used to hold the error condition.

Because no one could remember that Left by convention meant error, and because
it would by nice to gussy up what is held in the error type by having your
errors be able to point a source error, Scala then later added a Try
disjunction. Which is composed of two types Success[A](a:A) and Failure(e :
Throwable).

The Success part is fine it's hard to mess that part up, it's just like Ok,
having a uniform this just a wrapper around another type would be nice but, we
suck that down as programmer business as usual, the Failure side however
leaves a lot to be desired, by DESIGN the failure side cannot hold a failure
and instead has to hold an exception, this is wrong, failure is a failure.
Given the runtime on which Scala runs, the JVM, ignoring exceptions all-
together is probably a worse evil. However it should have been made a special
subcase of the Failure subclass rather than the only thing it can contain, or
Try itself should have been the sub-implemenation of some more generic
structure.

What to add a custom message to your failure? Ok make an exception that has a
message in it.

Want to pass a Failure up the chain while adding your own error message for
context? Ok make an exception then add the failure into it.

Want to fail without an exception? Ok make an exception and put it in the
Failure.

Looks like Rust is moving towards a great implementation. This is something
that has to be done in the standard library and has to be done well. The more
I see of Rust as a language the more impressed I am, when is Rust going to
compile to Javascript, oh never I see[1], well back to my ScalaJS cave then :)

1:[https://news.ycombinator.com/item?id=4630403](https://news.ycombinator.com/item?id=4630403)

~~~
steveklabnik
Rust used to also have Either, and it was changed to Result for the same
reasons. Name things what they mean. :)

~~~
kibwen
Funnily enough, we actually used to have both Either _and_ Result, until one
day we went through and realized that no code in existence was using Either
and decided to go all-in on Result instead.

~~~
steveklabnik
Someday, we should have a "History of Rust" thing, kinda like folklore.org or
something.

~~~
kibwen
I'd love to compile such a thing, but doing it properly would involve both
cataloguing every change _and_ recording the rationale for each. And because
the rationale for changes is so dependent on the context of the-language-as-
it-was-at-that-moment-in-time, random access of the archive would leave your
understanding incomplete. For example, I just said that we removed `Either`
because nobody was using it and because it was structurally a complete
duplicate of `Result`... but I omitted the fact that `Result` was almost
universally shunned at the time as well, in favor of `Option`. :P

Part of me wonders if the best way to do it wouldn't be to to start at Rust
1.0 and go on a tour backwards through history, i.e. "here's a great language,
now let's all make terrible decisions to make it worse!" :)

