Error type in keepawake-rs

I'm trying to figure how what's the best error type to use for my crate keepawake-rs for use as a library. It currently uses anyhow, but as far as I understand that's not considered the best practice for libraries.

I tried a switch to a thiserror based type here: Try switching to thiserror for the library part by segevfiner · Pull Request #22 · segevfiner/keepawake-rs · GitHub, but the system level error remains as a Box<dyn Error + Send + Sync>, as I'm not sure what's the right way to handle that when the type of errors varies depending on the OS, and a good error type for such an OS might not even exist, or there might be multiple possible error types that can be returned. I also still, end up using anyhow for the binary part so that I get a decent print from returning an Err from main, as that is one of the crates/error types that handle that.

Any recommendations or should I just stay with anyhow?

In your shoes, I've had my error type contain multiple options, with #[cfg] used to remove the ones that aren't possible on this platform:

#[derive(Debug, thiserror::Error)]
pub enum Errors {
    #[error("I've done a very bad thing")]
    Badness,
    #[cfg(target_os = "linux")]
    #[error(transparent)]
    PlatformError(#[from] zbus::Error),
    #[cfg(windows)]
    #[error(transparent)]
    PlatformError(#[from] windows::core::WindowsError),
}

fn main() -> Result<(), Errors> {
    #[cfg(target_os = "linux")]
    Err(zbus::Error::InterfaceNotFound)?;

    Ok(())
}

This will succeed on anything other than Linux, and fails on Linux with a suitable error.

I wonder if this variant is better than Try switching to thiserror for the library part (Variant #2) by segevfiner · Pull Request #25 · segevfiner/keepawake-rs · GitHub

Your version also works well, and in the context of what you're doing makes more sense; you've pushed the decision about which error type to use for the platform into a platform-specific module, and you're not compiling two different platform-specific modules on the same platform.

And the advantage here is that I can extract the underlying error cause from your error if I really want to; the only thing I'd want to check is whether it's clear in rustdoc output (cargo doc --open locally) that the type of syserr in the Error::System(syserr) variant is a specific underlying type, in case someone ever wants to match on the error type and react to certain causes of error but not others.

As an aside, I find it useful to copy anyhow's trick for a custom Result type; somewhere in your library, you have:

pub type Result<T, E = MyError> = core::result::Result<T, E>;

This then lets you have functions returning Result<()> and similar, just like with anyhow::Result, but using your own error type instead. Downside is that it's a little harder to find the error type, but this is useful if you don't expect the caller to want to distinguish the different error possibilities most of the time (there's two more clicks in documentation to get to the underlying MyError documentation).

It seems it's not that obvious by default:

But I can add an extra public SystemError alias to make that better:


But as you can see the SystemError underlying type is still the actual type which will vary depending on the platform you build the docs on.

Having SystemError's underlying type be platform specific is the point, though - if I know that I'm working on Windows, I could want to extract the underlying Windows-specific error from your Error type (via `System(SystemError)).

This gets complicated to do well; when I do cargo doc --open locally (or cargo doc --target x86_64-pc-windows-gnu --open, or whatever target I'm building for), it'll get it right, But you might need to use docs.rs metadata to get the docs.rs version of your documentation looking good.