Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove std::condition #12039

Merged
merged 1 commit into from
Feb 7, 2014
Merged

Remove std::condition #12039

merged 1 commit into from
Feb 7, 2014

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
Member

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

@alexcrichton \o/

@steveklabnik
Copy link
Member

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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Decide whether to keep conditions str::not_utf8 condition is a sign that the from_bytes API needs massaging
8 participants