-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Add FailingReader<R:Reader> to make every Reader fail on errors #12368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Is this much better than calling |
@alexcrichton : My purpose was in using iterators. I think that iterators silently ignore errors of the underlying |
How about something that's similar to this issue (in that it wraps a reader and modifies its error behaviour) and similar to our old conditions. High-level overview: have errors ignored when they occur, but recorded into an external user-visible handle, and so they can decide to handle errors there. struct ErrorHandleInternals { priv error: Option<IoError> }
pub struct ErrorHandle { priv inner: *mut ErrorHandleInternals }
impl ErrorHandle {
/// Retrieve any IO error that has occur before this point on the
/// corresponding `ChompingReader`.
/// This resets the error state and so an immediate second
/// call will return `Ok`.
pub fn error(&mut self) -> IoResult<()> {
Err(unsafe {(*self.inner).error.take_unwrap()})
}
}
pub struct ChompingReader<R> {
priv inner: R,
priv error_handle: ~ErrorHandleInternals,
// can't have concurrent access to `error_handle`
priv marker: std::kinds::marker::NoSend
}
impl ChompingReader<R> {
pub fn new(reader: R) -> (ChompingReader<R>, ErrorHandle) { ... }
}
impl<R: Reader> Reader for ChompingReader<R> {
fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> {
match self.inner.read(buf) {
Ok(x) => Ok(x)
Err(e) => {
// set an indication that an error happened
self.error_handle.error = Some(e)
Err(/* substitute an EOF error */)
}
}
}
} Used like let (mut reader, mut handle) = ChompingReader::new(my_reader);
// no failure, but errors are recorded, and the loop quits immediately...
for line in reader.lines() {
foo(line);
}
// ... so handling the error here immediately after the loop
// is pretty similar to handling it manually with .read_line.
match handle.error() {
Err(e) => println!("error occured in `.lines`)
Ok(()) => {}
} On first blush this is a lot of effort to use, and doesn't exactly seem to be worth it for things like |
It's also entirely possible that the |
To be sure that errors are not ignored, we could have either the fn drop(&mut self) {
if !task::failing() {
match *self {
Err(err) => fail!("unhandled IO error: {}" ,err),
Ok(_) => {}
}
}
} (Possibly with an explicit |
If fn main() {
let mut lines = io::stdin().lines();
for line in lines {
handle_line(line);
}
match lines.err {
Ok(()) => (),
Err(e) => handle_error(e)
}
} Add to that what @huonw suggested, which is that to avoid ignoring the error (which is the root cause of this issue), you'd need a destructor. Besides being ugly, this also has the problem of turning what should be a compile-time lint into an unexpected runtime failure. If I'm going to have runtime failure, I'd rather it just be triggered directly from At this point, I'm leaning towards saying that |
I think we should either remove lines() entirely or convert it to return an IoResult like every other io related method. Having lines() behave differently than everything else seems like it could cause issues. But that doesn't mean that we can't keep it pleasant to use. I posted to the ML that I favored just removing it. However, thinking about it more I think that creating a wrapping iterator that fails on errors might work too. Lets say that we added something like the following to libstd: trait IoResultIterator {
fn fail_on_error(self) -> FailOnErrorIterator<Self> {
FailOnErrorIterator {
wrapped: self
}
}
}
impl <T, I: Iterator<IoResult<T>>> IoResultIterator for I {}
struct FailOnErrorIterator<I> {
wrapped: I
}
impl <T, I: Iterator<IoResult<T>>> Iterator<T> for FailOnErrorIterator<I> {
fn next(&mut self) -> Option<T> {
match self.wrapped.next() {
Some(x) => match x {
Ok(y) => Some(y),
Err(io::IoError { kind: io::EndOfFile, .. }) => None,
_ => fail!()
},
None => None
}
}
} Then, I think the simple "read lines from STDIN case" becomes: fn main() {
for line in io::stdin().lines().fail_on_error() {
print!("received: {}", line);
}
} which is a little bit more verbose than it is now where it ignores errors. However, it doesn't risk surprising anyone with a failure that they didn't expect or force them to check for errors differently with lines() than anywhere else. EDITED to say that the wrapper fails on errors. |
@mneumann Does having an Iterator wrapper that fails on errors satisfy your use case (Assuming all IO related iterators are updated to return IoResult<>)? |
@DaGenix: Yes. |
I opened up #12414 which implements my proposal. I note in that issue that this discussion is still ongoing. I opened the issue to provide some code that can be analyzed, commented on, and/or criticized. |
…ate 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
…crichton 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. Fixes #12368
…exendoo Unnecessary call to min/max method Continuation of rust-lang/rust-clippy#12061 Fix rust-lang/rust-clippy#11901 and rust-lang/rust-clippy#11924 This implementation only deal with literal int, like `i32::MAX`, `-6_i32`, `0` changelog: added lint [`unnecessary_min_max`]
In case you want to fail upon an I/O error, you can wrap every Reader with a FailingReader.
The text was updated successfully, but these errors were encountered: