Skip to content

Commit 2a1cd44

Browse files
authored
Rollup merge of #71404 - cuviper:chain-unfused, r=scottmcm
Don't fuse Chain in its second iterator 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. Fixes #71375 r? @scottmcm
2 parents 1d3d80f + eeb687f commit 2a1cd44

File tree

3 files changed

+62
-35
lines changed

3 files changed

+62
-35
lines changed

src/libcore/iter/adapters/chain.rs

+22-10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ pub struct Chain<A, B> {
1818
// adapter because its specialization for `FusedIterator` unconditionally descends into the
1919
// iterator, and that could be expensive to keep revisiting stuff like nested chains. It also
2020
// hurts compiler performance to add more iterator layers to `Chain`.
21+
//
22+
// Only the "first" iterator is actually set `None` when exhausted, depending on whether you
23+
// iterate forward or backward. If you mix directions, then both sides may be `None`.
2124
a: Option<A>,
2225
b: Option<B>,
2326
}
@@ -43,6 +46,17 @@ macro_rules! fuse {
4346
};
4447
}
4548

49+
/// Try an iterator method without fusing,
50+
/// like an inline `.as_mut().and_then(...)`
51+
macro_rules! maybe {
52+
($self:ident . $iter:ident . $($call:tt)+) => {
53+
match $self.$iter {
54+
Some(ref mut iter) => iter.$($call)+,
55+
None => None,
56+
}
57+
};
58+
}
59+
4660
#[stable(feature = "rust1", since = "1.0.0")]
4761
impl<A, B> Iterator for Chain<A, B>
4862
where
@@ -54,7 +68,7 @@ where
5468
#[inline]
5569
fn next(&mut self) -> Option<A::Item> {
5670
match fuse!(self.a.next()) {
57-
None => fuse!(self.b.next()),
71+
None => maybe!(self.b.next()),
5872
item => item,
5973
}
6074
}
@@ -85,7 +99,7 @@ where
8599
}
86100
if let Some(ref mut b) = self.b {
87101
acc = b.try_fold(acc, f)?;
88-
self.b = None;
102+
// we don't fuse the second iterator
89103
}
90104
Try::from_ok(acc)
91105
}
@@ -114,7 +128,7 @@ where
114128
}
115129
self.a = None;
116130
}
117-
fuse!(self.b.nth(n))
131+
maybe!(self.b.nth(n))
118132
}
119133

120134
#[inline]
@@ -123,7 +137,7 @@ where
123137
P: FnMut(&Self::Item) -> bool,
124138
{
125139
match fuse!(self.a.find(&mut predicate)) {
126-
None => fuse!(self.b.find(predicate)),
140+
None => maybe!(self.b.find(predicate)),
127141
item => item,
128142
}
129143
}
@@ -174,7 +188,7 @@ where
174188
#[inline]
175189
fn next_back(&mut self) -> Option<A::Item> {
176190
match fuse!(self.b.next_back()) {
177-
None => fuse!(self.a.next_back()),
191+
None => maybe!(self.a.next_back()),
178192
item => item,
179193
}
180194
}
@@ -190,7 +204,7 @@ where
190204
}
191205
self.b = None;
192206
}
193-
fuse!(self.a.nth_back(n))
207+
maybe!(self.a.nth_back(n))
194208
}
195209

196210
#[inline]
@@ -199,7 +213,7 @@ where
199213
P: FnMut(&Self::Item) -> bool,
200214
{
201215
match fuse!(self.b.rfind(&mut predicate)) {
202-
None => fuse!(self.a.rfind(predicate)),
216+
None => maybe!(self.a.rfind(predicate)),
203217
item => item,
204218
}
205219
}
@@ -216,7 +230,7 @@ where
216230
}
217231
if let Some(ref mut a) = self.a {
218232
acc = a.try_rfold(acc, f)?;
219-
self.a = None;
233+
// we don't fuse the second iterator
220234
}
221235
Try::from_ok(acc)
222236
}
@@ -236,8 +250,6 @@ where
236250
}
237251

