Skip to content

Conversation

alexcrichton
Copy link
Member

This has been a long time coming. Conditions in rust were initially envisioned
as being a good alternative to error code return pattern. The idea is that all
errors are fatal-by-default, and you can opt-in to handling the error by
registering an error handler.

While sounding nice, conditions ended up having some unforseen shortcomings:

  • Actually handling an error has some very awkward syntax:

    let mut result = None; let mut answer = None; io::io_error::cond.trap(|e| { result = Some(e) }).inside(|| { answer = Some(some_io_operation()); }); match result { Some(err) => { /* hit an I/O error */ } None => { let answer = answer.unwrap(); /* deal with the result of I/O */ } } 

    This pattern can certainly use functions like io::result, but at its core
    actually handling conditions is fairly difficult

  • The "zero value" of a function is often confusing. One of the main ideas
    behind using conditions was to change the signature of I/O functions. Instead
    of read_be_u32() returning a result, it returned a u32. Errors were notified
    via a condition, and if you caught the condition you understood that the "zero
    value" returned is actually a garbage value. These zero values are often
    difficult to understand, however.

    One case of this is the read_bytes() function. The function takes an integer
    length of the amount of bytes to read, and returns an array of that size. The
    array may actually be shorter, however, if an error occurred.

    Another case is fs::stat(). The theoretical "zero value" is a blank stat
    struct, but it's a little awkward to create and return a zero'd out stat
    struct on a call to stat().

    In general, the return value of functions that can raise error are much more
    natural when using a Result as opposed to an always-usable zero-value.

  • Conditions impose a necessary runtime requirement on all I/O. In theory I/O
    is as simple as calling read() and write(), but using conditions imposed the
    restriction that a rust local task was required if you wanted to catch errors
    with I/O. While certainly an surmountable difficulty, this was always a bit of
    a thorn in the side of conditions.

  • Functions raising conditions are not always clear that they are raising
    conditions. This suffers a similar problem to exceptions where you don't
    actually know whether a function raises a condition or not. The documentation
    likely explains, but if someone retroactively adds a condition to a function
    there's nothing forcing upstream users to acknowledge a new point of task
    failure.

  • Libaries using I/O are not guaranteed to correctly raise on conditions when an
    error occurs. In developing various I/O libraries, it's much easier to just
    return None from a read rather than raising an error. The silent contract of
    "don't raise on EOF" was a little difficult to understand and threw a wrench
    into the answer of the question "when do I raise a condition?"

Many of these difficulties can be overcome through documentation, examples, and
general practice. In the end, all of these difficulties added together ended up
being too overwhelming and improving various aspects didn't end up helping that
much.

A result-based I/O error handling strategy also has shortcomings, but the
cognitive burden is much smaller. The tooling necessary to make this strategy as
usable as conditions were is much smaller than the tooling necessary for
conditions.

Perhaps conditions may manifest themselves as a future entity, but for now
we're going to remove them from the standard library.

Closes #9795
Closes #8968

@huonw
Copy link
Contributor

huonw commented Feb 5, 2014

The "conditions" guide includes error handling other than conditions; worth keeping?

@alexcrichton
Copy link
Member Author

It's true. There's a few ways to go about this:

  • Remove the guide altogether (losing some good content).
  • Leave the guide as-is (documenting outdated content)
  • Update the guide

Clearly the best option is to update the guide, but I think that we need some more mileage with the new lines/results before writing a new guide.

@flaper87
Copy link
Contributor

flaper87 commented Feb 5, 2014

I'd go with option 2 and 3. We could keep the guide as-is just marking the
outdated pieces and then file an issue to remind us that it should be
rewritten.

@adrientetar
Copy link
Contributor

The Condition Guide isn't only only conditions: "Conditions and Error Handling" talks about Option etc...

This is a useful resource, it should be kept and updated imo.

@bnoordhuis
Copy link
Contributor

We've given conditions a run for their money but they turned out to not quite be worth it.

Alex, can you elaborate on that? FWIW, conditions are one of the things I like best about Rust. For me, they hit the sweet spot between the headache that is exception stack unwinding and the nuisance of return code-based error handling.

(EDIT: If nothing else, can I suggest updating the commit log with the rationale?)

@alexcrichton
Copy link
Member Author

@bnoordhuis: I've updated the commit message. If that doesn't satisfy you, I'm more than willing to explain more!

@adridu59, @flaper87: for now, I think the best thing is to remove it. I'll try to write a new one based on results/lints after this lands.

@adrientetar
Copy link
Contributor

@steveklabnik
Copy link
Contributor

I'm in favor of just nuking the conditions guide and re-doing it as error handling proper, rather than trying to shoe-horn what's already here into something that's good by itself.

@flaper87
Copy link
Contributor

flaper87 commented Feb 5, 2014

@alexcrichton @steveklabnik sounds good to me!

