Skip to content

Commit

Permalink
std::error::Error: change the error iterator producer
Browse files Browse the repository at this point in the history
To produce an error iterator `std::error::Chain` one had to call
`<dyn Error>::chain()`, which was not very ergonomic, because you have to
manually cast to a trait object, if you didn't already have one with
the type erased.

```
    let mut iter = (&my_error as &(dyn Error)).chain();
    // or
    let mut iter = <dyn Error>::chain(&my_error);
    // or
    let mut iter = Error::chain(&my_error);
```

The `chain()` method can't be implemented on the Error trait, because of
rust-lang#69161

`Chain::new()` replaces `<dyn Error>::chain()` as a good alternative
without confusing users, why they can't use `my_error.chain()` directly.

The `Error::sources()` method doesn't have this problem, so implement
it more efficiently, than one could achieve this with `Chain::new().skip(1)`.

Related: rust-lang#58520
  • Loading branch information
haraldh committed Feb 3, 2021
1 parent b593389 commit e2df4da
Showing 1 changed file with 67 additions and 37 deletions.
104 changes: 67 additions & 37 deletions library/std/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,49 @@ pub trait Error: Debug + Display {
None
}

/// Returns an iterator starting with the `source()` of this error
/// and continuing with recursively calling `source()`
///
/// If you want to include the current error, use [`Chain::new`].
///
/// # Examples
///
/// ```
/// #![feature(error_iter)]
/// use std::error::Error;
/// use std::fmt;
///
/// #[derive(Debug)]
/// struct SourceError(&'static str, Option<Box<dyn Error + 'static>>);
///
/// impl fmt::Display for SourceError {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// write!(f, "{}", self.0)
/// }
/// }
///
/// impl Error for SourceError {
/// fn source(&self) -> Option<&(dyn Error + 'static)> {
/// self.1.as_ref().map(|e| e.as_ref())
/// }
/// }
///
/// let a = SourceError("A", None);
/// let b = SourceError("B", Some(Box::new(a)));
/// let c = SourceError("C", Some(Box::new(b)));
///
/// let mut iter = c.sources();
///
/// assert_eq!("B".to_string(), iter.next().unwrap().to_string());
/// assert_eq!("A".to_string(), iter.next().unwrap().to_string());
/// assert!(iter.next().is_none());
/// ```
#[unstable(feature = "error_iter", issue = "58520")]
#[inline]
fn sources(&self) -> Chain<'_> {
Chain { current: self.source() }
}

/// Gets the `TypeId` of `self`.
#[doc(hidden)]
#[unstable(
Expand Down Expand Up @@ -651,75 +694,62 @@ impl dyn Error {
Err(self)
}
}
}

/// An iterator over an [`Error`] and its sources.
#[unstable(feature = "error_iter", issue = "58520")]
#[derive(Clone, Debug)]
pub struct Chain<'a> {
current: Option<&'a (dyn Error + 'static)>,
}

impl<'a> Chain<'a> {
/// Returns an iterator starting with the current error and continuing with
/// recursively calling [`Error::source`].
///
/// If you want to omit the current error and only use its sources,
/// use `skip(1)`.
/// use [`Error::sources`].
///
/// # Examples
///
/// ```
/// #![feature(error_iter)]
/// use std::error::Error;
/// use std::error::{Error, Chain};
/// use std::fmt;
///
/// #[derive(Debug)]
/// struct A;
///
/// #[derive(Debug)]
/// struct B(Option<Box<dyn Error + 'static>>);
/// struct SourceError(&'static str, Option<Box<dyn Error + 'static>>);
///
/// impl fmt::Display for A {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "A")
/// impl fmt::Display for SourceError {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// write!(f, "{}", self.0)
/// }
/// }
///
/// impl fmt::Display for B {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "B")
/// }
/// }
///
/// impl Error for A {}
///
/// impl Error for B {
/// impl Error for SourceError {
/// fn source(&self) -> Option<&(dyn Error + 'static)> {
/// self.0.as_ref().map(|e| e.as_ref())
/// self.1.as_ref().map(|e| e.as_ref())
/// }
/// }
///
/// let b = B(Some(Box::new(A)));
///
/// // let err : Box<Error> = b.into(); // or
/// let err = &b as &(dyn Error);
/// let a = SourceError("A", None);
/// let b = SourceError("B", Some(Box::new(a)));
/// let c = SourceError("C", Some(Box::new(b)));
///
/// let mut iter = err.chain();
/// let mut iter = Chain::new(&c);
///
/// assert_eq!("C".to_string(), iter.next().unwrap().to_string());
/// assert_eq!("B".to_string(), iter.next().unwrap().to_string());
/// assert_eq!("A".to_string(), iter.next().unwrap().to_string());
/// assert!(iter.next().is_none());
/// assert!(iter.next().is_none());
/// ```
#[unstable(feature = "error_iter", issue = "58520")]
#[inline]
pub fn chain(&self) -> Chain<'_> {
Chain { current: Some(self) }
pub fn new(err: &'a (dyn Error + 'static)) -> Chain<'a> {
Chain { current: Some(err) }
}
}

/// An iterator over an [`Error`] and its sources.
///
/// If you want to omit the initial error and only process
/// its sources, use `skip(1)`.
#[unstable(feature = "error_iter", issue = "58520")]
#[derive(Clone, Debug)]
pub struct Chain<'a> {
current: Option<&'a (dyn Error + 'static)>,
}

#[unstable(feature = "error_iter", issue = "58520")]
impl<'a> Iterator for Chain<'a> {
type Item = &'a (dyn Error + 'static);
Expand Down

0 comments on commit e2df4da

Please sign in to comment.