Remove std::condition

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
This commit is contained in:
Alex Crichton 2014-02-04 19:02:10 -08:00
parent f039d10cf7
commit 454882dcb7
23 changed files with 91 additions and 1771 deletions

View File

@ -205,12 +205,6 @@ doc/guide-tasks.html: $(D)/guide-tasks.md $(HTML_DEPS)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
DOCS += doc/guide-conditions.html
doc/guide-conditions.html: $(D)/guide-conditions.md $(HTML_DEPS)
@$(call E, pandoc: $@)
$(Q)$(CFG_NODE) $(D)/prep.js --highlight $< | \
$(CFG_PANDOC) $(HTML_OPTS) --output=$@
DOCS += doc/guide-pointers.html
doc/guide-pointers.html: $(D)/guide-pointers.md $(HTML_DEPS)
@$(call E, pandoc: $@)

View File

@ -21,7 +21,7 @@ TEST_CRATES = $(TEST_TARGET_CRATES) $(TEST_HOST_CRATES)
# Markdown files under doc/ that should have their code extracted and run
DOC_TEST_NAMES = tutorial guide-ffi guide-macros guide-lifetimes \
guide-tasks guide-conditions guide-container guide-pointers \
guide-tasks guide-container guide-pointers \
complement-cheatsheet guide-runtime \
rust

View File