This has been a long time coming. Conditions in rust were initially envisioned as being a good alternative to error code return pattern. The idea is that all errors are fatal-by-default, and you can opt-in to handling the error by registering an error handler. While sounding nice, conditions ended up having some unforseen shortcomings: * Actually handling an error has some very awkward syntax: let mut result = None; let mut answer = None; io::io_error::cond.trap(|e| { result = Some(e) }).inside(|| { answer = Some(some_io_operation()); }); match result { Some(err) => { /* hit an I/O error */ } None => { let answer = answer.unwrap(); /* deal with the result of I/O */ } } This pattern can certainly use functions like io::result, but at its core actually handling conditions is fairly difficult * The "zero value" of a function is often confusing. One of the main ideas behind using conditions was to change the signature of I/O functions. Instead of read_be_u32() returning a result, it returned a u32. Errors were notified via a condition, and if you caught the condition you understood that the "zero value" returned is actually a garbage value. These zero values are often difficult to understand, however. One case of this is the read_bytes() function. The function takes an integer length of the amount of bytes to read, and returns an array of that size. The array may actually be shorter, however, if an error occurred. Another case is fs::stat(). The theoretical "zero value" is a blank stat struct, but it's a little awkward to create and return a zero'd out stat struct on a call to stat(). In general, the return value of functions that can raise error are much more natural when using a Result as opposed to an always-usable zero-value. * Conditions impose a necessary runtime requirement on *all* I/O. In theory I/O is as simple as calling read() and write(), but using conditions imposed the restriction that a rust local task was required if you wanted to catch errors with I/O. While certainly an surmountable difficulty, this was always a bit of a thorn in the side of conditions. * Functions raising conditions are not always clear that they are raising conditions. This suffers a similar problem to exceptions where you don't actually know whether a function raises a condition or not. The documentation likely explains, but if someone retroactively adds a condition to a function there's nothing forcing upstream users to acknowledge a new point of task failure. * Libaries using I/O are not guaranteed to correctly raise on conditions when an error occurs. In developing various I/O libraries, it's much easier to just return `None` from a read rather than raising an error. The silent contract of "don't raise on EOF" was a little difficult to understand and threw a wrench into the answer of the question "when do I raise a condition?" Many of these difficulties can be overcome through documentation, examples, and general practice. In the end, all of these difficulties added together ended up being too overwhelming and improving various aspects didn't end up helping that much. A result-based I/O error handling strategy also has shortcomings, but the cognitive burden is much smaller. The tooling necessary to make this strategy as usable as conditions were is much smaller than the tooling necessary for conditions. Perhaps conditions may manifest themselves as a future entity, but for now we're going to remove them from the standard library. Closes rust-lang#9795 Closes rust-lang#8968
bors added a commit that referenced this pull request Feb 7, 2014
This has been a long time coming. Conditions in rust were initially envisioned as being a good alternative to error code return pattern. The idea is that all errors are fatal-by-default, and you can opt-in to handling the error by registering an error handler. While sounding nice, conditions ended up having some unforseen shortcomings: * Actually handling an error has some very awkward syntax: let mut result = None; let mut answer = None; io::io_error::cond.trap(|e| { result = Some(e) }).inside(|| { answer = Some(some_io_operation()); }); match result { Some(err) => { /* hit an I/O error */ } None => { let answer = answer.unwrap(); /* deal with the result of I/O */ } } This pattern can certainly use functions like io::result, but at its core actually handling conditions is fairly difficult * The "zero value" of a function is often confusing. One of the main ideas behind using conditions was to change the signature of I/O functions. Instead of read_be_u32() returning a result, it returned a u32. Errors were notified via a condition, and if you caught the condition you understood that the "zero value" returned is actually a garbage value. These zero values are often difficult to understand, however. One case of this is the read_bytes() function. The function takes an integer length of the amount of bytes to read, and returns an array of that size. The array may actually be shorter, however, if an error occurred. Another case is fs::stat(). The theoretical "zero value" is a blank stat struct, but it's a little awkward to create and return a zero'd out stat struct on a call to stat(). In general, the return value of functions that can raise error are much more natural when using a Result as opposed to an always-usable zero-value. * Conditions impose a necessary runtime requirement on *all* I/O. In theory I/O is as simple as calling read() and write(), but using conditions imposed the restriction that a rust local task was required if you wanted to catch errors with I/O. While certainly an surmountable difficulty, this was always a bit of a thorn in the side of conditions. * Functions raising conditions are not always clear that they are raising conditions. This suffers a similar problem to exceptions where you don't actually know whether a function raises a condition or not. The documentation likely explains, but if someone retroactively adds a condition to a function there's nothing forcing upstream users to acknowledge a new point of task failure. * Libaries using I/O are not guaranteed to correctly raise on conditions when an error occurs. In developing various I/O libraries, it's much easier to just return `None` from a read rather than raising an error. The silent contract of "don't raise on EOF" was a little difficult to understand and threw a wrench into the answer of the question "when do I raise a condition?" Many of these difficulties can be overcome through documentation, examples, and general practice. In the end, all of these difficulties added together ended up being too overwhelming and improving various aspects didn't end up helping that much. A result-based I/O error handling strategy also has shortcomings, but the cognitive burden is much smaller. The tooling necessary to make this strategy as usable as conditions were is much smaller than the tooling necessary for conditions. Perhaps conditions may manifest themselves as a future entity, but for now we're going to remove them from the standard library. Closes #9795 Closes #8968
@bors bors closed this Feb 7, 2014
@bors bors merged commit 454882d into rust-lang:master Feb 7, 2014
@alexcrichton alexcrichton deleted the no-conditions branch February 7, 2014 07:44
@olivren
Copy link
Contributor

olivren commented Feb 7, 2014

I really appreciate that the motivations behind the removal of this feature is explained so clearly. Thanks for that !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

8 participants