mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 23:04:33 +00:00
auto merge of #12039 : alexcrichton/rust/no-conditions, r=brson
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:
commit
87fe3ccf09
@ -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: $@)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
— for example when processing must halt on the very first error, or backtrack —
|
||||
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 — or at least flexible — 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 — possibly including failure —
|
||||
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
|
||||
— effectively transactional style against heap structures —
|
||||
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.
|
@ -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]
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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)*)
|
||||
|
@ -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();
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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`
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
@ -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;
|
||||
}
|
@ -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()
|
||||
})
|
||||
}
|
@ -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()
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
})
|
||||
}
|
@ -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));
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
@ -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();
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user