Add new API try_downcast_inner
to std::io::Error
· Issue #57 · rust-lang/libs-team (original) (raw)
Proposal
Add new API try_downcast_inner
to std::io::Error
:
impl std::io::Error { fn try_downcast_inner<E: Error + Send + Sync + 'static>(self) -> Result<Box, Self>; }
Problem statement
Existing APIs requires two separate calls to obtain the raw os error or the inner error and they both return Option
.
There's no way to avoid unwrap
/expect
if we want to cover every possible corner case here, e.g., both of them somehow returns None
.
This is impossible without expect
/unwrap
because std::io::Error::into_inner
takes the error by value and returns Option<Box<dyn Error + Send + Sync>>
instead of Result<Box<dyn Error + Send + Sync>, Self>
.
Currently, we would have to do this to downcast the inner error:
(Adapted from cargo-bins/cargo-binstall#180)
#[derive(Debug)] #[derive(thiserror::Error)] enum E { #[error(transparent)] Io(std::io::Error),
#[error("...")]
SomeOtherVariant,
}
impl Fromstd::io::Error for E { fn from(err: std::io::Error) -> E { if err.get_ref().is_some() { let kind = err.kind();
let inner = err
.into_inner()
.expect("err.get_ref() returns Some, so err.into_inner() should also return Some");
inner
.downcast()
.map(|b| *b)
.unwrap_or_else(|err| E::Io(std::io::Error::new(kind, err)))
} else {
E::Io(err)
}
}
}
This has only 1 expect
/unwrap
, but it uses std::io::Error::new
, which uses Box::new
internally.
Another way to implement it would be:
impl Fromstd::io::Error for E { fn from(err: std::io::Error) -> E { let is_E = err .get_ref() .map(|e| e.is::()) .unwrap_or_default();
if is_E {
let inner = err
.into_inner()
.expect("err.get_ref() returns Some, so err.into_inner() should also return Some");
inner
.downcast()
.map(|b| *b)
.expect("e.is::<E>() returns true, so this must succeed")
} else {
E::Io(err)
}
}
}
This saves the call to std::io::Error::new
, but adds another unwrap
/expect
.
Thus, I propose to add another API to simplify the downcasting while avoiding unwrap
/expect
, which is std::io::Error::try_downcast_inner
.
It returns Result<Box<E>, Self>
, and when the downcast fails, it simply return the original std::io::Error
.
Motivation, use-cases
This makes downcasting much easier:
#[derive(Debug)] #[derive(thiserror::Error)] enum E { #[error(transparent)] Io(std::io::Error),
#[error("...")]
SomeOtherVariant,
}
impl Fromstd::io::Error for E { fn from(err: std::io::Error) -> E { err.try_downcast_inner::() .map(|b| *b) .unwrap_or_else(E::Io) } }
Solution sketches
The first solution I came to mind with is:
impl std::io::Error { fn into_underlying_err(self) -> Cause; fn from_underlying_err(cause: Cause) -> Self; }
#[non_exhaustive] enum Cause { RawOsError(i32), InnerError(Box<dyn Error + Send + Sync>), }
Then I realized that this is too complex and exposes too much details of std::io::Error
.
The second solution:
impl std::io::Error { fn into_inner2(self) -> Result<Box<dyn Error + Send + Sync>, Self> }
The problem with this solution is that it creates confusion by introducing a new function with name very similar to existing one.