In case of fallible conversions, the original value is often returned as part of the error, e.g. the implementation of TryFrom<Vec<T, A>> for [T; N]
has an error type which is Vec<T, A>
. Another example is FromUtf8Error
, which allows getting "back the byte vector that was used in the conversion attempt". See FromUtf8Error::into_bytes
.
This helps to not lose the original value.
I did something similar in sandkiste::errors::DatumConversionError
:
pub struct DatumConversionError<D> {
pub original: D,
pub details: &'static str,
}
However, this results in DatumConversionError<D>
to be able to be !Send
and !Sync
if the type D
is !Send
or !Sync
respectively.
Consequently, I cannot convert this to an anyhow::Error
, because that requires the error to be Send + Sync
.
To demonstrate the problem, I made a small Playground example:
use std::fmt;
use std::marker::PhantomData;
#[derive(Debug)]
pub struct Datum {
// this is `!Send` and `!Sync`:
_phantom: PhantomData<*mut ()>,
}
#[derive(Debug)]
pub struct DatumConversionError {
// we return the original `Datum`:
pub original: Datum,
pub details: &'static str,
}
impl fmt::Display for DatumConversionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.details)
}
}
impl std::error::Error for DatumConversionError {}
impl TryFrom<Datum> for String {
type Error = DatumConversionError;
fn try_from(value: Datum) -> Result<String, DatumConversionError> {
Err(DatumConversionError {
original: value,
details: "not implemented",
})
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let datum = Datum {
_phantom: PhantomData,
};
let _s: String = datum.try_into()?;
Ok(())
}
Errors:
Compiling playground v0.0.1 (/playground)
error[E0277]: `*mut ()` cannot be sent between threads safely
--> src/main.rs:40:38
|
40 | let _s: String = datum.try_into()?;
| ^ `*mut ()` cannot be sent between threads safely
|
= help: within `DatumConversionError`, the trait `Send` is not implemented for `*mut ()`
= help: the following other types implement trait `FromResidual<R>`:
<Result<T, F> as FromResidual<Result<Infallible, E>>>
<Result<T, F> as FromResidual<Yeet<E>>>
= note: required because it appears within the type `PhantomData<*mut ()>`
note: required because it appears within the type `Datum`
--> src/main.rs:5:12
|
5 | pub struct Datum {
| ^^^^^
note: required because it appears within the type `DatumConversionError`
--> src/main.rs:11:12
|
11 | pub struct DatumConversionError {
| ^^^^^^^^^^^^^^^^^^^^
= note: required for `anyhow::Error` to implement `From<DatumConversionError>`
= note: required for `Result<(), anyhow::Error>` to implement `FromResidual<Result<Infallible, DatumConversionError>>`
error[E0277]: `*mut ()` cannot be shared between threads safely
--> src/main.rs:40:38
|
40 | let _s: String = datum.try_into()?;
| ^ `*mut ()` cannot be shared between threads safely
|
= help: within `DatumConversionError`, the trait `Sync` is not implemented for `*mut ()`
= help: the following other types implement trait `FromResidual<R>`:
<Result<T, F> as FromResidual<Result<Infallible, E>>>
<Result<T, F> as FromResidual<Yeet<E>>>
= note: required because it appears within the type `PhantomData<*mut ()>`
note: required because it appears within the type `Datum`
--> src/main.rs:5:12
|
5 | pub struct Datum {
| ^^^^^
note: required because it appears within the type `DatumConversionError`
--> src/main.rs:11:12
|
11 | pub struct DatumConversionError {
| ^^^^^^^^^^^^^^^^^^^^
= note: required for `anyhow::Error` to implement `From<DatumConversionError>`
= note: required for `Result<(), anyhow::Error>` to implement `FromResidual<Result<Infallible, DatumConversionError>>`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to 2 previous errors
I wonder what's the best way to solve this issue? I feel like it's idiomatic to return the original value as part of the error. But I also feel like it's idiomatic to ensure that errors are Send + Sync
.
I guess I could require the caller to convert the error using .map_err(SomeOtherError::from)
before invoking the ?
operator. But this kinda ruins the anyhow
fluff of being able to simply not care about error handling that much.