I am trying to create a library that helps with creating IPC Servers and Clients using unix domain sockets.
Obviously there are going to be a lot of IO errors that the application that uses the library is going to be interested in.
I am wondering if it's good practice to treat some very common IO errors separately eg: Connection Refused and WouldBlock so that users can easily check those as opposed to examining io::ErrorKind
Something like this?
use thiserror::Error;
#[derive(Clone, Debug, Error)]
pub enum Error {
#[error("Connection refused by server")]
ConnectionRefused,
#[error("Operation would block")]
WouldBlock,
#[error(transparent)]
IOError(#[from] std::io::Error),
}
I wouldn't bother. How do you decide what's "very common"? Also, if you start doing this, you will start digging down into arbitrary depths of error chains. Your code shouldn't be responsible for that, it's an unnecessary level of complexity.
It also makes the construction of your own error type cumbersome (you always need to check the I/O error, you can't just use ? to perform the automatic From::from() conversion).
The current design also throws away the io::Error instance itself when it's ConnectionRefused or WouldBlock, which is definitely not something you should do.
Just make IoError a single variant with #[from], and call it a day.
Although about "very common" is easy: WouldBlock for example is very common since I am going to be constructing my library primarily around NonBlocking mode. So the application caller is always supposed to check if an operation WouldBlock and then try again.
I would go a different route; define methods on Error that tell you what your behaviour should be. Something like:
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("This is not data, it's an ex-parrot!")]
PiningForTheFjords,
#[error("Server said to try later")]
TryLater,
#[error(transparent)]
IOError(#[from] std::io::Error),
}
impl Error {
pub fn should_retry(&self) -> bool {
use Error::*;
match self {
PiningForTheFjords => false,
TryLater => true,
IOError(e) => {
use std::io::ErrorKind::*;
match e.kind() {
WouldBlock => true,
TimedOut => true,
Interrupted => true,
_ => false,
}
}
}
}
}
You'll want something more sophisticated than a bool return type (e.g. something telling you what to wait for before retrying), but this means that complicated code can use match the way should_retry does, to directly understand all the possible error causes, while simple code can use should_retry to tell it what its behaviour "should" be on this error.