Handling non std::io::Error(s) while implementing std::io::Write? - error-handling

I'm trying to implement std::io::Write over HTTP and I'm not sure how to handle errors that do not have a counterpart in std::io::ErrorKind.
Here's a short reproduction:
extern crate reqwest;
use std::io::Write;
use std::io::Result;
struct HttpClient {
// Some configurations (compression, certificates, timeouts)
}
impl Write for HttpClient {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
let client = ::reqwest::Client::builder().build()?;
let res = client.post("http://httpbin.org/post").body(buf).send()?;
Ok(buf.len())
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
The compiler responds with 2 errors:
error[E0277]: the trait bound `std::io::Error: std::convert::From<reqwest::Error>` is not satisfied
--> src/main.rs:12:22
|
12 | let client = ::reqwest::Client::builder().build()?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<reqwest::Error>` is not implemented for `std::io::Error`
|
= help: the following implementations were found:
<std::io::Error as std::convert::From<std::io::ErrorKind>>
<std::io::Error as std::convert::From<std::ffi::NulError>>
<std::io::Error as std::convert::From<std::io::IntoInnerError<W>>>
<std::io::Error as std::convert::From<serde_json::error::Error>>
<std::io::Error as std::convert::From<openssl::error::ErrorStack>>
= note: required by `std::convert::From::from`
error[E0277]: the trait bound `std::io::Error: std::convert::From<reqwest::Error>` is not satisfied
--> src/main.rs:13:19
|
13 | let res = client.post("http://httpbin.org/post").body(buf).send()?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<reqwest::Error>` is not implemented for `std::io::Error`
|
= help: the following implementations were found:
<std::io::Error as std::convert::From<std::io::ErrorKind>>
<std::io::Error as std::convert::From<std::ffi::NulError>>
<std::io::Error as std::convert::From<std::io::IntoInnerError<W>>>
<std::io::Error as std::convert::From<serde_json::error::Error>>
<std::io::Error as std::convert::From<openssl::error::ErrorStack>>
= note: required by `std::convert::From::from`
There's a couple of things I could do, but I'm not happy with either of them:
Use map_err to map reqwest::Error to std::io::Error - This is not always trivial. For example, how would I map TooManyRedirects? I could use std::io::ErrorKind::Other but it doesn't feel right.
Define my own error type MyError and implement std::convert::From for reqwest::Error to MyError and for MyError to std::io::Error - This raises the same concerns from before - not all errors are easily convertible.
Are there any other better options here?

Using io::Error is the only thing you can do because that's the contract that the trait requires. Everything else just boils down to details and ergonomics.
io::Error::new accepts an io::ErrorKind and something that can be converted into an error::Error.
I'd probably write a function that transforms your domain error into a io::Error by calling io::Error::new and then use this new function in map_err everywhere. I'd start by cramming everything into ErrorKind::Other until I found a reason that a specific Reqwest error should be something else.
Will your consumers really care about something specifically being too many redirects? By construction, the answer must be "no" because they might be operating on a File or a TcpSocket, neither of which have such a concept.
I don't believe I would create a wrapper error type in this case; I can't see how it would provide any value. It would require extra type annotations that you get "for free" with a function.
This is not always trivial.
That is correct — gluing two wildly different pieces together sometimes doesn't line up exactly the way we want. That's part of what makes programming both exciting and terrible.

Related

What is the correct error part of the Result type of a function propagating different error types?

I tried to write a function, that shall propagate different error types. Only for example see the following code:
use std::fs::File;
use std::io;
use std::io::Read;
fn main() {
let number = read_number_from_file().unwrap();
println!("Read number {}!", number);
}
// So what is the correct error part of the Result<i32, ...>?
fn read_number_from_file() -> Result<i32, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
let number = s.parse()?;
Ok(number)
}
Compiling it results in the following error unsurprisingly:
error[E0277]: `?` couldn't convert the error to `std::io::Error`
--> src\main.rs:16:27
|
16 | let number = s.parse()?;
| ^ the trait `std::convert::From<std::num::ParseIntError>` is not implemented for `std::io::Error`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following implementations were found:
<std::io::Error as std::convert::From<std::ffi::NulError>>
<std::io::Error as std::convert::From<std::io::ErrorKind>>
<std::io::Error as std::convert::From<std::io::IntoInnerError<W>>>
= note: required by `std::convert::From::from`
So what is the correct Error type of the Result type? Is there an answer not only for this specific case: I have a function with several calls to other functions returning Result<T, E> which in the error case shall be propagated out of the function to the caller but have different Types E?
You should define your own error type, which includes all possible errors that could happen:
#[derive(Debug)]
enum Error {
Io(io::Error),
ParseInt(num::ParseIntError)
}
impl From<io::Error> for Error {
fn from(other: io::Error) -> Error {
Error::Io(other)
}
}
impl From<num::ParseIntError> for Error {
fn from(other: num::ParseIntError) -> Error {
Error::ParseInt(other)
}
}
The From conversions allow the ? operator to work as expected.
You can save a lot of typing by using one of the several crates that will generate the boilerplate for you. Some popular ones are thiserror and snafu.
I prefer thiserror because it only adds the implementations that you would otherwise write yourself and, if you are writing a library, it is transparent to your library's users. snafu is arguably more powerful - it generates a lot more code, but is opinionated in what it generates, so you will need to get used to its paradigm in order to take full advantage of it, and the snafu concepts will become part of your library's API.
Using thiserror, the code above is reduced to:
use thiserror::Error;
#[derive(Debug, Error)]
enum Error {
#[error("IO Error: {0})]
Io(#[from] io::Error),
#[error("ParseInt Error: {0})]
ParseInt(#[from] num::ParseIntError)
}
Note that this is also generating Display implementations, which I didn't include above.

Rust: Read and map lines from stdin and handling different error types

I'm learning Rust and trying to solve some basic algorithm problems with it. In many cases, I want to read lines from stdin, perform some transformation on each line and return a vector of resulting items. One way I did this was like this:
// Fully working Rust code
let my_values: Vec<u32> = stdin
.lock()
.lines()
.filter_map(Result::ok)
.map(|line| line.parse::<u32>())
.filter_map(Result::ok)
.map(|x|x*2) // For example
.collect();
This works but of course silently ignores any errors that may occur. Now what I woud like to do is something along the lines of:
// Pseudo-ish code
let my_values: Result<Vec<u32>, X> = stdin
.lock()
.lines() // Can cause std::io::Error
.map(|line| line.parse::<u32>()) // Can cause std::num::ParseIntError
.map(|x| x*2)
.collect();
Where X is some kind of error type that I can match on afterwards. Preferably I want to perform the whole operation on one line at a time and immediately discard the string data after it has been parsed to an int.
I think I need to create some kind of Enum type to hold the various possible errors, possibly like this:
#[derive(Debug)]
enum InputError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}
However, I don't quite understand how to put everything together to make it clean and avoid having to explicitly match and cast everywhere. Also, is there some way to automatically create these enum error types or do I have to explicilty enumerate them every time I do this?
You're on the right track.
The way I'd approach this is by using the enum you've defined,
then add implementations of From for the error types you're interested in.
That will allow you to use the ? operator on your maps to get the kind of behaviour you want.
#[derive(Debug)]
enum MyError {
IOError(std::io::Error),
ParseIntError(std::num::ParseIntError),
}
impl From<std::io::Error> for MyError {
fn from(e:std::io::Error) -> MyError {
return MyError::IOError(e)
}
}
impl From<std::num::ParseIntError> for MyError {
fn from(e:std::num::ParseIntError) -> MyError {
return MyError::ParseIntError(e)
}
}
Then you can implement the actual transform as either
let my_values: Vec<_> = stdin
.lock()
.lines()
.map(|line| -> Result<u32,MyError> { Ok(line?.parse::<u32>()?*2) } )
.collect();
which will give you one entry for each input, like: {Ok(x), Err(MyError(x)), Ok(x)}.
or you can do:
let my_values: Result<Vec<_>,MyError> = stdin
.lock()
.lines()
.map(|line| -> Result<u32,MyError> { Ok(line?.parse::<u32>()?*2) } )
.collect();
Which will give you either Err(MyError(...)) or Ok([1,2,3])
Note that you can further reduce some of the error boilerplate by using an error handling crate like snafu, but in this case it's not too much.

Is there a way to get SNAFU's `.backtrace()` on arbitrary `&dyn std::error::Error` trait objects?

RFC 2504 will add a required fn backtrace(&self) -> Option<&Backtrace> to all std::error::Error. This is not ready yet, so for now, SNAFU, an error helper macro, polyfills this by tying an ErrorCompat trait to all types generated by the macro. This allows for backtrace support before it lands in Rust nightly.
However, this ErrorCompat trait is not implemented for all implementors of std::error::Error. I want to — in some generic error printing code — be able to display the chain of causes along with the stacktrace associated with where the SNAFU error was instantiated. Unfortunately, the source() function returns &(dyn Error + 'static).
use std::error::Error as StdError;
use snafu::{ResultExt, ErrorCompat};
fn main() {
let err: Result<(), _> = Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"));
let err = err.with_context(|| parse_error::ReadInput {
filename: "hello"
});
let err = err.with_context(|| compile_error::ParseStage);
// some generic error handling code
if let Err(err) = err {
// `cause` is type &(dyn std::error::Error + 'static)
let cause = err.source().unwrap();
if let Some(err) = /* attempt to downcast cause into &dyn snafu::ErrorCompat trait object */ {
println!("{}", err.backtrace().unwrap());
}
}
}
pub mod compile_error {
use snafu::{Snafu, Backtrace};
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(super)))]
pub enum Error {
#[snafu(display("Error parsing code: {}", source))]
ParseStage {
source: crate::parse_error::Error,
backtrace: Backtrace
},
}
}
pub mod parse_error {
use snafu::{Snafu, Backtrace};
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(super)))]
pub enum Error {
#[snafu(display("Could not read input {:?}: {}", filename, source))]
ReadInput {
filename: std::path::PathBuf,
source: std::io::Error,
backtrace: Backtrace
},
}
}
I've looked at std::any::Any::downcast_ref but this is for downcasting to a struct, not downcasting a trait object to another trait object. I'd like to avoid having to list out all possible concrete-typed SNAFU errors in my error-handling code.
I could cryo-freeze myself until RFC 2504 is (fully) implemented but surely there's some way to do this.
A dyn Error has the methods of Error and nothing else. If the backtrace cannot be deduced from those methods then where else could that information come from?
Unfortunately RFC 2504 is not yet stabilised, so you will need to be cryogenically frozen until at least Rust 1.39 if you want to wait for it.
It seems I missed this because nightly std docs weren't recompiled, but #![feature(backtrace)] is in nightly right now. SNAFU still needs to add support for it, so I'm still stuck on getting this all working.

