Skip to content

Commit

Permalink
Rollup merge of rust-lang#98387 - NobodyXu:feature/std_io_Error_try_d…
Browse files Browse the repository at this point in the history
…owngrade_inner, r=yaahc

Add new unstable API `downcast` to `std::io::Error`

rust-lang/libs-team#57

Signed-off-by: Jiahao XU <[email protected]>
  • Loading branch information
JohnTitor authored Jul 16, 2022
2 parents e6c43cf + 8e8a3be commit bf9ed99
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 1 deletion.
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 = "99262")]
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}"));
}

0 comments on commit bf9ed99

Please sign in to comment.