Skip to content

Commit 6b0fd78

Browse files
author
Palmer Cox
committed
Create io::util::IteratorExtension to provide fail_on_error() and update io iterators to produce IoResults
Most IO related functions return an IoResult so that the caller can handle failure in whatever way is appropriate. However, the `lines`, `bytes`, and `chars` iterators all supress errors. This means that code that needs to handle errors can't use any of these iterators. All three of these iterators were updated to produce IoResults. The problem with returning IoResults is that the caller now *has* to handle error conditions since the produced value is now wrapped in an IoResult. This complicates simple example code or one-off programs. In order to mitigate that problem, a new extension trait, io::util::IteratorExtension, is created that provides a new method for all iterators over IoResults - fail_on_error(). This method wraps the iterator in a FailOnError iterator that unwraps all results before producing them, but fails if a non-EndOfFile error is encountered. This allows for simple programs to remain almost as simple as before. With these changes, the existing behavior of supressing all errors for these iterators is effectively no longer available. Fixes rust-lang#12368
1 parent 62f1d68 commit 6b0fd78

File tree

8 files changed

+156
-47
lines changed

8 files changed

+156
-47
lines changed

src/compiletest/errors.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// except according to those terms.
1010

1111
use std::io::{BufferedReader, File};
12+
use std::io::util::IteratorExtensions;
1213

1314
pub struct ExpectedError { line: uint, kind: ~str, msg: ~str }
1415

@@ -18,7 +19,7 @@ pub fn load_errors(testfile: &Path) -> ~[ExpectedError] {
1819
let mut error_patterns = ~[];
1920
let mut rdr = BufferedReader::new(File::open(testfile).unwrap());
2021
let mut line_num = 1u;
21-
for ln in rdr.lines() {
22+
for ln in rdr.lines().fail_on_error() {
2223
error_patterns.push_all_move(parse_expected(line_num, ln));
2324
line_num += 1u;
2425
}

src/compiletest/header.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,10 @@ pub fn is_test_ignored(config: &config, testfile: &Path) -> bool {
134134

135135
fn iter_header(testfile: &Path, it: |&str| -> bool) -> bool {
136136
use std::io::{BufferedReader, File};
137+
use std::io::util::IteratorExtensions;
137138

138139
let mut rdr = BufferedReader::new(File::open(testfile).unwrap());
139-
for ln in rdr.lines() {
140+
for ln in rdr.lines().fail_on_error() {
140141
// Assume that any directives will be found before the first
141142
// module or function. This doesn't seem to be an optimization
142143
// with a warm page cache. Maybe with a cold one.

src/libstd/io/buffered.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -537,9 +537,9 @@ mod test {
537537
let in_buf = MemReader::new(bytes!("a\nb\nc").to_owned());
538538
let mut reader = BufferedReader::with_capacity(2, in_buf);
539539
let mut it = reader.lines();
540-
assert_eq!(it.next(), Some(~"a\n"));
541-
assert_eq!(it.next(), Some(~"b\n"));
542-
assert_eq!(it.next(), Some(~"c"));
540+
assert_eq!(it.next(), Some(Ok(~"a\n")));
541+
assert_eq!(it.next(), Some(Ok(~"b\n")));
542+
assert_eq!(it.next(), Some(Ok(~"c")));
543543
assert_eq!(it.next(), None);
544544
}
545545

@@ -569,8 +569,8 @@ mod test {
569569
let buf = [195u8, 159u8, 'a' as u8];
570570
let mut reader = BufferedReader::with_capacity(1, BufReader::new(buf));
571571
let mut it = reader.chars();
572-
assert_eq!(it.next(), Some('ß'));
573-
assert_eq!(it.next(), Some('a'));
572+
assert_eq!(it.next(), Some(Ok('ß')));
573+
assert_eq!(it.next(), Some(Ok('a')));
574574
assert_eq!(it.next(), None);
575575
}
576576

src/libstd/io/extensions.rs

+16-11
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,28 @@
1515
// FIXME: Not sure how this should be structured
1616
// FIXME: Iteration should probably be considered separately
1717

18+
use prelude::*;
1819
use container::Container;
1920
use iter::Iterator;
2021
use option::Option;
21-
use io::Reader;
22+
use io;
23+
use io::{IoError, IoResult, Reader};
2224
use vec::{OwnedVector, ImmutableVector};
2325
use ptr::RawPtr;
2426

2527
/// An iterator that reads a single byte on each iteration,
26-
/// until `.read_byte()` returns `None`.
28+
/// until `.read_byte()` returns `EndOfFile`.
2729
///
2830
/// # Notes about the Iteration Protocol
2931
///
3032
/// The `Bytes` may yield `None` and thus terminate
3133
/// an iteration, but continue to yield elements if iteration
3234
/// is attempted again.
3335
///
34-
/// # Failure
36+
/// # Error
3537
///
36-
/// Raises the same conditions as the `read` method, for
37-
/// each call to its `.next()` method.
38-
/// Yields `None` if the condition is handled.
38+
/// Any error other than EndOfFile that is produced by the underlying Reader
39+
/// is returned by the iterator and should be handled by the caller.
3940
pub struct Bytes<'r, T> {
4041
priv reader: &'r mut T,
4142
}
@@ -46,10 +47,14 @@ impl<'r, R: Reader> Bytes<'r, R> {
4647
}
4748
}
4849

49-
impl<'r, R: Reader> Iterator<u8> for Bytes<'r, R> {
50+
impl<'r, R: Reader> Iterator<IoResult<u8>> for Bytes<'r, R> {
5051
#[inline]
51-
fn next(&mut self) -> Option<u8> {
52-
self.reader.read_byte().ok()
52+
fn next(&mut self) -> Option<IoResult<u8>> {
53+
match self.reader.read_byte() {
54+
Ok(x) => Some(Ok(x)),
55+
Err(IoError { kind: io::EndOfFile, .. }) => None,
56+
Err(e) => Some(Err(e))
57+
}
5358
}
5459
}
5560

@@ -257,7 +262,7 @@ mod test {
257262
count: 0,
258263
};
259264
let byte = reader.bytes().next();
260-
assert!(byte == Some(10));
265+
assert!(byte == Some(Ok(10)));
261266
}
262267

263268
#[test]
@@ -272,7 +277,7 @@ mod test {
272277
let mut reader = ErroringReader;
273278
let mut it = reader.bytes();
274279
let byte = it.next();
275-
assert!(byte.is_none());
280+
assert!(byte.unwrap().is_err());
276281
}
277282

278283
#[test]

src/libstd/io/mod.rs

+36-27
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ Some examples of obvious things you might want to do
2929
3030
```rust
3131
use std::io;
32+
use std::io::util::IteratorExtensions;
3233
33-
for line in io::stdin().lines() {
34+
for line in io::stdin().lines().fail_on_error() {
3435
print!("{}", line);
3536
}
3637
```
@@ -60,23 +61,29 @@ Some examples of obvious things you might want to do
6061
```rust
6162
use std::io::BufferedReader;
6263
use std::io::File;
64+
use std::io::util::IteratorExtensions;
6365
6466
let path = Path::new("message.txt");
67+
# drop(File::create(&path).unwrap());
6568
let mut file = BufferedReader::new(File::open(&path));
66-
for line in file.lines() {
69+
for line in file.lines().fail_on_error() {
6770
print!("{}", line);
6871
}
72+
# let _ = ::std::io::fs::unlink(&path);
6973
```
7074
7175
* Pull the lines of a file into a vector of strings
7276
7377
```rust
7478
use std::io::BufferedReader;
7579
use std::io::File;
80+
use std::io::util::IteratorExtensions;
7681
7782
let path = Path::new("message.txt");
83+
# drop(File::create(&path).unwrap());
7884
let mut file = BufferedReader::new(File::open(&path));
79-
let lines: ~[~str] = file.lines().collect();
85+
let lines: ~[~str] = file.lines().fail_on_error().collect();
86+
# let _ = ::std::io::fs::unlink(&path);
8087
```
8188
8289
* Make a simple TCP client connection and request
@@ -432,10 +439,8 @@ pub trait Reader {
432439
///
433440
/// # Error
434441
///
435-
/// The iterator protocol causes all specifics about errors encountered to
436-
/// be swallowed. All errors will be signified by returning `None` from the
437-
/// iterator. If this is undesirable, it is recommended to use the
438-
/// `read_byte` method.
442+
/// Any error other than EndOfFile that is produced by the underlying Reader
443+
/// is returned by the iterator and should be handled by the caller.
439444
fn bytes<'r>(&'r mut self) -> extensions::Bytes<'r, Self> {
440445
extensions::Bytes::new(self)
441446
}
@@ -952,7 +957,7 @@ pub trait Stream: Reader + Writer { }
952957
impl<T: Reader + Writer> Stream for T {}
953958

954959
/// An iterator that reads a line on each iteration,
955-
/// until `.read_line()` returns `None`.
960+
/// until `.read_line()` encounters EndOfFile.
956961
///
957962
/// # Notes about the Iteration Protocol
958963
///
@@ -962,21 +967,24 @@ impl<T: Reader + Writer> Stream for T {}
962967
///
963968
/// # Error
964969
///
965-
/// This iterator will swallow all I/O errors, transforming `Err` values to
966-
/// `None`. If errors need to be handled, it is recommended to use the
967-
/// `read_line` method directly.
970+
/// Any error other than EndOfFile that is produced by the underlying Reader
971+
/// is returned by the iterator and should be handled by the caller.
968972
pub struct Lines<'r, T> {
969973
priv buffer: &'r mut T,
970974
}
971975

972-
impl<'r, T: Buffer> Iterator<~str> for Lines<'r, T> {
973-
fn next(&mut self) -> Option<~str> {
974-
self.buffer.read_line().ok()
976+
impl<'r, T: Buffer> Iterator<IoResult<~str>> for Lines<'r, T> {
977+
fn next(&mut self) -> Option<IoResult<~str>> {
978+
match self.buffer.read_line() {
979+
Ok(x) => Some(Ok(x)),
980+
Err(IoError { kind: EndOfFile, ..}) => None,
981+
Err(y) => Some(Err(y))
982+
}
975983
}
976984
}
977985

978986
/// An iterator that reads a utf8-encoded character on each iteration,
979-
/// until `.read_char()` returns `None`.
987+
/// until `.read_char()` encounters EndOfFile.
980988
///
981989
/// # Notes about the Iteration Protocol
982990
///
@@ -986,16 +994,19 @@ impl<'r, T: Buffer> Iterator<~str> for Lines<'r, T> {
986994
///
987995
/// # Error
988996
///
989-
/// This iterator will swallow all I/O errors, transforming `Err` values to
990-
/// `None`. If errors need to be handled, it is recommended to use the
991-
/// `read_char` method directly.
997+
/// Any error other than EndOfFile that is produced by the underlying Reader
998+
/// is returned by the iterator and should be handled by the caller.
992999
pub struct Chars<'r, T> {
9931000
priv buffer: &'r mut T
9941001
}
9951002

996-
impl<'r, T: Buffer> Iterator<char> for Chars<'r, T> {
997-
fn next(&mut self) -> Option<char> {
998-
self.buffer.read_char().ok()
1003+
impl<'r, T: Buffer> Iterator<IoResult<char>> for Chars<'r, T> {
1004+
fn next(&mut self) -> Option<IoResult<char>> {
1005+
match self.buffer.read_char() {
1006+
Ok(x) => Some(Ok(x)),
1007+
Err(IoError { kind: EndOfFile, ..}) => None,
1008+
Err(y) => Some(Err(y))
1009+
}
9991010
}
10001011
}
10011012

@@ -1061,9 +1072,8 @@ pub trait Buffer: Reader {
10611072
///
10621073
/// # Error
10631074
///
1064-
/// This iterator will transform all error values to `None`, discarding the
1065-
/// cause of the error. If this is undesirable, it is recommended to call
1066-
/// `read_line` directly.
1075+
/// Any error other than EndOfFile that is produced by the underlying Reader
1076+
/// is returned by the iterator and should be handled by the caller.
10671077
fn lines<'r>(&'r mut self) -> Lines<'r, Self> {
10681078
Lines { buffer: self }
10691079
}
@@ -1149,9 +1159,8 @@ pub trait Buffer: Reader {
11491159
///
11501160
/// # Error
11511161
///
1152-
/// This iterator will transform all error values to `None`, discarding the
1153-
/// cause of the error. If this is undesirable, it is recommended to call
1154-
/// `read_char` directly.
1162+
/// Any error other than EndOfFile that is produced by the underlying Reader
1163+
/// is returned by the iterator and should be handled by the caller.
11551164
fn chars<'r>(&'r mut self) -> Chars<'r, Self> {
11561165
Chars { buffer: self }
11571166
}

src/libstd/io/util.rs

+91
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,49 @@ pub fn copy<R: Reader, W: Writer>(r: &mut R, w: &mut W) -> io::IoResult<()> {
195195
}
196196
}
197197

198+
/// An extension trait that adds utility methods to Iterators over IoResults
199+
pub trait IteratorExtensions {
200+
/// Produce unwrapped elements until an error is encountered. If the error
201+
/// is EndOfFile, None is produced to end iteration. If any other error is
202+
/// encountered, the iterator fail!()s.
203+
///
204+
/// This method is useful for programs that do not want to concern themselves
205+
/// with error handling, such as simple example code or one time use programs.
206+
/// More robust programs should avoid using this method unless task failure is
207+
/// the desired behavior when an IO error occurs.
208+
fn fail_on_error(self) -> FailOnError<Self> {
209+
FailOnError {
210+
wrapped: self
211+
}
212+
}
213+
}
214+
215+
impl <T, I: Iterator<io::IoResult<T>>> IteratorExtensions for I {}
216+
217+
/// An iterator which produces unwrapped values from the wrapped iterator until
218+
/// EndOfFile is encountered. The iterator fail!()s if any other error is
219+
/// encountered.
220+
pub struct FailOnError<I> {
221+
priv wrapped: I
222+
}
223+
224+
impl <T, I: Iterator<io::IoResult<T>>> Iterator<T> for FailOnError<I> {
225+
fn next(&mut self) -> Option<T> {
226+
// All existing IO iterators already produce None when they encounter
227+
// EndOfFile. However, in an effort to be more general, this iterator also
228+
// checks for EndOfFile and returns None in that case as well.
229+
match self.wrapped.next() {
230+
Some(x) => match x {
231+
Ok(y) => Some(y),
232+
Err(io::IoError { kind: io::EndOfFile, .. }) => None,
233+
Err(e) => fail!(e.to_str())
234+
},
235+
None => None
236+
}
237+
}
238+
}
239+
240+
198241
#[cfg(test)]
199242
mod test {
200243
use io;
@@ -307,4 +350,52 @@ mod test {
307350
copy(&mut r, &mut w).unwrap();
308351
assert_eq!(~[0, 1, 2, 3, 4], w.unwrap());
309352
}
353+
354+
// This tests the usual case - using IteratorExtensions' fail_on_error()
355+
// with an Iterator that already produces None when it encounters EndOfFile.
356+
#[test]
357+
fn test_fail_on_error_with_lines() {
358+
let mut reader = MemReader::new(bytes!("a\nb\nc").to_owned());
359+
let mut it = reader.lines().fail_on_error();
360+
assert_eq!(it.next(), Some(~"a\n"));
361+
assert_eq!(it.next(), Some(~"b\n"));
362+
assert_eq!(it.next(), Some(~"c"));
363+
assert_eq!(it.next(), None);
364+
}
365+
366+
struct CountDown {
367+
count: uint,
368+
end: io::IoErrorKind
369+
}
370+
371+
impl Iterator<io::IoResult<uint>> for CountDown {
372+
fn next(&mut self) -> Option<io::IoResult<uint>> {
373+
if self.count > 0 {
374+
self.count -= 1;
375+
Some(Ok(self.count + 1))
376+
} else {
377+
Some(Err(io::IoError { kind: self.end, desc: "", detail: None } ))
378+
}
379+
}
380+
}
381+
382+
// This tests that fail_on_error() handles EndOfFile correctly for iterators
383+
// that don't return None when they encounter EndOfFile.
384+
#[test]
385+
fn test_fail_on_error_eof() {
386+
let iter = CountDown { count: 1, end: io::EndOfFile };
387+
let mut iter = iter.fail_on_error();
388+
assert!(iter.next().unwrap() == 1);
389+
assert!(iter.next().is_none());
390+
}
391+
392+
// This test checks that fail_on_error() does indeed fail!() if an iterator
393+
// produces an Err other than EndOfFile.
394+
#[test]
395+
#[should_fail]
396+
fn test_fail_on_error_failing() {
397+
let iter = CountDown { count: 0, end: io::OtherIoError };
398+
let mut iter = iter.fail_on_error();
399+
iter.next();
400+
}
310401
}

src/test/bench/shootout-k-nucleotide-pipes.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use std::mem::replace;
2323
use std::option;
2424
use std::os;
2525
use std::io;
26+
use std::io::util::IteratorExtensions;
2627
use std::str;
2728
use std::task;
2829
use std::vec;
@@ -181,7 +182,7 @@ fn main() {
181182
// reading the sequence of interest
182183
let mut proc_mode = false;
183184
184-
for line in rdr.lines() {
185+
for line in rdr.lines().fail_on_error() {
185186
let line = line.trim().to_owned();
186187
187188
if line.len() == 0u { continue; }

0 commit comments

Comments
 (0)