How to match custom Fails with the failure crate

I'm trying to understand how to use the failure crate. It works splendidly as a unification of different types of standard errors, but when creating custom errors (Fails), I do not understand how to match for custom errors. For example:
use failure::{Fail, Error};
#[derive(Debug, Fail)]
pub enum Badness {
#[fail(display = "Ze badness")]
Level(String)
}
pub fn do_badly() -> Result<(), Error> {
Err(Badness::Level("much".to_owned()).into())
}
#[test]
pub fn get_badness() {
match do_badly() {
Err(Badness::Level(level)) => panic!("{:?} badness!", level),
_ => (),
};
}
fails with
error[E0308]: mismatched types
--> barsa-nagios-forwarder/src/main.rs:74:9
|
73 | match do_badly() {
| ---------- this match expression has type `failure::Error`
74 | Err(Badness::Level(level)) => panic!("{:?} badness!", level),
| ^^^^^^^^^^^^^^^^^^^^^ expected struct `failure::Error`, found enum `Badness`
|
= note: expected type `failure::Error`
found type `Badness`
How can I formulate a pattern which matches a specific custom error?
You need to downcast the Error
When you create a failure::Error from some type that implements the Fail trait (via from or into, as you do), you temporarily hide the information about the type you're wrapping from the compiler. It doesn't know that Error is a Badness - because it can also be any other Fail type, that's the point. You need to remind the compiler of this, the action is called downcasting. The failure::Error has three methods for this: downcast, downcast_ref and downcast_mut. After you've downcast it, you can pattern match on the result as normal - but you need to take into account the possibility that downcasting itself may fail (if you try to downcast to a wrong type).
Here's how it'd look with downcast:
pub fn get_badness() {
if let Err(wrapped_error) = do_badly() {
if let Ok(bad) = wrapped_error.downcast::<Badness>() {
panic!("{:?} badness!", bad);
}
}
}
(two if lets can be combined in this case).
This quickly gets very unpleasant if more than one error type needs to be tested, since downcast consumes the failure::Error it was called on (so you can't try another downcast on the same variable if the first one fails). I sadly couldn't figure out an elegant way to do this. Here's a variant one shouldn't really use (panic! in map is questionable, and doing anything else there would be plenty awkward, and I don't even want to think about more cases than two):
#[derive(Debug, Fail)]
pub enum JustSoSo {
#[fail(display = "meh")]
Average,
}
pub fn get_badness() {
if let Err(wrapped_error) = do_badly() {
let e = wrapped_error.downcast::<Badness>()
.map(|bad| panic!("{:?} badness!", bad))
.or_else(|original| original.downcast::<JustSoSo>());
if let Ok(so) = e {
println!("{}", so);
}
}
}
or_else chain should work OK if you actually want to produce some value of the same type from all of the possible\relevant errors. Consider also using non-consuming methods if a reference to the original error is fine for you, as this would allow you to just make a series of if let blocks , one for each downcast attempt.
An alternative
Don't put your errors into failure::Error, put them in a custom enum as variants. It's more boilerplate, but you get painless pattern matching, which the compiler also will be able to check for sanity. If you choose to do this, I'd recommend derive_more crate which is capable of deriving From for such enums; snafu looks very interesting as well, but I have yet to try it. In its most basic form this approach looks like this:
pub enum SomeError {
Bad(Badness),
NotTooBad(JustSoSo),
}
pub fn do_badly_alt() -> Result<(), SomeError> {
Err(SomeError::Bad(Badness::Level("much".to_owned())))
}
pub fn get_badness_alt() {
if let Err(wrapper) = do_badly_alt() {
match wrapper {
SomeError::Bad(bad) => panic!("{:?} badness!", bad),
SomeError::NotTooBad(so) => println!("{}", so),
}
}
}

Are there any conventions for aggregating multiple errors as the causes of another error?

I'm writing a function that iterates over a vector of Result and returns success if they all were successful, or an error if any failed. Limitations in error::Error are frustrating me and I'm not sure how to work around them. Currently I have something like:
let mut errors = Vec::new();
for result in results {
match result {
Err(err) => errors.push(err),
Ok(success) => { ... }
}
}
if errors.is_empty() {
return Ok(())
else {
return Err(MyErrorType(errors))
}
The problem with my current approach is that I can only set one error to be the cause of MyErrorType, and my error's description needs to be a static String so I can't include the descriptions of each of the triggering failures. All of the failures are potentially relevant to the caller.
There is no convention that I know of, and indeed I have never had the issue of attempting to report multiple errors at once...
... that being said, there are two points that may help you:
There is no limitation that the description be a 'static String, you are likely confusing &'static str and &str. In fn description(&self) -> &str, the lifetime of str is linked to the lifetime of self (lifetime elision) and therefore an embedded String satisfies the constraints
Error is an interface to deal with errors uniformly. In this case, indeed, only a single cause was foreseen, however it does not preclude a more specific type to aggregate multiple causes and since Error allows downcasting (Error::is, Error::downcast, ...) the more specific type can be retrieved by the handler and queried in full
As such, I would suggest that you create a new concrete type solely dedicated to holding multiple errors (in a Vec<Box<Error>>), and implementing the Error interface. It's up to you to decide on the description and cause it will expose.
A single type will let your clients test more easily for downcasting than having an unknown (and potentially growing as time goes) number of potential downcast targets.
expanding a bit on point 1 of Matthieu's good answer.
The point where you're likely running into trouble (I know I did when I tried to implement Error) is that you want to have a dynamic description().
// my own error type
#[derive(Debug)] struct MyError { value: u8 }
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "bummer! Got a {}", self.value)
}
}
// I am now tempted to add the problematic value dynamically
// into the description, but I run into trouble with lifetimes
// this DOES NOT COMPILE because the String I'm building
// goes out of scope and I can't return a reference to it
impl error::Error for MyError {
fn description(&self) -> &str {
&format!("can't put a {} here!", self.value)
}
}
solution 1
Don't dynamically build description(). Just use a static str. This is what most implementations of Error on github seem to do.
If you need to retrieve and display (or log) the value you can always access it from your MyError type. Plus Display (that you must implement for all Error impls) does allow you to create dynamic strings.
I created a contrived example on the playground that shows how to track multiple errors.
solution 2
(what Matthieu is suggesting) you can store the error message in the error itself.
#[derive(Debug)] struct MyError { value: u8, msg: String }
impl MyError {
fn new(value: u8) -> MyError {
MyError { value: value, msg: format!("I don't like value {}", value) }
}
}
// now this works because the returned &str has the same lifetime
// as self
impl error::Error for MyError {
fn description(&self) -> &str {
&self.msg
}
}