I'm trying to create some structured error types for Calloop. It's an event loop, so it not only has to handle internal errors, but user provided callbacks and user implementations of its traits. There's a lot of overlap with IO errors, so here's where I'm starting from:
#[derive(thiserror::Error, Debug)]
enum Error {
#[error("invalid token provided to internal function")]
InvalidToken,
#[error("underlying IO error")]
IoError(#[from] std::io::Error),
#[error("error generated by event source or callback")]
CallbackError(#[from] Box<dyn std::error::Error + Sync + Send>),
}
type Result<T> = core::result::Result<T, Error>;
I don't particularly care about using thiserror
, it's helpful but it's not mandatory.
Here's the sort of usage I'm aiming for (note the Result
type is an alias, not core::Result
):
fn test_function_1() -> Result<String> {
let v = &[0xDD1E, 0x006d];
Ok(String::from_utf16(v)?)
}
fn test_function_2() -> Result<()> {
Err(anyhow::anyhow!("oh no"))?;
Ok(())
}
These are just examples. They could be any kind of errors. Unfortunately this fails to compile:
Compiling playground v0.0.1 (/playground)
error[E0277]: `?` couldn't convert the error to `Error`
--> src/lib.rs:17:29
|
15 | fn test_function_1() -> Result<String> {
| -------------- expected `Error` because of this
16 | let v = &[0xDD1E, 0x006d];
17 | Ok(String::from_utf16(v)?)
| ^ the trait `From<FromUtf16Error>` is not implemented for `Error`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following implementations were found:
<Error as From<Box<(dyn std::error::Error + Send + Sync + 'static)>>>
<Error as From<std::io::Error>>
= note: required because of the requirements on the impl of `FromResidual<std::result::Result<Infallible, FromUtf16Error>>` for `std::result::Result<String, Error>`
= note: required by `from_residual`
error[E0277]: `?` couldn't convert the error to `Error`
--> src/lib.rs:21:34
|
20 | fn test_function_2() -> Result<()> {
| ---------- expected `Error` because of this
21 | Err(anyhow::anyhow!("oh no"))?;
| ^ the trait `From<anyhow::Error>` is not implemented for `Error`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following implementations were found:
<Error as From<Box<(dyn std::error::Error + Send + Sync + 'static)>>>
<Error as From<std::io::Error>>
= note: required because of the requirements on the impl of `FromResidual<std::result::Result<Infallible, anyhow::Error>>` for `std::result::Result<(), Error>`
= note: required by `from_residual`
However, it does compile if I add map_err()
eg.
Ok(String::from_utf16(v).map_err(|e| Error::CallbackError(e.into()))?)
But this is exactly the kind of thing I'm trying to make more ergonomic by implementing a proper error type! The really annoying thing is, it's all identical boilerplate, which seems like it should be covered by some kind of conversion. The exact same snippet, map_err(|e| Error::CallbackError(e.into()))
, works on FromUtf16Error
, anyhow::Error
, etc. etc. All it does is call into()
!
I had thought that I could implement a trait to do this for me eg.
impl<E: std::error::Error> From<E> for Error {
fn from(error: E) -> Self {
Self::CallbackError(error.into())
}
}
Nope!
Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait `std::convert::From<Error>` for type `Error`
--> src/lib.rs:13:1
|
13 | impl<E: std::error::Error> From<E> for Error {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `core`:
- impl<T> From<T> for T;
This is extremely baffling to me — I can't implement a conversion from the trait my error type implements to my own error type... because there's already a conversion defined in core
. But the conversion in core
doesn't actually handle the conversion that I'm trying to define!
At this point, I'm 100% stumped. Is there any way to have a custom error type that works automatically with ?
without needing map_err()
s everywhere that do nothing but call into()
? Is there any way to implement From
for std::error::Error
for something that, itself, implements std::error::Error
?