238252
// Note: *both* must be fused to handle double-ended iterators.
239-
// Now that we "fuse" both sides, we *could* implement this unconditionally,
240-
// but we should be cautious about committing to that in the public API.
241253
#[stable(feature = "fused", since = "1.26.0")]
242254
impl<A, B> FusedIterator for Chain<A, B>
243255
where

src/libcore/tests/iter.rs

+39-25
Original file line numberDiff line numberDiff line change
@@ -207,50 +207,64 @@ fn test_iterator_chain_find() {
207207
assert_eq!(iter.next(), None);
208208
}
209209

210-
#[test]
211-
fn test_iterator_chain_size_hint() {
212-
struct Iter {
213-
is_empty: bool,
214-
}
210+
struct Toggle {
211+
is_empty: bool,
212+
}
215213

216-
impl Iterator for Iter {
217-
type Item = ();
214+
impl Iterator for Toggle {
215+
type Item = ();
218216

219-
// alternates between `None` and `Some(())`
220-
fn next(&mut self) -> Option<Self::Item> {
221-
if self.is_empty {
222-
self.is_empty = false;
223-
None
224-
} else {
225-
self.is_empty = true;
226-
Some(())
227-
}
217+
// alternates between `None` and `Some(())`
218+
fn next(&mut self) -> Option<Self::Item> {
219+
if self.is_empty {
220+
self.is_empty = false;
221+
None
222+
} else {
223+
self.is_empty = true;
224+
Some(())
228225
}
226+
}
229227

230-
fn size_hint(&self) -> (usize, Option<usize>) {
231-
if self.is_empty { (0, Some(0)) } else { (1, Some(1)) }
232-
}
228+
fn size_hint(&self) -> (usize, Option<usize>) {
229+
if self.is_empty { (0, Some(0)) } else { (1, Some(1)) }
233230
}
231+
}
234232

235-
impl DoubleEndedIterator for Iter {
236-
fn next_back(&mut self) -> Option<Self::Item> {
237-
self.next()
238-
}
233+
impl DoubleEndedIterator for Toggle {
234+
fn next_back(&mut self) -> Option<Self::Item> {
235+
self.next()
239236
}
237+
}
240238

239+
#[test]
240+
fn test_iterator_chain_size_hint() {
241241
// this chains an iterator of length 0 with an iterator of length 1,
242242
// so after calling `.next()` once, the iterator is empty and the
243243
// state is `ChainState::Back`. `.size_hint()` should now disregard
244244
// the size hint of the left iterator
245-
let mut iter = Iter { is_empty: true }.chain(once(()));
245+
let mut iter = Toggle { is_empty: true }.chain(once(()));
246246
assert_eq!(iter.next(), Some(()));
247247
assert_eq!(iter.size_hint(), (0, Some(0)));
248248

249-
let mut iter = once(()).chain(Iter { is_empty: true });
249+
let mut iter = once(()).chain(Toggle { is_empty: true });
250250
assert_eq!(iter.next_back(), Some(()));
251251
assert_eq!(iter.size_hint(), (0, Some(0)));
252252
}
253253

254+
#[test]
255+
fn test_iterator_chain_unfused() {
256+
// Chain shouldn't be fused in its second iterator, depending on direction
257+
let mut iter = NonFused::new(empty()).chain(Toggle { is_empty: true });
258+
iter.next().unwrap_none();
259+
iter.next().unwrap();
260+
iter.next().unwrap_none();
261+
262+
let mut iter = Toggle { is_empty: true }.chain(NonFused::new(empty()));
263+
iter.next_back().unwrap_none();
264+
iter.next_back().unwrap();
265+
iter.next_back().unwrap_none();
266+
}
267+
254268
#[test]
255269
fn test_zip_nth() {
256270
let xs = [0, 1, 2, 4, 5];

src/libcore/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#![feature(unwrap_infallible)]
4343
#![feature(leading_trailing_ones)]
4444
#![feature(const_forget)]
45+
#![feature(option_unwrap_none)]
4546

4647
extern crate test;
4748

0 commit comments

Comments
 (0)