@ -1,837 +0,0 @@
% The Rust Condition and Error-handling Guide
# Introduction
Rust does not provide exception handling[^why-no-exceptions]
in the form most commonly seen in other programming languages such as C++ or Java.
Instead, it provides four mechanisms that work together to handle errors or other rare events.
The four mechanisms are:
- Options
- Results
- Failure
- Conditions
This guide will lead you through use of these mechanisms
in order to understand the trade-offs of each and relationships between them.
# Example program
This guide will be based around an example program
that attempts to read lines from a file
consisting of pairs of numbers,
and then print them back out with slightly different formatting.
The input to the program might look like this:
~~~~ {.notrust}
$ cat numbers.txt
1 2
34 56
789 123
45 67
~~~~
For which the intended output looks like this:
~~~~ {.notrust}
$ ./example numbers.txt
0001, 0002
0034, 0056
0789, 0123
0045, 0067
~~~~
An example program that does this task reads like this:
~~~~
# #[allow(unused_imports)];
use std::io::{BufferedReader, File};
# mod BufferedReader {
# use std::io::{File, IoResult};
# use std::io::MemReader;
# use std::io::BufferedReader;
# static s : &'static [u8] = bytes!("1 2\n\
# 34 56\n\
# 789 123\n\
# 45 67\n\
# ");
# pub fn new(_inner: IoResult<File>) -> BufferedReader<MemReader> {
# BufferedReader::new(MemReader::new(s.to_owned()))
# }
# }
fn main() {
let pairs = read_int_pairs();
for &(a,b) in pairs.iter() {
println!("{:4.4d}, {:4.4d}", a, b);
}
}
fn read_int_pairs() -> ~[(int,int)] {
let mut pairs = ~[];
// Path takes a generic by-value, rather than by reference
let path = Path::new(&"foo.txt");
let mut reader = BufferedReader::new(File::open(&path));
// 1. Iterate over the lines of our file.
for line in reader.lines() {
// 2. Split the line into fields ("words").
let fields = line.words().to_owned_vec();
// 3. Match the vector of fields against a vector pattern.
match fields {
// 4. When the line had two fields:
[a, b] => {
// 5. Try parsing both fields as ints.
match (from_str::<int>(a), from_str::<int>(b)) {
// 6. If parsing succeeded for both, push both.
(Some(a), Some(b)) => pairs.push((a,b)),
// 7. Ignore non-int fields.
_ => ()
}
}
// 8. Ignore lines that don't have 2 fields.
_ => ()
}
}
pairs
}
~~~~
This example shows the use of `Option`,
along with some other forms of error-handling (and non-handling).
We will look at these mechanisms
and then modify parts of the example to perform "better" error handling.
# Options
The simplest and most lightweight mechanism in Rust for indicating an error is the type `std::option::Option<T>`.
This type is a general purpose `enum`
for conveying a value of type `T`, represented as `Some(T)`
_or_ the sentinel `None`, to indicate the absence of a `T` value.
For simple APIs, it may be sufficient to encode errors as `Option<T>`,
returning `Some(T)` on success and `None` on error.
In the example program, the call to `from_str::<int>` returns `Option<int>`
with the understanding that "all parse errors" result in `None`.
The resulting `Option<int>` values are matched against the pattern `(Some(a), Some(b))`
in steps 5 and 6 in the example program,
to handle the case in which both fields were parsed successfully.
Using `Option` as in this API has some advantages:
- Simple API, users can read it and guess how it works.
- Very efficient, only an extra `enum` tag on return values.
- Caller has flexibility in handling or propagating errors.
- Caller is forced to acknowledge existence of possible-error before using value.
However, it has serious disadvantages too:
- Verbose, requires matching results or calling `Option::unwrap` everywhere.
- Infects caller: if caller doesn't know how to handle the error, must propagate (or force).
- Temptation to do just that: force the `Some(T)` case by blindly calling `unwrap`,
which hides the error from the API without providing any way to make the program robust against the error.
- Collapses all errors into one:
- Caller can't handle different errors differently.
- Caller can't even report a very precise error message
Note that in order to keep the example code reasonably compact,
several unwanted cases are silently ignored:
lines that do not contain two fields, as well as fields that do not parse as ints.
To propagate these cases to the caller using `Option` would require even more verbose code.
# Results
Before getting into _trapping_ the error,
we will look at a slight refinement on the `Option` type above.
This second mechanism for indicating an error is called a `Result`.
The type `std::result::Result<T,E>` is another simple `enum` type with two forms, `Ok(T)` and `Err(E)`.
The `Result` type is not substantially different from the `Option` type in terms of its ergonomics.
Its main advantage is that the error constructor `Err(E)` can convey _more detail_ about the error.
For example, the `from_str` API could be reformed
to return a `Result` carrying an informative description of a parse error,
like this:
~~~~ {.ignore}
enum IntParseErr {
EmptyInput,
Overflow,
BadChar(char)
}
fn from_str(&str) -> Result<int,IntParseErr> {
// ...
}
~~~~
This would give the caller more information for both handling and reporting the error,
but would otherwise retain the verbosity problems of using `Option`.
In particular, it would still be necessary for the caller to return a further `Result` to _its_ caller if it did not want to handle the error.
Manually propagating result values this way can be attractive in certain circumstances
&mdash; for example when processing must halt on the very first error, or backtrack &mdash;
but as we will see later, many cases have simpler options available.
# Failure
The third and arguably easiest mechanism for handling errors is called "failure".
In fact it was hinted at earlier by suggesting that one can choose to propagate `Option` or `Result` types _or "force" them_.
"Forcing" them, in this case, means calling a method like `Option<T>::unwrap`,
which contains the following code:
~~~~ {.ignore}
pub fn unwrap(self) -> T {
match self {
Some(x) => return x,
None => fail!("option::unwrap `None`")
}
}
~~~~
That is, it returns `T` when `self` is `Some(T)`, and _fails_ when `self` is `None`.
Every Rust task can _fail_, either indirectly due to a kill signal or other asynchronous event,
or directly by failing an `assert!` or calling the `fail!` macro.
Failure is an _unrecoverable event_ at the task level:
it causes the task to halt normal execution and unwind its control stack,
freeing all task-local resources (the local heap as well as any task-owned values from the global heap)
and running destructors (the `drop` method of the `Drop` trait)
as frames are unwound and heap values destroyed.
A failing task is not permitted to "catch" the unwinding during failure and recover,
it is only allowed to clean up and exit.
Failure has advantages:
- Simple and non-verbose. Suitable for programs that can't reasonably continue past an error anyways.
- _All_ errors (except memory-safety errors) can be uniformly trapped in a supervisory task outside the failing task.
For a large program to be robust against a variety of errors,
often some form of task-level partitioning to contain pervasive errors (arithmetic overflow, division by zero,
logic bugs) is necessary anyways.
As well as obvious disadvantages:
- A blunt instrument, terminates the containing task entirely.
Recall that in the first two approaches to error handling,
the example program was only handling success cases, and ignoring error cases.
That is, if the input is changed to contain a malformed line:
~~~~ {.notrust}
$ cat bad.txt
1 2
34 56
ostrich
789 123
45 67
~~~~
Then the program would give the same output as if there was no error:
~~~~ {.notrust}
$ ./example bad.txt
0001, 0002
0034, 0056
0789, 0123
0045, 0067
~~~~
If the example is rewritten to use failure, these error cases can be trapped.
In this rewriting, failures are trapped by placing the I/O logic in a sub-task,
and trapping its exit status using `task::try`:
~~~~
# #[allow(unused_imports)];
use std::io::{BufferedReader, File};
use std::task;
# mod BufferedReader {
# use std::io::{File, IoResult};
# use std::io::MemReader;
# use std::io::BufferedReader;
# static s : &'static [u8] = bytes!("1 2\n\
# 34 56\n\
# 789 123\n\
# 45 67\n\
# ");
# pub fn new(_inner: IoResult<File>) -> BufferedReader<MemReader> {
# BufferedReader::new(MemReader::new(s.to_owned()))
# }
# }
fn main() {
// Isolate failure within a subtask.
let result = task::try(proc() {
// The protected logic.
let pairs = read_int_pairs();
for &(a,b) in pairs.iter() {
println!("{:4.4d}, {:4.4d}", a, b);
}
});
if result.is_err() {
println!("parsing failed");
}
}
fn read_int_pairs() -> ~[(int,int)] {
let mut pairs = ~[];
let path = Path::new(&"foo.txt");
let mut reader = BufferedReader::new(File::open(&path));
for line in reader.lines() {
match line.words().to_owned_vec() {
[a, b] => pairs.push((from_str::<int>(a).unwrap(),
from_str::<int>(b).unwrap())),
// Explicitly fail on malformed lines.
_ => fail!()
}
}
pairs
}
~~~~
With these changes in place, running the program on malformed input gives a different answer:
~~~~ {.notrust}
$ ./example bad.txt
rust: task failed at 'explicit failure', ./example.rs:44
parsing failed
~~~~
Note that while failure unwinds the sub-task performing I/O in `read_int_pairs`,
control returns to `main` and can easily continue uninterrupted.
In this case, control simply prints out `parsing failed` and then exits `main` (successfully).
Failure of a (sub-)task is analogous to calling `exit(1)` or `abort()` in a unix C program:
all the state of a sub-task is cleanly discarded on exit,
and a supervisor task can take appropriate action
without worrying about its own state having been corrupted.
# Conditions
The final mechanism for handling errors is called a "condition".
Conditions are less blunt than failure, and less cumbersome than the `Option` or `Result` types;
indeed they are designed to strike just the right balance between the two.
Conditions require some care to use effectively, but give maximum flexibility with minimum verbosity.
While conditions use exception-like terminology ("trap", "raise") they are significantly different:
- Like exceptions and failure, conditions separate the site at which the error is raised from the site where it is trapped.
- Unlike exceptions and unlike failure, when a condition is raised and trapped, _no unwinding occurs_.
- A successfully trapped condition causes execution to continue _at the site of the error_, as though no error occurred.
Conditions are declared with the `condition!` macro.
Each condition has a name, an input type and an output type, much like a function.
In fact, conditions are implemented as dynamically-scoped functions held in task local storage.
The `condition!` macro declares a module with the name of the condition;
the module contains a single static value called `cond`, of type `std::condition::Condition`.
The `cond` value within the module is the rendezvous point
between the site of error and the site that handles the error.
It has two methods of interest: `raise` and `trap`.
The `raise` method maps a value of the condition's input type to its output type.
The input type should therefore convey all relevant information to the condition handler.
The output type should convey all relevant information _for continuing execution at the site of error_.
When the error site raises a condition handler,
the `Condition::raise` method searches for the innermost installed task-local condition _handler_,
and if any such handler is found, calls it with the provided input value.
If no handler is found, `Condition::raise` will fail the task with an appropriate error message.
Rewriting the example to use a condition in place of ignoring malformed lines makes it slightly longer,
but similarly clear as the version that used `fail!` in the logic where the error occurs:
~~~~
# #[allow(unused_imports)];
use std::io::{BufferedReader, File};
# mod BufferedReader {
# use std::io::{File, IoResult};
# use std::io::MemReader;
# use std::io::BufferedReader;
# static s : &'static [u8] = bytes!("1 2\n\
# 34 56\n\
# 789 123\n\
# 45 67\n\
# ");
# pub fn new(_inner: IoResult<File>) -> BufferedReader<MemReader> {
# BufferedReader::new(MemReader::new(s.to_owned()))
# }
# }
// Introduce a new condition.
condition! {
pub malformed_line : ~str -> (int,int);
}
fn main() {
let pairs = read_int_pairs();
for &(a,b) in pairs.iter() {
println!("{:4.4d}, {:4.4d}", a, b);
}
}
fn read_int_pairs() -> ~[(int,int)] {
let mut pairs = ~[];
let path = Path::new(&"foo.txt");
let mut reader = BufferedReader::new(File::open(&path));
for line in reader.lines() {
match line.words().to_owned_vec() {
[a, b] => pairs.push((from_str::<int>(a).unwrap(),
from_str::<int>(b).unwrap())),
// On malformed lines, call the condition handler and
// push whatever the condition handler returns.
_ => pairs.push(malformed_line::cond.raise(line.clone()))
}
}
pairs
}
~~~~
When this is run on malformed input, it still fails,
but with a slightly different failure message than before:
~~~~ {.notrust}
$ ./example bad.txt
rust: task failed at 'Unhandled condition: malformed_line: ~"ostrich"', .../libstd/condition.rs:43
~~~~
While this superficially resembles the trapped `fail!` call before,
it is only because the example did not install a handler for the condition.
The different failure message is indicating, among other things,
that the condition-handling system is being invoked and failing
only due to the absence of a _handler_ that traps the condition.
# Trapping a condition
To trap a condition, use `Condition::trap` in some caller of the site that calls `Condition::raise`.
For example, this version of the program traps the `malformed_line` condition
and replaces bad input lines with the pair `(-1,-1)`:
~~~~
# #[allow(unused_imports)];
use std::io::{BufferedReader, File};
# mod BufferedReader {
# use std::io::{File, IoResult};
# use std::io::MemReader;
# use std::io::BufferedReader;
# static s : &'static [u8] = bytes!("1 2\n\
# 34 56\n\
# 789 123\n\
# 45 67\n\
# ");
# pub fn new(_inner: IoResult<File>) -> BufferedReader<MemReader> {
# BufferedReader::new(MemReader::new(s.to_owned()))
# }
# }
condition! {
pub malformed_line : ~str -> (int,int);
}
fn main() {
// Trap the condition:
malformed_line::cond.trap(|_| (-1,-1)).inside(|| {
// The protected logic.
let pairs = read_int_pairs();
for &(a,b) in pairs.iter() {
println!("{:4.4d}, {:4.4d}", a, b);
}
})
}
fn read_int_pairs() -> ~[(int,int)] {
let mut pairs = ~[];
let path = Path::new(&"foo.txt");
let mut reader = BufferedReader::new(File::open(&path));
for line in reader.lines() {
match line.words().to_owned_vec() {
[a, b] => pairs.push((from_str::<int>(a).unwrap(),
from_str::<int>(b).unwrap())),
_ => pairs.push(malformed_line::cond.raise(line.clone()))
}
}
pairs
}
~~~~
Note that the remainder of the program is _unchanged_ with this trap in place;
only the caller that installs the trap changed.
Yet when the condition-trapping variant runs on the malformed input,
it continues execution past the malformed line, substituting the handler's return value.
~~~~ {.notrust}
$ ./example bad.txt
0001, 0002
0034, 0056
-0001, -0001
0789, 0123
0045, 0067
~~~~
# Refining a condition
As you work with a condition, you may find that the original set of options you present for recovery is insufficient.
This is no different than any other issue of API design:
a condition handler is an API for recovering from the condition, and sometimes APIs need to be enriched.
In the example program, the first form of the `malformed_line` API implicitly assumes that recovery involves a substitute value.
This assumption may not be correct; some callers may wish to skip malformed lines, for example.
Changing the condition's return type from `(int,int)` to `Option<(int,int)>` will suffice to support this type of recovery:
~~~~
# #[allow(unused_imports)];
use std::io::{BufferedReader, File};
# mod BufferedReader {
# use std::io::{IoResult, File};
# use std::io::MemReader;
# use std::io::BufferedReader;
# static s : &'static [u8] = bytes!("1 2\n\
# 34 56\n\
# 789 123\n\
# 45 67\n\
# ");
# pub fn new(_inner: IoResult<File>) -> BufferedReader<MemReader> {
# BufferedReader::new(MemReader::new(s.to_owned()))
# }
# }
// Modify the condition signature to return an Option.
condition! {
pub malformed_line : ~str -> Option<(int,int)>;
}
fn main() {
// Trap the condition and return `None`
malformed_line::cond.trap(|_| None).inside(|| {
// The protected logic.
let pairs = read_int_pairs();
for &(a,b) in pairs.iter() {
println!("{:4.4d}, {:4.4d}", a, b);
}
})
}
fn read_int_pairs() -> ~[(int,int)] {
let mut pairs = ~[];
let path = Path::new(&"foo.txt");
let mut reader = BufferedReader::new(File::open(&path));
for line in reader.lines() {
match line.words().to_owned_vec() {
[a, b] => pairs.push((from_str::<int>(a).unwrap(),
from_str::<int>(b).unwrap())),
// On malformed lines, call the condition handler and
// either ignore the line (if the handler returns `None`)
// or push any `Some(pair)` value returned instead.
_ => {
match malformed_line::cond.raise(line.clone()) {
Some(pair) => pairs.push(pair),
None => ()
}
}
}
}
pairs
}
~~~~
Again, note that the remainder of the program is _unchanged_,
in particular the signature of `read_int_pairs` is unchanged,
even though the innermost part of its reading-loop has a new way of handling a malformed line.
When the example is run with the `None` trap in place,
the line is ignored as it was in the first example,
but the choice of whether to ignore or use a substitute value has been moved to some caller,
possibly a distant caller.
~~~~ {.notrust}
$ ./example bad.txt
0001, 0002
0034, 0056
0789, 0123
0045, 0067
~~~~
# Further refining a condition
Like with any API, the process of refining argument and return types of a condition will continue,
until all relevant combinations encountered in practice are encoded.
In the example, suppose a third possible recovery form arose: reusing the previous value read.
This can be encoded in the handler API by introducing a helper type: `enum MalformedLineFix`.
~~~~
# #[allow(unused_imports)];
use std::io::{BufferedReader, File};
# mod BufferedReader {
# use std::io::{File, IoResult};
# use std::io::MemReader;
# use std::io::BufferedReader;
# static s : &'static [u8] = bytes!("1 2\n\
# 34 56\n\
# 789 123\n\
# 45 67\n\
# ");
# pub fn new(_inner: IoResult<File>) -> BufferedReader<MemReader> {
# BufferedReader::new(MemReader::new(s.to_owned()))
# }
# }
// Introduce a new enum to convey condition-handling strategy to error site.
pub enum MalformedLineFix {
UsePair(int,int),
IgnoreLine,
UsePreviousLine
}
// Modify the condition signature to return the new enum.
// Note: a condition introduces a new module, so the enum must be
// named with the `super::` prefix to access it.
condition! {
pub malformed_line : ~str -> super::MalformedLineFix;
}
fn main() {
// Trap the condition and return `UsePreviousLine`
malformed_line::cond.trap(|_| UsePreviousLine).inside(|| {
// The protected logic.
let pairs = read_int_pairs();
for &(a,b) in pairs.iter() {
println!("{:4.4d}, {:4.4d}", a, b);
}
})
}
fn read_int_pairs() -> ~[(int,int)] {
let mut pairs = ~[];
let path = Path::new(&"foo.txt");
let mut reader = BufferedReader::new(File::open(&path));
for line in reader.lines() {
match line.words().to_owned_vec() {
[a, b] => pairs.push((from_str::<int>(a).unwrap(),
from_str::<int>(b).unwrap())),
// On malformed lines, call the condition handler and
// take action appropriate to the enum value returned.
_ => {
match malformed_line::cond.raise(line.clone()) {
UsePair(a,b) => pairs.push((a,b)),
IgnoreLine => (),
UsePreviousLine => {
let prev = pairs[pairs.len() - 1];
pairs.push(prev)
}
}
}
}
}
pairs
}
~~~~
Running the example with `UsePreviousLine` as the fix code returned from the handler
gives the expected result:
~~~~ {.notrust}
$ ./example bad.txt
0001, 0002
0034, 0056
0034, 0056
0789, 0123
0045, 0067
~~~~
At this point the example has a rich variety of recovery options,
none of which is visible to casual users of the `read_int_pairs` function.
This is intentional: part of the purpose of using a condition
is to free intermediate callers from the burden of having to write repetitive error-propagation logic,
and/or having to change function call and return types as error-handling strategies are refined.
# Multiple conditions, intermediate callers
So far the function trapping the condition and the function raising it have been immediately adjacent in the call stack.
That is, the caller traps and its immediate callee raises.
In most programs, the function that traps may be separated by very many function calls from the function that raises.
Again, this is part of the point of using conditions:
to support that separation without having to thread multiple error values and recovery strategies all the way through the program's main logic.
Careful readers will notice that there is a remaining failure mode in the example program: the call to `.unwrap()` when parsing each integer.
For example, when presented with a file that has the correct number of fields on a line,
but a non-numeric value in one of them, such as this:
~~~~ {.notrust}
$ cat bad.txt
1 2
34 56
7 marmot
789 123
45 67
~~~~
Then the program fails once more:
~~~~ {.notrust}
$ ./example bad.txt
task <unnamed> failed at 'called `Option::unwrap()` on a `None` value', .../libstd/option.rs:314
~~~~
To make the program robust &mdash; or at least flexible &mdash; in the face of this potential failure,
a second condition and a helper function will suffice:
~~~~
# #[allow(unused_imports)];
use std::io::{BufferedReader, File};
# mod BufferedReader {
# use std::io::{File, IoResult};
# use std::io::MemReader;
# use std::io::BufferedReader;
# static s : &'static [u8] = bytes!("1 2\n\
# 34 56\n\
# 789 123\n\
# 45 67\n\
# ");
# pub fn new(_inner: IoResult<File>) -> BufferedReader<MemReader> {
# BufferedReader::new(MemReader::new(s.to_owned()))
# }
# }
pub enum MalformedLineFix {
UsePair(int,int),
IgnoreLine,
UsePreviousLine
}
condition! {
pub malformed_line : ~str -> ::MalformedLineFix;
}
// Introduce a second condition.
condition! {
pub malformed_int : ~str -> int;
}
fn main() {
// Trap the `malformed_int` condition and return -1
malformed_int::cond.trap(|_| -1).inside(|| {
// Trap the `malformed_line` condition and return `UsePreviousLine`
malformed_line::cond.trap(|_| UsePreviousLine).inside(|| {
// The protected logic.
let pairs = read_int_pairs();
for &(a,b) in pairs.iter() {
println!("{:4.4d}, {:4.4d}", a, b);
}
})
})
}
// Parse an int; if parsing fails, call the condition handler and
// return whatever it returns.
fn parse_int(x: &str) -> int {
match from_str::<int>(x) {
Some(v) => v,
None => malformed_int::cond.raise(x.to_owned())
}
}
fn read_int_pairs() -> ~[(int,int)] {
let mut pairs = ~[];
let path = Path::new(&"foo.txt");
let mut reader = BufferedReader::new(File::open(&path));
for line in reader.lines() {
match line.words().to_owned_vec() {
// Delegate parsing ints to helper function that will
// handle parse errors by calling `malformed_int`.
[a, b] => pairs.push((parse_int(a), parse_int(b))),
_ => {
match malformed_line::cond.raise(line.clone()) {
UsePair(a,b) => pairs.push((a,b)),
IgnoreLine => (),
UsePreviousLine => {
let prev = pairs[pairs.len() - 1];
pairs.push(prev)
}
}
}
}
}
pairs
}
~~~~
Again, note that `read_int_pairs` has not changed signature,
nor has any of the machinery for trapping or raising `malformed_line`,
but now the program can handle the "right number of fields, non-integral field" form of bad input:
~~~~ {.notrust}
$ ./example bad.txt
0001, 0002
0034, 0056
0007, -0001
0789, 0123
0045, 0067
~~~~
There are three other things to note in this variant of the example program:
- It traps multiple conditions simultaneously,
nesting the protected logic of one `trap` call inside the other.
- There is a function in between the `trap` site and `raise` site for the `malformed_int` condition.
There could be any number of calls between them:
so long as the `raise` occurs within a callee (of any depth) of the logic protected by the `trap` call,
it will invoke the handler.
- This variant insulates callers from a design choice in the library:
the `from_str` function was designed to return an `Option<int>`,
but this program insulates callers from that choice,
routing all `None` values that arise from parsing integers in this file into the condition.
# When to use which technique
This guide explored several techniques for handling errors.
Each is appropriate to different circumstances:
- If an error may be extremely frequent, expected, and very likely dealt with by an immediate caller,
then returning an `Option` or `Result` type is best. These types force the caller to handle the error,
and incur the lowest speed overhead, usually only returning one extra word to tag the return value.
Between `Option` and `Result`: use an `Option` when there is only one kind of error,
otherwise make an `enum FooErr` to represent the possible error codes and use `Result<T,FooErr>`.
- If an error can reasonably be handled at the site it occurs by one of a few strategies &mdash; possibly including failure &mdash;
and it is not clear which strategy a caller would want to use, a condition is best.
For many errors, the only reasonable "non-stop" recovery strategies are to retry some number of times,
create or substitute an empty or sentinel value, ignore the error, or fail.
- If an error cannot reasonably be handled at the site it occurs,
and the only reasonable response is to abandon a large set of operations in progress,
then directly failing is best.
Note that an unhandled condition will cause failure (along with a more-informative-than-usual message),
so if there is any possibility that a caller might wish to "ignore and keep going",
it is usually harmless to use a condition in place of a direct call to `fail!()`.
[^why-no-exceptions]: Exceptions in languages like C++ and Java permit unwinding, like Rust's failure system,
but with the option to halt unwinding partway through the process and continue execution.
This behavior unfortunately means that the _heap_ may be left in an inconsistent but accessible state,
if an exception is thrown part way through the process of initializing or modifying memory.
To compensate for this risk, correct C++ and Java code must program in an extremely elaborate and difficult "exception-safe" style
&mdash; effectively transactional style against heap structures &mdash;
or else risk introducing silent and very difficult-to-debug errors due to control resuming in a corrupted heap after a caught exception.
These errors are frequently memory-safety errors, which Rust strives to eliminate,
and so Rust unwinding is unrecoverable within a single task:
once unwinding starts, the entire local heap of a task is destroyed and the task is terminated.

