diff --git a/mk/docs.mk b/mk/docs.mk index a5577c27308..28d2fd80627 100644 --- a/mk/docs.mk +++ b/mk/docs.mk @@ -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: $@) diff --git a/mk/tests.mk b/mk/tests.mk index 85c63acb0f1..a1e0446e645 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -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 diff --git a/src/doc/guide-conditions.md b/src/doc/guide-conditions.md deleted file mode 100644 index 5b7494c0618..00000000000 --- a/src/doc/guide-conditions.md +++ /dev/null @@ -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) -> BufferedReader { -# 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::(a), from_str::(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`. -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`, -returning `Some(T)` on success and `None` on error. -In the example program, the call to `from_str::` returns `Option` -with the understanding that "all parse errors" result in `None`. -The resulting `Option` 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` 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 { - // ... -} -~~~~ - -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::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) -> BufferedReader { -# 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::(a).unwrap(), - from_str::(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) -> BufferedReader { -# 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::(a).unwrap(), - from_str::(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) -> BufferedReader { -# 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::(a).unwrap(), - from_str::(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) -> BufferedReader { -# 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::(a).unwrap(), - from_str::(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) -> BufferedReader { -# 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::(a).unwrap(), - from_str::(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 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) -> BufferedReader { -# 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::(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`, - 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`. - - - 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. diff --git a/src/libstd/c_str.rs b/src/libstd/c_str.rs index 7e79ad97df0..6fc31b673ac 100644 --- a/src/libstd/c_str.rs +++ b/src/libstd/c_str.rs @@ -75,24 +75,11 @@ 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(&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 @@ fn test_iterator() { #[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] diff --git a/src/libstd/condition.rs b/src/libstd/condition.rs deleted file mode 100644 index e0dc5c8b65d..00000000000 --- a/src/libstd/condition.rs +++ /dev/null @@ -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 or the MIT license -// , 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 { - priv handle: Closure, - priv prev: Option<@Handler>, -} - -/// 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 { - /// Name of the condition handler - name: &'static str, - /// TLS key used to insert/remove values in TLS. - key: local_data::Key<@Handler> -} - -impl Condition { - /// 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, - priv handler: @Handler -} - -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(&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 -} - -#[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); - } - } - } -} diff --git a/src/libstd/fmt/parse.rs b/src/libstd/fmt/parse.rs index 6c08eae7474..555fe29bed0 100644 --- a/src/libstd/fmt/parse.rs +++ b/src/libstd/fmt/parse.rs @@ -19,8 +19,6 @@ 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> for Parser<'a> { @@ -207,14 +207,15 @@ pub fn new<'a>(s: &'a str) -> 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 @@ fn fmtdflt() -> FormatSpec<'static> { } 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 @@ fn simple() { 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 @@ fn select_cases() { })]); } - #[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() { diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index adce11fed2d..0398af9c1c1 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -178,8 +178,6 @@ 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; diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index b31ae92d742..cd81d6037e6 100644 --- a/src/libstd/macros.rs +++ b/src/libstd/macros.rs @@ -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)*) diff --git a/src/libstd/path/mod.rs b/src/libstd/path/mod.rs index 4aa4a3feab1..f3f70c263ec 100644 --- a/src/libstd/path/mod.rs +++ b/src/libstd/path/mod.rs @@ -147,12 +147,6 @@ 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(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 @@ fn extension_str<'a>(&'a self) -> Option<&'a str> { /// /// # 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(&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 @@ fn set_filename(&mut self, filename: T) { /// /// # Failure /// - /// Raises the `null_byte` condition if the extension contains a NUL. + /// Fails the task if the extension contains a NUL. fn set_extension(&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 @@ fn set_extension(&mut self, extension: T) { 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 @@ fn set_extension(&mut self, extension: T) { 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 @@ fn set_extension(&mut self, extension: T) { /// /// # 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(&self, filename: T) -> Self { let mut p = self.clone(); @@ -383,7 +349,7 @@ fn with_filename(&self, filename: T) -> Self { /// /// # 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(&self, extension: T) -> Self { let mut p = self.clone(); @@ -408,16 +374,11 @@ fn dir_path(&self) -> Self { /// /// # Failure /// - /// Raises the `null_byte` condition if the path contains a NUL. + /// Fails the task if the path contains a NUL. #[inline] fn push(&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 @@ fn push_many(&mut self, paths: &[T]) { /// /// # Failure /// - /// Raises the `null_byte` condition if the path contains a NUL. + /// Fails the task if the path contains a NUL. #[inline] fn join(&self, path: T) -> Self { let mut p = self.clone(); diff --git a/src/libstd/path/posix.rs b/src/libstd/path/posix.rs index ba0cd0bb521..6970ebfb15d 100644 --- a/src/libstd/path/posix.rs +++ b/src/libstd/path/posix.rs @@ -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(path: T) -> Path { GenericPath::new(path) @@ -527,83 +527,21 @@ fn test_opt_paths() { #[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] diff --git a/src/libstd/path/windows.rs b/src/libstd/path/windows.rs index eec6f37b627..90154adb7fe 100644 --- a/src/libstd/path/windows.rs +++ b/src/libstd/path/windows.rs @@ -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(path: T) -> Path { @@ -1248,83 +1248,21 @@ fn test_opt_paths() { #[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] diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 55425eb2e72..376d685c8ac 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -44,7 +44,6 @@ * `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` diff --git a/src/libsyntax/ext/format.rs b/src/libsyntax/ext/format.rs index 1893cc0c722..9d2a891bf6b 100644 --- a/src/libsyntax/ext/format.rs +++ b/src/libsyntax/ext/format.rs @@ -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() { diff --git a/src/test/auxiliary/xc_conditions.rs b/src/test/auxiliary/xc_conditions.rs deleted file mode 100644 index 927602de169..00000000000 --- a/src/test/auxiliary/xc_conditions.rs +++ /dev/null @@ -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 or the MIT license -// , 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) -} diff --git a/src/test/auxiliary/xc_conditions_2.rs b/src/test/auxiliary/xc_conditions_2.rs deleted file mode 100644 index 16a5bb56343..00000000000 --- a/src/test/auxiliary/xc_conditions_2.rs +++ /dev/null @@ -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 or the MIT license -// , 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; -} diff --git a/src/test/auxiliary/xc_conditions_3.rs b/src/test/auxiliary/xc_conditions_3.rs deleted file mode 100644 index afe9de7abb9..00000000000 --- a/src/test/auxiliary/xc_conditions_3.rs +++ /dev/null @@ -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 or the MIT license -// , 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() - }) -} diff --git a/src/test/auxiliary/xc_conditions_4.rs b/src/test/auxiliary/xc_conditions_4.rs deleted file mode 100644 index 569f22d3644..00000000000 --- a/src/test/auxiliary/xc_conditions_4.rs +++ /dev/null @@ -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 or the MIT license -// , 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 { - fn call(self) -> T; -} - -pub fn callback>(t:TH) -> T { - t.call() -} diff --git a/src/test/auxiliary/xcrate_static_addresses.rs b/src/test/auxiliary/xcrate_static_addresses.rs index f0df2b1e71e..b6cbae2d2af 100644 --- a/src/test/auxiliary/xcrate_static_addresses.rs +++ b/src/test/auxiliary/xcrate_static_addresses.rs @@ -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); -} diff --git a/src/test/run-pass/xc_conditions_client.rs b/src/test/run-pass/xc_conditions_client.rs deleted file mode 100644 index 99df4b771c5..00000000000 --- a/src/test/run-pass/xc_conditions_client.rs +++ /dev/null @@ -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 or the MIT license -// , 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); - }) -} diff --git a/src/test/run-pass/xc_conditions_client_2.rs b/src/test/run-pass/xc_conditions_client_2.rs deleted file mode 100644 index b3164f1f0aa..00000000000 --- a/src/test/run-pass/xc_conditions_client_2.rs +++ /dev/null @@ -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 or the MIT license -// , 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)); -} diff --git a/src/test/run-pass/xc_conditions_client_3.rs b/src/test/run-pass/xc_conditions_client_3.rs deleted file mode 100644 index 7d16572c139..00000000000 --- a/src/test/run-pass/xc_conditions_client_3.rs +++ /dev/null @@ -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 or the MIT license -// , 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) -} diff --git a/src/test/run-pass/xc_conditions_client_4.rs b/src/test/run-pass/xc_conditions_client_4.rs deleted file mode 100644 index e8eea00e492..00000000000 --- a/src/test/run-pass/xc_conditions_client_4.rs +++ /dev/null @@ -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 or the MIT license -// , 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 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) - }) -} diff --git a/src/test/run-pass/xcrate-static-addresses.rs b/src/test/run-pass/xcrate-static-addresses.rs index 2ea8ab864e1..8fdeba84fb7 100644 --- a/src/test/run-pass/xcrate-static-addresses.rs +++ b/src/test/run-pass/xcrate-static-addresses.rs @@ -18,11 +18,4 @@ 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(); - }) }