Auto merge of #27991 - bluss:chain-rev, r=alexcrichton
Correct iterator adaptor Chain The iterator protocol specifies that the iteration ends with the return value `None` from `.next()` (or `.next_back()`) and it is unspecified what further calls return. The chain adaptor must account for this in its DoubleEndedIterator implementation. It uses three states: - Both `a` and `b` are valid - Only the Front iterator (`a`) is valid - Only the Back iterator (`b`) is valid The fourth state (neither iterator is valid) only occurs after Chain has returned None once, so we don't need to store this state. Fixes #26316
This commit is contained in:
commit
ef3255b063
@ -184,7 +184,7 @@ pub trait Iterator {
|
||||
fn chain<U>(self, other: U) -> Chain<Self, U::IntoIter> where
|
||||
Self: Sized, U: IntoIterator<Item=Self::Item>,
|
||||
{
|
||||
Chain{a: self, b: other.into_iter(), flag: false}
|
||||
Chain{a: self, b: other.into_iter(), state: ChainState::Both}
|
||||
}
|
||||
|
||||
/// Creates an iterator that iterates over both this and the specified
|
||||
@ -1277,7 +1277,30 @@ impl<I> Iterator for Cycle<I> where I: Clone + Iterator {
|
||||
pub struct Chain<A, B> {
|
||||
a: A,
|
||||
b: B,
|
||||
flag: bool,
|
||||
state: ChainState,
|
||||
}
|
||||
|
||||
// The iterator protocol specifies that iteration ends with the return value
|
||||
// `None` from `.next()` (or `.next_back()`) and it is unspecified what
|
||||
// further calls return. The chain adaptor must account for this since it uses
|
||||
// two subiterators.
|
||||
//
|
||||
// It uses three states:
|
||||
//
|
||||
// - Both: `a` and `b` are remaining
|
||||
// - Front: `a` remaining
|
||||
// - Back: `b` remaining
|
||||
//
|
||||
// The fourth state (neither iterator is remaining) only occurs after Chain has
|
||||
// returned None once, so we don't need to store this state.
|
||||
#[derive(Clone)]
|
||||
enum ChainState {
|
||||
// both front and back iterator are remaining
|
||||
Both,
|
||||
// only front is remaining
|
||||
Front,
|
||||
// only back is remaining
|
||||
Back,
|
||||
}
|
||||
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
@ -1289,42 +1312,58 @@ impl<A, B> Iterator for Chain<A, B> where
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<A::Item> {
|
||||
if self.flag {
|
||||
self.b.next()
|
||||
} else {
|
||||
match self.a.next() {
|
||||
Some(x) => return Some(x),
|
||||
_ => ()
|
||||
}
|
||||
self.flag = true;
|
||||
self.b.next()
|
||||
match self.state {
|
||||
ChainState::Both => match self.a.next() {
|
||||
elt @ Some(..) => return elt,
|
||||
None => {
|
||||
self.state = ChainState::Back;
|
||||
self.b.next()
|
||||
}
|
||||
},
|
||||
ChainState::Front => self.a.next(),
|
||||
ChainState::Back => self.b.next(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn count(self) -> usize {
|
||||
(if !self.flag { self.a.count() } else { 0 }) + self.b.count()
|
||||
match self.state {
|
||||
ChainState::Both => self.a.count() + self.b.count(),
|
||||
ChainState::Front => self.a.count(),
|
||||
ChainState::Back => self.b.count(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn nth(&mut self, mut n: usize) -> Option<A::Item> {
|
||||
if !self.flag {
|
||||
for x in self.a.by_ref() {
|
||||
if n == 0 {
|
||||
return Some(x)
|
||||
match self.state {
|
||||
ChainState::Both | ChainState::Front => {
|
||||
for x in self.a.by_ref() {
|
||||
if n == 0 {
|
||||
return Some(x)
|
||||
}
|
||||
n -= 1;
|
||||
}
|
||||
if let ChainState::Both = self.state {
|
||||
self.state = ChainState::Back;
|
||||
}
|
||||
n -= 1;
|
||||
}
|
||||
self.flag = true;
|
||||
ChainState::Back => {}
|
||||
}
|
||||
if let ChainState::Back = self.state {
|
||||
self.b.nth(n)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.b.nth(n)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn last(self) -> Option<A::Item> {
|
||||
let a_last = if self.flag { None } else { self.a.last() };
|
||||
let b_last = self.b.last();
|
||||
b_last.or(a_last)
|
||||
match self.state {
|
||||
ChainState::Both => self.b.last().or(self.a.last()),
|
||||
ChainState::Front => self.a.last(),
|
||||
ChainState::Back => self.b.last()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -1350,9 +1389,16 @@ impl<A, B> DoubleEndedIterator for Chain<A, B> where
|
||||
{
|
||||
#[inline]
|
||||
fn next_back(&mut self) -> Option<A::Item> {
|
||||
match self.b.next_back() {
|
||||
Some(x) => Some(x),
|
||||
None => self.a.next_back()
|
||||
match self.state {
|
||||
ChainState::Both => match self.b.next_back() {
|
||||
elt @ Some(..) => return elt,
|
||||
None => {
|
||||
self.state = ChainState::Front;
|
||||
self.a.next_back()
|
||||
}
|
||||
},
|
||||
ChainState::Front => self.a.next_back(),
|
||||
ChainState::Back => self.b.next_back(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -729,6 +729,26 @@ fn test_double_ended_chain() {
|
||||
assert_eq!(it.next_back().unwrap(), &5);
|
||||
assert_eq!(it.next_back().unwrap(), &7);
|
||||
assert_eq!(it.next_back(), None);
|
||||
|
||||
|
||||
// test that .chain() is well behaved with an unfused iterator
|
||||
struct CrazyIterator(bool);
|
||||
impl CrazyIterator { fn new() -> CrazyIterator { CrazyIterator(false) } }
|
||||
impl Iterator for CrazyIterator {
|
||||
type Item = i32;
|
||||
fn next(&mut self) -> Option<i32> {
|
||||
if self.0 { Some(99) } else { self.0 = true; None }
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleEndedIterator for CrazyIterator {
|
||||
fn next_back(&mut self) -> Option<i32> {
|
||||
self.next()
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(CrazyIterator::new().chain(0..10).rev().last(), Some(0));
|
||||
assert!((0..10).chain(CrazyIterator::new()).rev().any(|i| i == 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
Loading…
x
Reference in New Issue
Block a user