View File

@ -75,24 +75,11 @@ use ptr::RawPtr;
use ptr;
use str::StrSlice;
use str;
use vec::{CloneableVector, ImmutableVector, MutableVector};
use vec::{ImmutableVector, MutableVector};
use vec;
use unstable::intrinsics;
use rt::global_heap::malloc_raw;
/// Resolution options for the `null_byte` condition
pub enum NullByteResolution {
/// Truncate at the null byte
Truncate,
/// Use a replacement byte
ReplaceWith(libc::c_char)
}
condition! {
// This should be &[u8] but there's a lifetime issue (#5370).
pub null_byte: (~[u8]) -> NullByteResolution;
}
/// The representation of a C String.
///
/// This structure wraps a `*libc::c_char`, and will automatically free the
@ -252,7 +239,7 @@ pub trait ToCStr {
///
/// # Failure
///
/// Raises the `null_byte` condition if the receiver has an interior null.
/// Fails the task if the receiver has an interior null.
fn to_c_str(&self) -> CString;
/// Unsafe variant of `to_c_str()` that doesn't check for nulls.
@ -273,7 +260,7 @@ pub trait ToCStr {
///
/// # Failure
///
/// Raises the `null_byte` condition if the receiver has an interior null.
/// Fails the task if the receiver has an interior null.
#[inline]
fn with_c_str<T>(&self, f: |*libc::c_char| -> T) -> T {
self.to_c_str().with_ref(f)
@ -362,12 +349,7 @@ fn check_for_null(v: &[u8], buf: *mut libc::c_char) {
for i in range(0, v.len()) {
unsafe {
let p = buf.offset(i as int);
if *p == 0 {
match null_byte::cond.raise(v.to_owned()) {
Truncate => break,
ReplaceWith(c) => *p = c
}
}
assert!(*p != 0);
}
}
}
@ -541,29 +523,9 @@ mod tests {
#[test]
fn test_to_c_str_fail() {
use c_str::null_byte::cond;
use task;
let mut error_happened = false;
cond.trap(|err| {
assert_eq!(err, bytes!("he", 0, "llo").to_owned())
error_happened = true;
Truncate
}).inside(|| "he\x00llo".to_c_str());
assert!(error_happened);
cond.trap(|_| {
ReplaceWith('?' as libc::c_char)
}).inside(|| "he\x00llo".to_c_str()).with_ref(|buf| {
unsafe {
assert_eq!(*buf.offset(0), 'h' as libc::c_char);
assert_eq!(*buf.offset(1), 'e' as libc::c_char);
assert_eq!(*buf.offset(2), '?' as libc::c_char);
assert_eq!(*buf.offset(3), 'l' as libc::c_char);
assert_eq!(*buf.offset(4), 'l' as libc::c_char);
assert_eq!(*buf.offset(5), 'o' as libc::c_char);
assert_eq!(*buf.offset(6), 0);
}
})
assert!(task::try(proc() { "he\x00llo".to_c_str() }).is_err());
}
#[test]

View File

@ -1,349 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/*!
Condition handling
Conditions are a utility used to deal with handling error conditions. The syntax
of a condition handler strikes a resemblance to try/catch blocks in other
languages, but condition handlers are *not* a form of exception handling in the
same manner.
A condition is declared through the `condition!` macro provided by the compiler:
```rust
condition! {
pub my_error: int -> ~str;
}
# fn main() {}
```
This macro declares an inner module called `my_error` with one static variable,
`cond` that is a static `Condition` instance. To help understand what the other
parameters are used for, an example usage of this condition would be:
```rust
# condition! { pub my_error: int -> ~str; }
# fn main() {
my_error::cond.trap(|raised_int| {
// the condition `my_error` was raised on, and the value it raised is stored
// in `raised_int`. This closure must return a `~str` type (as specified in
// the declaration of the condition
if raised_int == 3 { ~"three" } else { ~"oh well" }
}).inside(|| {
// The condition handler above is installed for the duration of this block.
// That handler will override any previous handler, but the previous handler
// is restored when this block returns (handlers nest)
//
// If any code from this block (or code from another block) raises on the
// condition, then the above handler will be invoked (so long as there's no
// other nested handler).
println!("{}", my_error::cond.raise(3)); // prints "three"
println!("{}", my_error::cond.raise(4)); // prints "oh well"
})
# }
```
Condition handling is useful in cases where propagating errors is either to
cumbersome or just not necessary in the first place. It should also be noted,
though, that if there is not handler installed when a condition is raised, then
the task invokes `fail!()` and will terminate.
## More Info
Condition handlers as an error strategy is well explained in the [conditions
tutorial](http://static.rust-lang.org/doc/master/tutorial-conditions.html),
along with comparing and contrasting it with other error handling strategies.
*/
use local_data;
use prelude::*;
use unstable::raw::Closure;
#[doc(hidden)]
pub struct Handler<T, U> {
priv handle: Closure,
priv prev: Option<@Handler<T, U>>,
}
/// This struct represents the state of a condition handler. It contains a key
/// into TLS which holds the currently install handler, along with the name of
/// the condition (useful for debugging).
///
/// This struct should never be created directly, but rather only through the
/// `condition!` macro provided to all libraries using `std`.
pub struct Condition<T, U> {
/// Name of the condition handler
name: &'static str,
/// TLS key used to insert/remove values in TLS.
key: local_data::Key<@Handler<T, U>>
}
impl<T, U> Condition<T, U> {
/// Creates an object which binds the specified handler. This will also save
/// the current handler *on creation* such that when the `Trap` is consumed,
/// it knows which handler to restore.
///
/// # Example
///
/// ```rust
/// condition! { my_error: int -> int; }
///
/// # fn main() {
/// let trap = my_error::cond.trap(|error| error + 3);
///
/// // use `trap`'s inside method to register the handler and then run a
/// // block of code with the handler registered
/// # }
/// ```
pub fn trap<'a>(&'a self, h: 'a |T| -> U) -> Trap<'a, T, U> {
let h: Closure = unsafe { ::cast::transmute(h) };
let prev = local_data::get(self.key, |k| k.map(|x| *x));
let h = @Handler { handle: h, prev: prev };
Trap { cond: self, handler: h }
}
/// Raises on this condition, invoking any handler if one has been
/// registered, or failing the current task otherwise.
///
/// While a condition handler is being run, the condition will have no
/// handler listed, so a task failure will occur if the condition is
/// re-raised during the handler.
///
/// # Arguments
///
/// * t - The argument to pass along to the condition handler.
///
/// # Return value
///
/// If a handler is found, its return value is returned, otherwise this
/// function will not return.
pub fn raise(&self, t: T) -> U {
let msg = format!("Unhandled condition: {}: {:?}", self.name, t);
self.raise_default(t, || fail!("{}", msg.clone()))
}
/// Performs the same functionality as `raise`, except that when no handler
/// is found the `default` argument is called instead of failing the task.
pub fn raise_default(&self, t: T, default: || -> U) -> U {
match local_data::pop(self.key) {
None => {
debug!("Condition.raise: found no handler");
default()
}
Some(handler) => {
debug!("Condition.raise: found handler");
match handler.prev {
None => {}
Some(hp) => local_data::set(self.key, hp)
}
let handle : |T| -> U = unsafe {
::cast::transmute(handler.handle)
};
let u = handle(t);
local_data::set(self.key, handler);
u
}
}
}
}
/// A `Trap` is created when the `trap` method is invoked on a `Condition`, and
/// it is used to actually bind a handler into the TLS slot reserved for this
/// condition.
///
/// Normally this object is not dealt with directly, but rather it's directly
/// used after being returned from `trap`
pub struct Trap<'a, T, U> {
priv cond: &'a Condition<T, U>,
priv handler: @Handler<T, U>
}
impl<'a, T, U> Trap<'a, T, U> {
/// Execute a block of code with this trap handler's exception handler
/// registered.
///
/// # Example
///
/// ```rust
/// condition! { my_error: int -> int; }
///
/// # fn main() {
/// let result = my_error::cond.trap(|error| error + 3).inside(|| {
/// my_error::cond.raise(4)
/// });
/// assert_eq!(result, 7);
/// # }
/// ```
pub fn inside<V>(&self, inner: 'a || -> V) -> V {
let _g = Guard { cond: self.cond };
debug!("Trap: pushing handler to TLS");
local_data::set(self.cond.key, self.handler);
inner()
}
/// Returns a guard that will automatically reset the condition upon
/// exit of the scope. This is useful if you want to use conditions with
/// an RAII pattern.
pub fn guard(&self) -> Guard<'a,T,U> {
let guard = Guard {
cond: self.cond
};
debug!("Guard: pushing handler to TLS");
local_data::set(self.cond.key, self.handler);
guard
}
}
/// A guard that will automatically reset the condition handler upon exit of
/// the scope. This is useful if you want to use conditions with an RAII
/// pattern.
pub struct Guard<'a, T, U> {
priv cond: &'a Condition<T, U>
}
#[unsafe_destructor]
impl<'a, T, U> Drop for Guard<'a, T, U> {
fn drop(&mut self) {
debug!("Guard: popping handler from TLS");
let curr = local_data::pop(self.cond.key);
match curr {
None => {}
Some(h) => match h.prev {
None => {}
Some(hp) => local_data::set(self.cond.key, hp)
}
}
}
}
#[cfg(test)]
mod test {
condition! {
sadness: int -> int;
}
fn trouble(i: int) {
debug!("trouble: raising condition");
let j = sadness::cond.raise(i);
debug!("trouble: handler recovered with {}", j);
}
fn nested_trap_test_inner() {
let mut inner_trapped = false;
sadness::cond.trap(|_j| {
debug!("nested_trap_test_inner: in handler");
inner_trapped = true;
0
}).inside(|| {
debug!("nested_trap_test_inner: in protected block");
trouble(1);
});
assert!(inner_trapped);
}
#[test]
fn nested_trap_test_outer() {
let mut outer_trapped = false;
sadness::cond.trap(|_j| {
debug!("nested_trap_test_outer: in handler");
outer_trapped = true; 0
}).inside(|| {
debug!("nested_guard_test_outer: in protected block");
nested_trap_test_inner();
trouble(1);
});
assert!(outer_trapped);
}
fn nested_reraise_trap_test_inner() {
let mut inner_trapped = false;
sadness::cond.trap(|_j| {
debug!("nested_reraise_trap_test_inner: in handler");
inner_trapped = true;
let i = 10;
debug!("nested_reraise_trap_test_inner: handler re-raising");
sadness::cond.raise(i)
}).inside(|| {
debug!("nested_reraise_trap_test_inner: in protected block");
trouble(1);
});
assert!(inner_trapped);
}
#[test]
fn nested_reraise_trap_test_outer() {
let mut outer_trapped = false;
sadness::cond.trap(|_j| {
debug!("nested_reraise_trap_test_outer: in handler");
outer_trapped = true; 0
}).inside(|| {
debug!("nested_reraise_trap_test_outer: in protected block");
nested_reraise_trap_test_inner();
});
assert!(outer_trapped);
}
#[test]
fn test_default() {
let mut trapped = false;
sadness::cond.trap(|j| {
debug!("test_default: in handler");
sadness::cond.raise_default(j, || { trapped=true; 5 })
}).inside(|| {
debug!("test_default: in protected block");
trouble(1);
});
assert!(trapped);
}
// Issue #6009
mod m {
condition! {
// #6009, #8215: should this truly need a `pub` for access from n?
pub sadness: int -> int;
}
mod n {
use super::sadness;
#[test]
fn test_conditions_are_public() {
let mut trapped = false;
sadness::cond.trap(|_| {
trapped = true;
0
}).inside(|| {
sadness::cond.raise(0);
});
assert!(trapped);
}
}
}
}

View File

@ -19,8 +19,6 @@ use prelude::*;
use char;
use str;
condition! { pub parse_error: ~str -> (); }
/// A piece is a portion of the format string which represents the next part to
/// emit. These are emitted as a stream by the `Parser` class.
#[deriving(Eq)]
@ -170,6 +168,8 @@ pub struct Parser<'a> {
priv input: &'a str,
priv cur: str::CharOffsets<'a>,
priv depth: uint,
/// Error messages accumulated during parsing
errors: ~[~str],
}
impl<'a> Iterator<Piece<'a>> for Parser<'a> {
@ -207,14 +207,15 @@ impl<'a> Parser<'a> {
input: s,
cur: s.char_indices(),
depth: 0,
errors: ~[],
}
}
/// Notifies of an error. The message doesn't actually need to be of type
/// ~str, but I think it does when this eventually uses conditions so it
/// might as well start using it now.
fn err(&self, msg: &str) {
parse_error::cond.raise("invalid format string: " + msg);
fn err(&mut self, msg: &str) {
self.errors.push(msg.to_owned());
}
/// Optionally consumes the specified character. If the character is not at
@ -671,7 +672,9 @@ mod tests {
}
fn musterr(s: &str) {
Parser::new(s).next();
let mut p = Parser::new(s);
p.next();
assert!(p.errors.len() != 0);
}
#[test]
@ -684,12 +687,12 @@ mod tests {
same("\\}", ~[String("}")]);
}
#[test] #[should_fail] fn invalid01() { musterr("{") }
#[test] #[should_fail] fn invalid02() { musterr("\\") }
#[test] #[should_fail] fn invalid03() { musterr("\\a") }
#[test] #[should_fail] fn invalid04() { musterr("{3a}") }
#[test] #[should_fail] fn invalid05() { musterr("{:|}") }
#[test] #[should_fail] fn invalid06() { musterr("{:>>>}") }
#[test] fn invalid01() { musterr("{") }
#[test] fn invalid02() { musterr("\\") }
#[test] fn invalid03() { musterr("\\a") }
#[test] fn invalid04() { musterr("{3a}") }
#[test] fn invalid05() { musterr("{:|}") }
#[test] fn invalid06() { musterr("{:>>>}") }
#[test]
fn format_nothing() {
@ -916,36 +919,16 @@ mod tests {
})]);
}
#[test] #[should_fail] fn badselect01() {
musterr("{select, }")
}
#[test] #[should_fail] fn badselect02() {
musterr("{1, select}")
}
#[test] #[should_fail] fn badselect03() {
musterr("{1, select, }")
}
#[test] #[should_fail] fn badselect04() {
musterr("{1, select, a {}}")
}
#[test] #[should_fail] fn badselect05() {
musterr("{1, select, other }}")
}
#[test] #[should_fail] fn badselect06() {
musterr("{1, select, other {}")
}
#[test] #[should_fail] fn badselect07() {
musterr("{select, other {}")
}
#[test] #[should_fail] fn badselect08() {
musterr("{1 select, other {}")
}
#[test] #[should_fail] fn badselect09() {
musterr("{:d select, other {}")
}
#[test] #[should_fail] fn badselect10() {
musterr("{1:d select, other {}")
}
#[test] fn badselect01() { musterr("{select, }") }
#[test] fn badselect02() { musterr("{1, select}") }
#[test] fn badselect03() { musterr("{1, select, }") }
#[test] fn badselect04() { musterr("{1, select, a {}}") }
#[test] fn badselect05() { musterr("{1, select, other }}") }
#[test] fn badselect06() { musterr("{1, select, other {}") }
#[test] fn badselect07() { musterr("{select, other {}") }
#[test] fn badselect08() { musterr("{1 select, other {}") }
#[test] fn badselect09() { musterr("{:d select, other {}") }
#[test] fn badselect10() { musterr("{1:d select, other {}") }
#[test]
fn plural_simple() {

View File

@ -178,8 +178,6 @@ pub mod run;
pub mod cast;
pub mod fmt;
pub mod cleanup;
#[deprecated]
pub mod condition;
pub mod logging;
pub mod util;
pub mod mem;
@ -216,7 +214,6 @@ mod std {
pub use clone;
pub use cmp;
pub use comm;
pub use condition;
pub use fmt;
pub use io;
pub use kinds;

View File

@ -123,50 +123,6 @@ macro_rules! unreachable (() => (
fail!("internal error: entered unreachable code");
))
#[macro_export]
macro_rules! condition (
{ pub $c:ident: $input:ty -> $out:ty; } => {
pub mod $c {
#[allow(unused_imports)];
#[allow(non_uppercase_statics)];
#[allow(missing_doc)];
use super::*;
local_data_key!(key: @::std::condition::Handler<$input, $out>)
pub static cond :
::std::condition::Condition<$input,$out> =
::std::condition::Condition {
name: stringify!($c),
key: key
};
}
};
{ $c:ident: $input:ty -> $out:ty; } => {
mod $c {
#[allow(unused_imports)];
#[allow(non_uppercase_statics)];
#[allow(dead_code)];
use super::*;
local_data_key!(key: @::std::condition::Handler<$input, $out>)
pub static cond :
::std::condition::Condition<$input,$out> =
::std::condition::Condition {
name: stringify!($c),
key: key
};
}
}
)
#[macro_export]
macro_rules! format(($($arg:tt)*) => (
format_args!(::std::fmt::format, $($arg)*)

View File

@ -147,12 +147,6 @@ pub use is_sep_byte = self::windows::is_sep_byte;
pub mod posix;
pub mod windows;
// Condition that is raised when a NUL is found in a byte vector given to a Path function
condition! {
// this should be a &[u8] but there's a lifetime issue
null_byte: ~[u8] -> ~[u8];
}
/// A trait that represents the generic operations available on paths
pub trait GenericPath: Clone + GenericPathUnsafe {
/// Creates a new Path from a byte vector or string.
@ -160,18 +154,13 @@ pub trait GenericPath: Clone + GenericPathUnsafe {
///
/// # Failure
///
/// Raises the `null_byte` condition if the path contains a NUL.
/// Fails the task if the path contains a NUL.
///
/// See individual Path impls for additional restrictions.
#[inline]
fn new<T: BytesContainer>(path: T) -> Self {
if contains_nul(path.container_as_bytes()) {
let path = self::null_byte::cond.raise(path.container_into_owned_bytes());
assert!(!contains_nul(path));
unsafe { GenericPathUnsafe::new_unchecked(path) }
} else {
unsafe { GenericPathUnsafe::new_unchecked(path) }
}
assert!(!contains_nul(path.container_as_bytes()));
unsafe { GenericPathUnsafe::new_unchecked(path) }
}
/// Creates a new Path from a byte vector or string, if possible.
@ -283,16 +272,11 @@ pub trait GenericPath: Clone + GenericPathUnsafe {
///
/// # Failure
///
/// Raises the `null_byte` condition if the filename contains a NUL.
/// Fails the task if the filename contains a NUL.
#[inline]
fn set_filename<T: BytesContainer>(&mut self, filename: T) {
if contains_nul(filename.container_as_bytes()) {
let filename = self::null_byte::cond.raise(filename.container_into_owned_bytes());
assert!(!contains_nul(filename));
unsafe { self.set_filename_unchecked(filename) }
} else {
unsafe { self.set_filename_unchecked(filename) }
}
assert!(!contains_nul(filename.container_as_bytes()));
unsafe { self.set_filename_unchecked(filename) }
}
/// Replaces the extension with the given byte vector or string.
/// If there is no extension in `self`, this adds one.
@ -301,8 +285,9 @@ pub trait GenericPath: Clone + GenericPathUnsafe {
///
/// # Failure
///
/// Raises the `null_byte` condition if the extension contains a NUL.
/// Fails the task if the extension contains a NUL.
fn set_extension<T: BytesContainer>(&mut self, extension: T) {
assert!(!contains_nul(extension.container_as_bytes()));
// borrowck causes problems here too
let val = {
match self.filename() {
@ -315,21 +300,11 @@ pub trait GenericPath: Clone + GenericPathUnsafe {
None
} else {
let mut v;
if contains_nul(extension.container_as_bytes()) {
let ext = extension.container_into_owned_bytes();
let extension = self::null_byte::cond.raise(ext);
assert!(!contains_nul(extension));
v = vec::with_capacity(name.len() + extension.len() + 1);
v.push_all(name);
v.push(dot);
v.push_all(extension);
} else {
let extension = extension.container_as_bytes();
v = vec::with_capacity(name.len() + extension.len() + 1);
v.push_all(name);
v.push(dot);
v.push_all(extension);
}
let extension = extension.container_as_bytes();
v = vec::with_capacity(name.len() + extension.len() + 1);
v.push_all(name);
v.push(dot);
v.push_all(extension);
Some(v)
}
}
@ -338,19 +313,10 @@ pub trait GenericPath: Clone + GenericPathUnsafe {
Some(name.slice_to(idx).to_owned())
} else {
let mut v;
if contains_nul(extension.container_as_bytes()) {
let ext = extension.container_into_owned_bytes();
let extension = self::null_byte::cond.raise(ext);
assert!(!contains_nul(extension));
v = vec::with_capacity(idx + extension.len() + 1);
v.push_all(name.slice_to(idx+1));
v.push_all(extension);
} else {
let extension = extension.container_as_bytes();
v = vec::with_capacity(idx + extension.len() + 1);
v.push_all(name.slice_to(idx+1));
v.push_all(extension);
}
let extension = extension.container_as_bytes();
v = vec::with_capacity(idx + extension.len() + 1);
v.push_all(name.slice_to(idx+1));
v.push_all(extension);
Some(v)
}
}
@ -370,7 +336,7 @@ pub trait GenericPath: Clone + GenericPathUnsafe {
///
/// # Failure
///
/// Raises the `null_byte` condition if the filename contains a NUL.
/// Fails the task if the filename contains a NUL.
#[inline]
fn with_filename<T: BytesContainer>(&self, filename: T) -> Self {
let mut p = self.clone();
@ -383,7 +349,7 @@ pub trait GenericPath: Clone + GenericPathUnsafe {
///
/// # Failure
///
/// Raises the `null_byte` condition if the extension contains a NUL.
/// Fails the task if the extension contains a NUL.
#[inline]
fn with_extension<T: BytesContainer>(&self, extension: T) -> Self {
let mut p = self.clone();
@ -408,16 +374,11 @@ pub trait GenericPath: Clone + GenericPathUnsafe {
///
/// # Failure
///
/// Raises the `null_byte` condition if the path contains a NUL.
/// Fails the task if the path contains a NUL.
#[inline]
fn push<T: BytesContainer>(&mut self, path: T) {
if contains_nul(path.container_as_bytes()) {
let path = self::null_byte::cond.raise(path.container_into_owned_bytes());
assert!(!contains_nul(path));
unsafe { self.push_unchecked(path) }
} else {
unsafe { self.push_unchecked(path) }
}
assert!(!contains_nul(path.container_as_bytes()));
unsafe { self.push_unchecked(path) }
}
/// Pushes multiple paths (as byte vectors or strings) onto `self`.
/// See `push` for details.
@ -445,7 +406,7 @@ pub trait GenericPath: Clone + GenericPathUnsafe {
///
/// # Failure
///
/// Raises the `null_byte` condition if the path contains a NUL.
/// Fails the task if the path contains a NUL.
#[inline]
fn join<T: BytesContainer>(&self, path: T) -> Self {
let mut p = self.clone();

View File

@ -318,7 +318,7 @@ impl Path {
///
/// # Failure
///
/// Raises the `null_byte` condition if the vector contains a NUL.
/// Fails the task if the vector contains a NUL.
#[inline]
pub fn new<T: BytesContainer>(path: T) -> Path {
GenericPath::new(path)
@ -527,83 +527,21 @@ mod tests {
#[test]
fn test_null_byte() {
use path::null_byte::cond;
let mut handled = false;
let mut p = cond.trap(|v| {
handled = true;
assert_eq!(v.as_slice(), b!("foo/bar", 0));
(b!("/bar").to_owned())
}).inside(|| {
use task;
let result = task::try(proc() {
Path::new(b!("foo/bar", 0))
});
assert!(handled);
assert_eq!(p.as_vec(), b!("/bar"));
assert!(result.is_err());
handled = false;
cond.trap(|v| {
handled = true;
assert_eq!(v.as_slice(), b!("f", 0, "o"));
(b!("foo").to_owned())
}).inside(|| {
p.set_filename(b!("f", 0, "o"))
let result = task::try(proc() {
Path::new("test").set_filename(b!("f", 0, "o"))
});
assert!(handled);
assert_eq!(p.as_vec(), b!("/foo"));
assert!(result.is_err());
handled = false;
cond.trap(|v| {
handled = true;
assert_eq!(v.as_slice(), b!("f", 0, "o"));
(b!("foo").to_owned())
}).inside(|| {
p.push(b!("f", 0, "o"));
let result = task::try(proc() {
Path::new("test").push(b!("f", 0, "o"));
});
assert!(handled);
assert_eq!(p.as_vec(), b!("/foo/foo"));
}
#[test]
fn test_null_byte_fail() {
use path::null_byte::cond;
use task;
macro_rules! t(
($name:expr => $code:expr) => (
{
let mut t = task::task();
t.name($name);
let res = t.try(proc() $code);
assert!(res.is_err());
}
)
)
t!(~"new() w/nul" => {
cond.trap(|_| {
(b!("null", 0).to_owned())
}).inside(|| {
Path::new(b!("foo/bar", 0))
});
})
t!(~"set_filename w/nul" => {
let mut p = Path::new(b!("foo/bar"));
cond.trap(|_| {
(b!("null", 0).to_owned())
}).inside(|| {
p.set_filename(b!("foo", 0))
});
})
t!(~"push w/nul" => {
let mut p = Path::new(b!("foo/bar"));
cond.trap(|_| {
(b!("null", 0).to_owned())
}).inside(|| {
p.push(b!("foo", 0))
});
})
assert!(result.is_err());
}
#[test]

View File

@ -590,7 +590,7 @@ impl Path {
///
/// # Failure
///
/// Raises the `null_byte` condition if the vector contains a NUL.
/// Fails the task if the vector contains a NUL.
/// Fails if invalid UTF-8.
#[inline]
pub fn new<T: BytesContainer>(path: T) -> Path {
@ -1248,83 +1248,21 @@ mod tests {
#[test]
fn test_null_byte() {
use path::null_byte::cond;
let mut handled = false;
let mut p = cond.trap(|v| {
handled = true;
assert_eq!(v.as_slice(), b!("foo\\bar", 0));
(b!("\\bar").to_owned())
}).inside(|| {
Path::new(b!("foo\\bar", 0))
});
assert!(handled);
assert_eq!(p.as_vec(), b!("\\bar"));
handled = false;
cond.trap(|v| {
handled = true;
assert_eq!(v.as_slice(), b!("f", 0, "o"));
(b!("foo").to_owned())
}).inside(|| {
p.set_filename(b!("f", 0, "o"))
});
assert!(handled);
assert_eq!(p.as_vec(), b!("\\foo"));
handled = false;
cond.trap(|v| {
handled = true;
assert_eq!(v.as_slice(), b!("f", 0, "o"));
(b!("foo").to_owned())
}).inside(|| {
p.push(b!("f", 0, "o"));
});
assert!(handled);
assert_eq!(p.as_vec(), b!("\\foo\\foo"));
}
#[test]
fn test_null_byte_fail() {
use path::null_byte::cond;
use task;
let result = task::try(proc() {
Path::new(b!("foo/bar", 0))
});
assert!(result.is_err());
macro_rules! t(
($name:expr => $code:expr) => (
{
let mut t = task::task();
t.name($name);
let res = t.try(proc() $code);
assert!(res.is_err());
}
)
)
let result = task::try(proc() {
Path::new("test").set_filename(b!("f", 0, "o"))
});
assert!(result.is_err());
t!(~"from_vec() w\\nul" => {
cond.trap(|_| {
(b!("null", 0).to_owned())
}).inside(|| {
Path::new(b!("foo\\bar", 0))
});
})
t!(~"set_filename w\\nul" => {
let mut p = Path::new(b!("foo\\bar"));
cond.trap(|_| {
(b!("null", 0).to_owned())
}).inside(|| {
p.set_filename(b!("foo", 0))
});
})
t!(~"push w\\nul" => {
let mut p = Path::new(b!("foo\\bar"));
cond.trap(|_| {
(b!("null", 0).to_owned())
}).inside(|| {
p.push(b!("foo", 0))
});
})
let result = task::try(proc() {
Path::new("test").push(b!("f", 0, "o"));
});
assert!(result.is_err());
}
#[test]

View File

@ -44,7 +44,6 @@ Several modules in `core` are clients of `rt`:
* `std::local_data` - The interface to local data.
* `std::gc` - The garbage collector.
* `std::unstable::lang` - Miscellaneous lang items, some of which rely on `std::rt`.
* `std::condition` - Uses local data.
* `std::cleanup` - Local heap destruction.
* `std::io` - In the future `std::io` will use an `rt` implementation.
* `std::logging`

View File

@ -786,22 +786,25 @@ pub fn expand_args(ecx: &mut ExtCtxt, sp: Span,
None => return MacResult::dummy_expr()
};
let mut err = false;
parse::parse_error::cond.trap(|m| {
if !err {
err = true;
cx.ecx.span_err(efmt.span, m);
}
}).inside(|| {
for piece in parse::Parser::new(fmt.get()) {
if !err {
let mut parser = parse::Parser::new(fmt.get());
loop {
match parser.next() {
Some(piece) => {
if parser.errors.len() > 0 { break }
cx.verify_piece(&piece);
let piece = cx.trans_piece(&piece);
cx.pieces.push(piece);
}
None => break
}
});
if err { return MRExpr(efmt) }
}
match parser.errors.shift() {
Some(error) => {
cx.ecx.span_err(efmt.span, "invalid format string: " + error);
return MRExpr(efmt);
}
None => {}
}
// Make sure that all arguments were used and all arguments have types.
for (i, ty) in cx.arg_types.iter().enumerate() {

View File

@ -1,19 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[crate_type="lib"];
condition! {
pub oops: int -> int;
}
pub fn trouble() -> int {
oops::cond.raise(1)
}

View File

@ -1,15 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[crate_type="lib"];
condition! {
pub oops: int -> int;
}

View File

@ -1,21 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[crate_type="lib"];
condition! {
pub oops: int -> int;
}
pub fn guard(k: extern fn() -> int, x: int) -> int {
oops::cond.trap(|i| i*x).inside(|| {
k()
})
}

View File

@ -1,28 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[crate_type="lib"];
#[deriving(Eq)]
pub enum Color {
Red, Green, Blue
}
condition! {
pub oops: (int,f64,~str) -> Color;
}
pub trait Thunk<T> {
fn call(self) -> T;
}
pub fn callback<T,TH:Thunk<T>>(t:TH) -> T {
t.call()
}

View File

@ -24,9 +24,3 @@ pub fn verify_same2(a: &'static int) {
let b = global2 as *int as uint;
assert_eq!(a, b);
}
condition!{ pub test: int -> (); }
pub fn raise() {
test::cond.raise(3);
}

View File

@ -1,40 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// xfail-fast
// aux-build:xc_conditions.rs
extern mod xc_conditions;
use xc_conditions::oops;
use xc_conditions::trouble;
// Tests of cross-crate conditions; the condition is
// defined in lib, and we test various combinations
// of `trap` and `raise` in the client or the lib where
// the condition was defined. Also in test #4 we use
// more complex features (generics, traits) in
// combination with the condition.
//
// trap raise
// ------------
// xc_conditions : client lib
// xc_conditions_2: client client
// xc_conditions_3: lib client
// xc_conditions_4: client client (with traits)
//
// the trap=lib, raise=lib case isn't tested since
// there's no cross-crate-ness to test in that case.
pub fn main() {
oops::cond.trap(|_i| 12345).inside(|| {
let x = trouble();
assert_eq!(x,12345);
})
}

View File

@ -1,19 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// xfail-fast
// aux-build:xc_conditions_2.rs
extern mod xc_conditions_2;
use xcc = xc_conditions_2;
pub fn main() {
xcc::oops::cond.trap(|_| 1).inside(|| xcc::oops::cond.raise(1));
}

View File

@ -1,38 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// xfail-fast
// aux-build:xc_conditions_3.rs
extern mod xc_conditions_3;
use xcc = xc_conditions_3;
pub fn main() {
assert_eq!(xcc::guard(a, 1), 40);
}
pub fn a() -> int {
assert_eq!(xcc::oops::cond.raise(7), 7);
xcc::guard(b, 2)
}
pub fn b() -> int {
assert_eq!(xcc::oops::cond.raise(8), 16);
xcc::guard(c, 3)
}
pub fn c() -> int {
assert_eq!(xcc::oops::cond.raise(9), 27);
xcc::guard(d, 4)
}
pub fn d() -> int {
xcc::oops::cond.raise(10)
}

View File

@ -1,32 +0,0 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// xfail-fast
// aux-build:xc_conditions_4.rs
extern mod xc_conditions_4;
use xcc = xc_conditions_4;
struct SThunk {
x: int
}
impl xcc::Thunk<xcc::Color> for SThunk {
fn call(self) -> xcc::Color {
xcc::oops::cond.raise((self.x, 1.23, ~"oh no"))
}
}
pub fn main() {
xcc::oops::cond.trap(|_| xcc::Red).inside(|| {
let t = SThunk { x : 10 };
assert_eq!(xcc::callback(t), xcc::Red)
})
}

View File

@ -18,11 +18,4 @@ use other = xcrate_static_addresses;
pub fn main() {
other::verify_same(&other::global);
other::verify_same2(other::global2);
// Previously this fail'd because there were two addresses that were being
// used when declaring constants.
other::test::cond.trap(|_| {
}).inside(|| {
other::raise();
})
}