diff --git a/doc/rust.css b/doc/rust.css index 402519162b53a..bcde838ab4d86 100644 --- a/doc/rust.css +++ b/doc/rust.css @@ -216,6 +216,10 @@ dd { list-style-type: none; padding-left: 0px; } +/* Only display one level of hierarchy in the TOC */ +#TOC ul ul { + display: none; +} sub, sup { diff --git a/doc/tutorial-conditions.md b/doc/tutorial-conditions.md index 2fb35fce9c46a..6782a05fbf9c2 100644 --- a/doc/tutorial-conditions.md +++ b/doc/tutorial-conditions.md @@ -45,22 +45,20 @@ An example program that does this task reads like this: ~~~~{.xfail-test} # #[allow(unused_imports)]; -extern mod extra; -use extra::fileinput::FileInput; -use std::int; -# mod FileInput { -# use std::io::{Reader, BytesReader}; -# static s : &'static [u8] = bytes!("1 2\n\ -# 34 56\n\ -# 789 123\n\ -# 45 67\n\ -# "); -# pub fn from_args() -> @Reader{ -# @BytesReader { -# bytes: s, -# pos: @mut 0 -# } as @Reader -# } +use std::io::buffered::BufferedReader; +use std::io::fs::File; +# mod BufferedReader { +# use std::io::fs::File; +# use std::io::mem::MemReader; +# use std::io::buffered::BufferedReader; +# static s : &'static [u8] = bytes!("1 2\n\ +# 34 56\n\ +# 789 123\n\ +# 45 67\n\ +# "); +# pub fn new(_inner: Option) -> BufferedReader { +# BufferedReader::new(MemReader::new(s.to_owned())) +# } # } fn main() { @@ -70,42 +68,37 @@ fn main() { } } - fn read_int_pairs() -> ~[(int,int)] { - let mut pairs = ~[]; - let fi = FileInput::from_args(); - while ! fi.eof() { + let args = std::os::args(); - // 1. Read a line of input. - let line = fi.read_line(); + // Path takes a generic by-value, rather than by reference + let path = Path::new(args.get_opt(1).expect("No input file parameter!").as_slice()); + 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 } ~~~~ @@ -115,7 +108,6 @@ 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`. @@ -152,7 +144,6 @@ 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, @@ -181,7 +172,7 @@ This would give the caller more information for both handling and reporting the 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 -- +— 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 @@ -251,26 +242,23 @@ 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`: -~~~~ {.xfail-test} -# #[allowed(unused_imports)]; -extern mod extra; -use extra::fileinput::FileInput; -use std::int; +~~~~{.xfail-test} +# #[allow(unused_imports)]; +use std::io::buffered::BufferedReader; +use std::io::fs::File; use std::task; -# mod FileInput { -# use std::io::{Reader, BytesReader}; -# static s : &'static [u8] = bytes!("1 2\n\ -# 34 56\n\ -# ostrich\n\ -# 789 123\n\ -# 45 67\n\ -# "); -# pub fn from_args() -> @Reader{ -# @BytesReader { -# bytes: s, -# pos: @mut 0 -# } as @Reader -# } +# mod BufferedReader { +# use std::io::fs::File; +# use std::io::mem::MemReader; +# use std::io::buffered::BufferedReader; +# static s : &'static [u8] = bytes!("1 2\n\ +# 34 56\n\ +# 789 123\n\ +# 45 67\n\ +# "); +# pub fn new(_inner: Option) -> BufferedReader { +# BufferedReader::new(MemReader::new(s.to_owned())) +# } # } fn main() { @@ -292,11 +280,12 @@ fn main() { fn read_int_pairs() -> ~[(int,int)] { let mut pairs = ~[]; - let fi = FileInput::from_args(); - while ! fi.eof() { - let line = fi.read_line(); - let fields = line.words().to_owned_vec(); - match fields { + let args = std::os::args(); + let path = Path::new(args.get_opt(1).expect("No input file parameter!").as_slice()); + + 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())), @@ -324,7 +313,6 @@ 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". @@ -358,25 +346,22 @@ If no handler is found, `Condition::raise` will fail the task with an appropriat 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: -~~~~ {.xfail-test} +~~~~{.xfail-test} # #[allow(unused_imports)]; -extern mod extra; -use extra::fileinput::FileInput; -use std::int; -# mod FileInput { -# use std::io::{Reader, BytesReader}; -# static s : &'static [u8] = bytes!("1 2\n\ -# 34 56\n\ -# ostrich\n\ -# 789 123\n\ -# 45 67\n\ -# "); -# pub fn from_args() -> @Reader{ -# @BytesReader { -# bytes: s, -# pos: @mut 0 -# } as @Reader -# } +use std::io::buffered::BufferedReader; +use std::io::fs::File; +# mod BufferedReader { +# use std::io::fs::File; +# use std::io::mem::MemReader; +# use std::io::buffered::BufferedReader; +# static s : &'static [u8] = bytes!("1 2\n\ +# 34 56\n\ +# 789 123\n\ +# 45 67\n\ +# "); +# pub fn new(_inner: Option) -> BufferedReader { +# BufferedReader::new(MemReader::new(s.to_owned())) +# } # } // Introduce a new condition. @@ -393,14 +378,14 @@ fn main() { fn read_int_pairs() -> ~[(int,int)] { let mut pairs = ~[]; - let fi = FileInput::from_args(); - while ! fi.eof() { - let line = fi.read_line(); - let fields = line.words().to_owned_vec(); - match fields { + let args = std::os::args(); + let path = Path::new(args.get_opt(1).expect("No input file parameter!").as_slice()); + + 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())) @@ -432,23 +417,20 @@ and replaces bad input lines with the pair `(-1,-1)`: ~~~~{.xfail-test} # #[allow(unused_imports)]; -extern mod extra; -use extra::fileinput::FileInput; -use std::int; -# mod FileInput { -# use std::io::{Reader, BytesReader}; -# static s : &'static [u8] = bytes!("1 2\n\ -# 34 56\n\ -# ostrich\n\ -# 789 123\n\ -# 45 67\n\ -# "); -# pub fn from_args() -> @Reader{ -# @BytesReader { -# bytes: s, -# pos: @mut 0 -# } as @Reader -# } +use std::io::buffered::BufferedReader; +use std::io::fs::File; +# mod BufferedReader { +# use std::io::fs::File; +# use std::io::mem::MemReader; +# use std::io::buffered::BufferedReader; +# static s : &'static [u8] = bytes!("1 2\n\ +# 34 56\n\ +# 789 123\n\ +# 45 67\n\ +# "); +# pub fn new(_inner: Option) -> BufferedReader { +# BufferedReader::new(MemReader::new(s.to_owned())) +# } # } condition! { @@ -470,11 +452,12 @@ fn main() { fn read_int_pairs() -> ~[(int,int)] { let mut pairs = ~[]; - let fi = FileInput::from_args(); - while ! fi.eof() { - let line = fi.read_line(); - let fields = line.words().to_owned_vec(); - match fields { + let args = std::os::args(); + let path = Path::new(args.get_opt(1).expect("No input file parameter!").as_slice()); + + 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())) @@ -509,23 +492,20 @@ Changing the condition's return type from `(int,int)` to `Option<(int,int)>` wil ~~~~{.xfail-test} # #[allow(unused_imports)]; -extern mod extra; -use extra::fileinput::FileInput; -use std::int; -# mod FileInput { -# use std::io::{Reader, BytesReader}; -# static s : &'static [u8] = bytes!("1 2\n\ -# 34 56\n\ -# ostrich\n\ -# 789 123\n\ -# 45 67\n\ -# "); -# pub fn from_args() -> @Reader{ -# @BytesReader { -# bytes: s, -# pos: @mut 0 -# } as @Reader -# } +use std::io::buffered::BufferedReader; +use std::io::fs::File; +# mod BufferedReader { +# use std::io::fs::File; +# use std::io::mem::MemReader; +# use std::io::buffered::BufferedReader; +# static s : &'static [u8] = bytes!("1 2\n\ +# 34 56\n\ +# 789 123\n\ +# 45 67\n\ +# "); +# pub fn new(_inner: Option) -> BufferedReader { +# BufferedReader::new(MemReader::new(s.to_owned())) +# } # } // Modify the condition signature to return an Option. @@ -548,11 +528,12 @@ fn main() { fn read_int_pairs() -> ~[(int,int)] { let mut pairs = ~[]; - let fi = FileInput::from_args(); - while ! fi.eof() { - let line = fi.read_line(); - let fields = line.words().to_owned_vec(); - match fields { + let args = std::os::args(); + let path = Path::new(args.get_opt(1).expect("No input file parameter!").as_slice()); + + 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())), @@ -596,23 +577,20 @@ This can be encoded in the handler API by introducing a helper type: `enum Malfo ~~~~{.xfail-test} # #[allow(unused_imports)]; -extern mod extra; -use extra::fileinput::FileInput; -use std::int; -# mod FileInput { -# use std::io::{Reader, BytesReader}; -# static s : &'static [u8] = bytes!("1 2\n\ -# 34 56\n\ -# ostrich\n\ -# 789 123\n\ -# 45 67\n\ -# "); -# pub fn from_args() -> @Reader{ -# @BytesReader { -# bytes: s, -# pos: @mut 0 -# } as @Reader -# } +use std::io::buffered::BufferedReader; +use std::io::fs::File; +# mod BufferedReader { +# use std::io::fs::File; +# use std::io::mem::MemReader; +# use std::io::buffered::BufferedReader; +# static s : &'static [u8] = bytes!("1 2\n\ +# 34 56\n\ +# 789 123\n\ +# 45 67\n\ +# "); +# pub fn new(_inner: Option) -> BufferedReader { +# BufferedReader::new(MemReader::new(s.to_owned())) +# } # } // Introduce a new enum to convey condition-handling strategy to error site. @@ -644,11 +622,12 @@ fn main() { fn read_int_pairs() -> ~[(int,int)] { let mut pairs = ~[]; - let fi = FileInput::from_args(); - while ! fi.eof() { - let line = fi.read_line(); - let fields = line.words().to_owned_vec(); - match fields { + let args = std::os::args(); + let path = Path::new(args.get_opt(1).expect("No input file parameter!").as_slice()); + + 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())), @@ -717,28 +696,25 @@ $ ./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, +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: ~~~~{.xfail-test} # #[allow(unused_imports)]; -extern mod extra; -use extra::fileinput::FileInput; -use std::int; -# mod FileInput { -# use std::io::{Reader, BytesReader}; -# static s : &'static [u8] = bytes!("1 2\n\ -# 34 56\n\ -# 7 marmot\n\ -# 789 123\n\ -# 45 67\n\ -# "); -# pub fn from_args() -> @Reader{ -# @BytesReader { -# bytes: s, -# pos: @mut 0 -# } as @Reader -# } +use std::io::buffered::BufferedReader; +use std::io::fs::File; +# mod BufferedReader { +# use std::io::fs::File; +# use std::io::mem::MemReader; +# use std::io::buffered::BufferedReader; +# static s : &'static [u8] = bytes!("1 2\n\ +# 34 56\n\ +# 789 123\n\ +# 45 67\n\ +# "); +# pub fn new(_inner: Option) -> BufferedReader { +# BufferedReader::new(MemReader::new(s.to_owned())) +# } # } pub enum MalformedLineFix { @@ -784,12 +760,12 @@ fn parse_int(x: &str) -> int { fn read_int_pairs() -> ~[(int,int)] { let mut pairs = ~[]; - let fi = FileInput::from_args(); - while ! fi.eof() { - let line = fi.read_line(); - let fields = line.words().to_owned_vec(); - match fields { + let args = std::os::args(); + let path = Path::new(args.get_opt(1).expect("No input file parameter!").as_slice()); + 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))), @@ -850,7 +826,7 @@ Each is appropriate to different circumstances: 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 -- + - 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. @@ -869,7 +845,7 @@ but with the option to halt unwinding partway through the process and continue e 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 -- +— 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: diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs index c5ae645751590..8f0fedfe0dbc9 100644 --- a/src/libstd/io/mod.rs +++ b/src/libstd/io/mod.rs @@ -476,9 +476,9 @@ pub trait Reader { /// /// # Example /// - /// let reader = File::open(&Path::new("foo.txt")) - /// while !reader.eof() { - /// println(reader.read_line()); + /// let mut reader = BufferedReader::new(File::open(&Path::new("foo.txt"))); + /// for line in reader.lines() { + /// println(line); /// } /// /// # Failure