Repository: dtolnay/anyhow (Rust)

Issue:
Consider implementing DoubleEndedIterator for Chain
We may sometimes want errors that are printed inside-out from lowest level to highest.

```rust
for e in error.chain().rev() {
    println!("{}", e);
}
```

This may requires some buffering but that's fine, the caller would be doing it anyway in this situation.


Discussion:

Patch:
diff --git a/src/chain.rs b/src/chain.rs
new file mode 100644
index 0000000..e2937cb
--- /dev/null
+++ b/src/chain.rs
@@ -0,0 +1,69 @@
+use self::ChainState::{Buffered, Linked};
+use crate::Chain;
+use std::error::Error as StdError;
+use std::vec;
+
+#[derive(Clone)]
+pub(crate) enum ChainState<'a> {
+    Linked {
+        next: Option<&'a (dyn StdError + 'static)>,
+    },
+    Buffered {
+        rest: vec::IntoIter<&'a (dyn StdError + 'static)>,
+    },
+}
+
+impl<'a> Iterator for Chain<'a> {
+    type Item = &'a (dyn StdError + 'static);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match &mut self.state {
+            Linked { next } => {
+                let error = (*next)?;
+                *next = error.source();
+                Some(error)
+            }
+            Buffered { rest } => rest.next(),
+        }
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let len = self.len();
+        (len, Some(len))
+    }
+}
+
+impl DoubleEndedIterator for Chain<'_> {
+    fn next_back(&mut self) -> Option<Self::Item> {
+        match &mut self.state {
+            Linked { mut next } => {
+                let mut rest = Vec::new();
+                while let Some(cause) = next {
+                    next = cause.source();
+                    rest.push(cause);
+                }
+                let mut rest = rest.into_iter();
+                let last = rest.next_back();
+                self.state = Buffered { rest };
+                last
+            }
+            Buffered { rest } => rest.next_back(),
+        }
+    }
+}
+
+impl ExactSizeIterator for Chain<'_> {
+    fn len(&self) -> usize {
+        match &self.state {
+            Linked { mut next } => {
+                let mut len = 0;
+                while let Some(cause) = next {
+                    next = cause.source();
+                    len += 1;
+                }
+                len
+            }
+            Buffered { rest } => rest.len(),
+        }
+    }
+}
diff --git a/src/error.rs b/src/error.rs
index f51033e..b2bf934 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,4 +1,5 @@
 use crate::backtrace::Backtrace;
+use crate::chain::ChainState;
 use crate::{Chain, Error};
 use std::any::TypeId;
 use std::error::Error as StdError;
@@ -673,7 +674,9 @@ impl ErrorImpl<()> {
 
     pub(crate) fn chain(&self) -> Chain {
         Chain {
-            next: Some(self.error()),
+            state: ChainState::Linked {
+                next: Some(self.error()),
+            },
         }
     }
 }
@@ -726,13 +729,3 @@ impl From<Error> for Box<dyn StdError + 'static> {
         Box::<dyn StdError + Send + Sync>::from(error)
     }
 }
-
-impl<'a> Iterator for Chain<'a> {
-    type Item = &'a (dyn StdError + 'static);
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let next = self.next?;
-        self.next = next.source();
-        Some(next)
-    }
-}
diff --git a/src/lib.rs b/src/lib.rs
index ab552ec..ac0f02b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -169,6 +169,7 @@
 
 #[macro_use]
 mod backtrace;
+mod chain;
 mod context;
 mod error;
 mod fmt;
@@ -178,8 +179,8 @@ mod macros;
 #[cfg(not(feature = "std"))]
 compile_error!("no_std support is not implemented yet");
 
+use crate::chain::ChainState;
 use crate::error::ErrorImpl;
-use std::error::Error as StdError;
 use std::fmt::Display;
 use std::mem::ManuallyDrop;
 
@@ -302,7 +303,7 @@ pub struct Error {
 /// ```
 #[derive(Clone)]
 pub struct Chain<'a> {
-    next: Option<&'a (dyn StdError + 'static)>,
+    state: ChainState<'a>,
 }
 
 /// `Result<T, Error>`


Test patch:
diff --git a/tests/test_chain.rs b/tests/test_chain.rs
new file mode 100644
index 0000000..b1c5a3d
--- /dev/null
+++ b/tests/test_chain.rs
@@ -0,0 +1,45 @@
+use anyhow::{anyhow, Error};
+
+fn error() -> Error {
+    anyhow!(0).context(1).context(2).context(3)
+}
+
+#[test]
+fn test_iter() {
+    let e = error();
+    let mut chain = e.chain();
+    assert_eq!("3", chain.next().unwrap().to_string());
+    assert_eq!("2", chain.next().unwrap().to_string());
+    assert_eq!("1", chain.next().unwrap().to_string());
+    assert_eq!("0", chain.next().unwrap().to_string());
+    assert!(chain.next().is_none());
+    assert!(chain.next_back().is_none());
+}
+
+#[test]
+fn test_rev() {
+    let e = error();
+    let mut chain = e.chain().rev();
+    assert_eq!("0", chain.next().unwrap().to_string());
+    assert_eq!("1", chain.next().unwrap().to_string());
+    assert_eq!("2", chain.next().unwrap().to_string());
+    assert_eq!("3", chain.next().unwrap().to_string());
+    assert!(chain.next().is_none());
+    assert!(chain.next_back().is_none());
+}
+
+#[test]
+fn test_len() {
+    let e = error();
+    let mut chain = e.chain();
+    assert_eq!(4, chain.len());
+    assert_eq!("3", chain.next().unwrap().to_string());
+    assert_eq!(3, chain.len());
+    assert_eq!("0", chain.next_back().unwrap().to_string());
+    assert_eq!(2, chain.len());
+    assert_eq!("2", chain.next().unwrap().to_string());
+    assert_eq!(1, chain.len());
+    assert_eq!("1", chain.next_back().unwrap().to_string());
+    assert_eq!(0, chain.len());
+    assert!(chain.next().is_none());
+}