]> git.lizzy.rs Git - rust.git/commitdiff
Don't fuse Chain in its second iterator
authorJosh Stone <jistone@redhat.com>
Tue, 21 Apr 2020 21:56:59 +0000 (14:56 -0700)
committerJosh Stone <jistone@redhat.com>
Tue, 21 Apr 2020 21:56:59 +0000 (14:56 -0700)
Only the "first" iterator is actually set `None` when exhausted,
depending on whether you iterate forward or backward. This restores
behavior similar to the former `ChainState`, where it would transition
from `Both` to `Front`/`Back` and only continue from that side.

However, if you mix directions, then this may still set both sides to
`None`, totally fusing the iterator.

src/libcore/iter/adapters/chain.rs
src/libcore/tests/iter.rs
src/libcore/tests/lib.rs

index 2dd405ced20e1577bdbe77ea7a86927a076b0862..6700ef017bde43922f51411083d3f99d4b9ce134 100644 (file)
@@ -18,6 +18,9 @@ pub struct Chain<A, B> {
     // adapter because its specialization for `FusedIterator` unconditionally descends into the
     // iterator, and that could be expensive to keep revisiting stuff like nested chains. It also
     // hurts compiler performance to add more iterator layers to `Chain`.
+    //
+    // Only the "first" iterator is actually set `None` when exhausted, depending on whether you
+    // iterate forward or backward. If you mix directions, then both sides may be `None`.
     a: Option<A>,
     b: Option<B>,
 }
@@ -43,6 +46,17 @@ macro_rules! fuse {
     };
 }
 
+/// Try an iterator method without fusing,
+/// like an inline `.as_mut().and_then(...)`
+macro_rules! maybe {
+    ($self:ident . $iter:ident . $($call:tt)+) => {
+        match $self.$iter {
+            Some(ref mut iter) => iter.$($call)+,
+            None => None,
+        }
+    };
+}
+
 #[stable(feature = "rust1", since = "1.0.0")]
 impl<A, B> Iterator for Chain<A, B>
 where
@@ -54,7 +68,7 @@ impl<A, B> Iterator for Chain<A, B>
     #[inline]
     fn next(&mut self) -> Option<A::Item> {
         match fuse!(self.a.next()) {
-            None => fuse!(self.b.next()),
+            None => maybe!(self.b.next()),
             item => item,
         }
     }
@@ -85,7 +99,7 @@ fn try_fold<Acc, F, R>(&mut self, mut acc: Acc, mut f: F) -> R
         }
         if let Some(ref mut b) = self.b {
             acc = b.try_fold(acc, f)?;
-            self.b = None;
+            // we don't fuse the second iterator
         }
         Try::from_ok(acc)
     }
@@ -114,7 +128,7 @@ fn nth(&mut self, mut n: usize) -> Option<A::Item> {
             }
             self.a = None;
         }
-        fuse!(self.b.nth(n))
+        maybe!(self.b.nth(n))
     }
 
     #[inline]
@@ -123,7 +137,7 @@ fn find<P>(&mut self, mut predicate: P) -> Option<Self::Item>
         P: FnMut(&Self::Item) -> bool,
     {
         match fuse!(self.a.find(&mut predicate)) {
-            None => fuse!(self.b.find(predicate)),
+            None => maybe!(self.b.find(predicate)),
             item => item,
         }
     }
@@ -174,7 +188,7 @@ impl<A, B> DoubleEndedIterator for Chain<A, B>
     #[inline]
     fn next_back(&mut self) -> Option<A::Item> {
         match fuse!(self.b.next_back()) {
-            None => fuse!(self.a.next_back()),
+            None => maybe!(self.a.next_back()),
             item => item,
         }
     }
@@ -190,7 +204,7 @@ fn nth_back(&mut self, mut n: usize) -> Option<A::Item> {
             }
             self.b = None;
         }
-        fuse!(self.a.nth_back(n))
+        maybe!(self.a.nth_back(n))
     }
 
     #[inline]
@@ -199,7 +213,7 @@ fn rfind<P>(&mut self, mut predicate: P) -> Option<Self::Item>
         P: FnMut(&Self::Item) -> bool,
     {
         match fuse!(self.b.rfind(&mut predicate)) {
-            None => fuse!(self.a.rfind(predicate)),
+            None => maybe!(self.a.rfind(predicate)),
             item => item,
         }
     }
