Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new unstable API downcast to std::io::Error #98387

Merged
Merged
62 changes: 62 additions & 0 deletions library/std/src/io/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,68 @@ impl Error {
}
}

/// Attempt to downgrade the inner error to `E` if any.
///
/// If this [`Error`] was constructed via [`new`] then this function will
/// attempt to perform downgrade on it, otherwise it will return [`Err`].
///
/// If downgrade succeeds, it will return [`Ok`], otherwise it will also
/// return [`Err`].
///
/// [`new`]: Error::new
///
/// # Examples
///
/// ```
/// #![feature(io_error_downcast)]
///
/// use std::fmt;
/// use std::io;
/// use std::error::Error;
///
/// #[derive(Debug)]
/// enum E {
/// Io(io::Error),
/// SomeOtherVariant,
/// }
///
/// impl fmt::Display for E {
/// // ...
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// # todo!()
/// # }
/// }
/// impl Error for E {}
///
/// impl From<io::Error> for E {
/// fn from(err: io::Error) -> E {
/// err.downcast::<E>()
/// .map(|b| *b)
/// .unwrap_or_else(E::Io)
/// }
/// }
/// ```
#[unstable(feature = "io_error_downcast", issue = "none")]
yaahc marked this conversation as resolved.
Show resolved Hide resolved
pub fn downcast<E>(self) -> result::Result<Box<E>, Self>
where
E: error::Error + Send + Sync + 'static,
{
match self.repr.into_data() {
ErrorData::Custom(b) if b.error.is::<E>() => {
let res = (*b).error.downcast::<E>();

// downcast is a really trivial and is marked as inline, so
// it's likely be inlined here.
//
// And the compiler should be able to eliminate the branch
// that produces `Err` here since b.error.is::<E>()
// returns true.
Ok(res.unwrap())
}
repr_data => Err(Self { repr: Repr::new(repr_data) }),
}
}

/// Returns the corresponding [`ErrorKind`] for this error.
///
/// # Examples
Expand Down
9 changes: 9 additions & 0 deletions library/std/src/io/error/repr_bitpacked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ unsafe impl Send for Repr {}
unsafe impl Sync for Repr {}

impl Repr {
pub(super) fn new(dat: ErrorData<Box<Custom>>) -> Self {
match dat {
ErrorData::Os(code) => Self::new_os(code),
ErrorData::Simple(kind) => Self::new_simple(kind),
ErrorData::SimpleMessage(simple_message) => Self::new_simple_message(simple_message),
ErrorData::Custom(b) => Self::new_custom(b),
}
}

pub(super) fn new_custom(b: Box<Custom>) -> Self {
let p = Box::into_raw(b).cast::<u8>();
// Should only be possible if an allocator handed out a pointer with
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/io/error/repr_unpacked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ type Inner = ErrorData<Box<Custom>>;
pub(super) struct Repr(Inner);

impl Repr {
#[inline]
pub(super) fn new(dat: ErrorData<Box<Custom>>) -> Self {
Self(dat)
}
pub(super) fn new_custom(b: Box<Custom>) -> Self {
Self(Inner::Custom(b))
}
Expand Down
53 changes: 52 additions & 1 deletion library/std/src/io/error/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{const_io_error, Custom, Error, ErrorData, ErrorKind, Repr};
use super::{const_io_error, Custom, Error, ErrorData, ErrorKind, Repr, SimpleMessage};
use crate::assert_matches::assert_matches;
use crate::error;
use crate::fmt;
Expand Down Expand Up @@ -141,3 +141,54 @@ fn test_custom_error_packing() {
}) if error.downcast_ref::<Bojji>().as_deref() == Some(&Bojji(true)),
);
}

#[derive(Debug)]
struct E;

impl fmt::Display for E {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}

impl error::Error for E {}

#[test]
fn test_std_io_error_downcast() {
// Case 1: custom error, downcast succeeds
let io_error = Error::new(ErrorKind::Other, Bojji(true));
let e: Box<Bojji> = io_error.downcast().unwrap();
assert!(e.0);

// Case 2: custom error, downcast fails
let io_error = Error::new(ErrorKind::Other, Bojji(true));
let io_error = io_error.downcast::<E>().unwrap_err();

// ensures that the custom error is intact
assert_eq!(ErrorKind::Other, io_error.kind());
let e: Box<Bojji> = io_error.downcast().unwrap();
assert!(e.0);

// Case 3: os error
let errno = 20;
let io_error = Error::from_raw_os_error(errno);
let io_error = io_error.downcast::<E>().unwrap_err();

assert_eq!(errno, io_error.raw_os_error().unwrap());

// Case 4: simple
let kind = ErrorKind::OutOfMemory;
let io_error: Error = kind.into();
let io_error = io_error.downcast::<E>().unwrap_err();

assert_eq!(kind, io_error.kind());

// Case 5: simple message
const SIMPLE_MESSAGE: SimpleMessage =
SimpleMessage { kind: ErrorKind::Other, message: "simple message error test" };
let io_error = Error::from_static_message(&SIMPLE_MESSAGE);
let io_error = io_error.downcast::<E>().unwrap_err();

assert_eq!(SIMPLE_MESSAGE.kind, io_error.kind());
assert_eq!(SIMPLE_MESSAGE.message, &*format!("{io_error}"));
}