snafu
snafu copied to clipboard
Document how to use errors that implement failure's `Fail` as an underlying source
I just started using snafu
and up to now it is much more egonoic for me than failure
. Therefore, thanks for this crate!
However, working with snafu I stumbled on a problem today. I just can't figure out how to handle Fail
s that I get from an external crate:
use snafu::{Snafu, ResultExt};
use rumqtt::{MqttClient, MqttOptions};
use std::fmt;
#[derive(Debug, Snafu)]
enum Error {
/// source requires to impl `Error` which the underlying `Fail` typically not does.
/// Therefore, I use my own Error type that holds a `String`.
#[snafu(source(from(rumqtt::ConnectError, TextError::from)))]
#[snafu(display("Failed to connect to MQTT broker: {}", source))]
Connection { source: TextError },
}
#[derive(Debug)]
struct TextError(String);
impl std::error::Error for TextError {}
impl fmt::Display for TextError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl TextError {
fn from<T: fmt::Display>(d: T) -> Self {
Self(d.to_string())
}
}
/// However this does not work
fn main() {
let ops = MqttOptions::new("client", "localhost", 1883);
let (mut client, notifier) = MqttClient::start(ops).context(Connection).unwrap();
}
The compiler states that Connection
requires a TextError
instead of an rumqtt::ConnectError
. Probably, I did something wrong or the #[snafu(source(from(...)))
does not what I think it does.
Any advice how to handle this (without .map_err()
)?
PS: I give a talk about error handling in Rust at my local Rust Meetup group this evening where I want to present snafu
, so a quick answer would be much appreciated.
The issue here is that #[snafu(source(from(...)))]
is in the wrong location; it needs to be on the source
field:
Incorrect
#[derive(Debug, Snafu)]
enum Error {
#[snafu(source(from(rumqtt::ConnectError, TextError::from)))]
#[snafu(display("Failed to connect to MQTT broker: {}", source))]
Connection { source: TextError },
}
Correct
#[derive(Debug, Snafu)]
enum Error {
#[snafu(display("Failed to connect to MQTT broker: {}", source))]
Connection {
#[snafu(source(from(rumqtt::ConnectError, TextError::from)))]
source: TextError,
},
}
The good news is that version 0.5 of SNAFU will have much better error reporting about misused attributes (#105), so this would have been more obvious.
That being said, I'd suggest using failure's Compat
struct instead of creating your own:
use snafu::{Snafu, ResultExt};
use rumqtt::{MqttClient, MqttOptions};
#[derive(Debug, Snafu)]
enum Error {
#[snafu(display("Failed to connect to MQTT broker: {}", source))]
Connection {
#[snafu(source(from(rumqtt::ConnectError, failure::Fail::compat)))]
source: failure::Compat<rumqtt::ConnectError>
},
}
fn main() {
let ops = MqttOptions::new("client", "localhost", 1883);
let (mut client, notifier) = MqttClient::start(ops).context(Connection).unwrap();
}
Opinion wise, this is an aspect of failure that I dislike — the fact that an error is implemented with failure "leaks out" in the public API of the crate, making interoperation that much harder.
Thank you, I was pretty sure that I simply placed the attribute false. Now it works 👍