@@ -216,7 +230,7 @@ fn try_rfold<Acc, F, R>(&mut self, mut acc: Acc, mut f: F) -> R
         }
         if let Some(ref mut a) = self.a {
             acc = a.try_rfold(acc, f)?;
-            self.a = None;
+            // we don't fuse the second iterator
         }
         Try::from_ok(acc)
     }
@@ -236,8 +250,6 @@ fn rfold<Acc, F>(self, mut acc: Acc, mut f: F) -> Acc
 }
 
 // Note: *both* must be fused to handle double-ended iterators.
-// Now that we "fuse" both sides, we *could* implement this unconditionally,
-// but we should be cautious about committing to that in the public API.
 #[stable(feature = "fused", since = "1.26.0")]
 impl<A, B> FusedIterator for Chain<A, B>
 where
index 1a1dbcd7b871aaab2e22a1c847a521fd097b0dd2..7da02b11676abd32a36a0a601798160ad06591ee 100644 (file)
@@ -207,50 +207,64 @@ fn test_iterator_chain_find() {
     assert_eq!(iter.next(), None);
 }
 
-#[test]
-fn test_iterator_chain_size_hint() {
-    struct Iter {
-        is_empty: bool,
-    }
+struct Toggle {
+    is_empty: bool,
+}
 
-    impl Iterator for Iter {
-        type Item = ();
+impl Iterator for Toggle {
+    type Item = ();
 
-        // alternates between `None` and `Some(())`
-        fn next(&mut self) -> Option<Self::Item> {
-            if self.is_empty {
-                self.is_empty = false;
-                None
-            } else {
-                self.is_empty = true;
-                Some(())
-            }
+    // alternates between `None` and `Some(())`
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.is_empty {
+            self.is_empty = false;
+            None
+        } else {
+            self.is_empty = true;
+            Some(())
         }
+    }
 
-        fn size_hint(&self) -> (usize, Option<usize>) {
-            if self.is_empty { (0, Some(0)) } else { (1, Some(1)) }
-        }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        if self.is_empty { (0, Some(0)) } else { (1, Some(1)) }
     }
+}
 
-    impl DoubleEndedIterator for Iter {
-        fn next_back(&mut self) -> Option<Self::Item> {
-            self.next()
-        }
+impl DoubleEndedIterator for Toggle {
+    fn next_back(&mut self) -> Option<Self::Item> {
+        self.next()
     }
+}
 
+#[test]
+fn test_iterator_chain_size_hint() {
     // this chains an iterator of length 0 with an iterator of length 1,
     // so after calling `.next()` once, the iterator is empty and the
     // state is `ChainState::Back`. `.size_hint()` should now disregard
     // the size hint of the left iterator
-    let mut iter = Iter { is_empty: true }.chain(once(()));
+    let mut iter = Toggle { is_empty: true }.chain(once(()));
     assert_eq!(iter.next(), Some(()));
     assert_eq!(iter.size_hint(), (0, Some(0)));
 
-    let mut iter = once(()).chain(Iter { is_empty: true });
+    let mut iter = once(()).chain(Toggle { is_empty: true });
     assert_eq!(iter.next_back(), Some(()));
     assert_eq!(iter.size_hint(), (0, Some(0)));
 }
 
+#[test]
+fn test_iterator_chain_unfused() {
+    // Chain shouldn't be fused in its second iterator, depending on direction
+    let mut iter = NonFused::new(empty()).chain(Toggle { is_empty: true });
+    iter.next().unwrap_none();
+    iter.next().unwrap();
+    iter.next().unwrap_none();
+
+    let mut iter = Toggle { is_empty: true }.chain(NonFused::new(empty()));
+    iter.next_back().unwrap_none();
+    iter.next_back().unwrap();
+    iter.next_back().unwrap_none();
+}
+
 #[test]
 fn test_zip_nth() {
     let xs = [0, 1, 2, 4, 5];
index 05f958cbe81fe09e4e2d707ec6395c88ef2ab542..e7d36d327cd89c14af2ddf628eff1aef612e366c 100644 (file)
@@ -42,6 +42,7 @@
 #![feature(unwrap_infallible)]
 #![feature(leading_trailing_ones)]
 #![feature(const_forget)]
+#![feature(option_unwrap_none)]
 
 extern crate test;