Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chain has an incorrect implementation of DoubleEndedIterator #26316

Closed
eefriedman opened this Issue Jun 15, 2015 · 4 comments

Comments

Projects
None yet
4 participants
@eefriedman
Copy link
Contributor

eefriedman commented Jun 15, 2015

Testcase:

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(1) } else { self.0 = true; None }
    }
}
impl DoubleEndedIterator for CrazyIterator {
    fn next_back(&mut self) -> Option<i32> {
        self.next()
    }
}

fn main() {
    println!("{}", CrazyIterator::new().chain(0..10).rev().any(|i| i > 10i32));
    println!("{}", (0..10).chain(CrazyIterator::new()).rev().any(|i| i > 10i32));
}

gives:

false
playpen: timeout triggered!

I'm pretty sure CrazyIterator isn't a legitimate implementation of DoubleEndedIterator, but the documentation doesn't say that explicitly, and it explicitly states that this is a legitimate implementation of Iterator. (If this is supposed to be allowed, std::iter::Chain has a bug.)

@steveklabnik

This comment has been minimized.

Copy link
Member

steveklabnik commented Jun 15, 2015

next_back(), the only required method, does say "returning None if the range is empty."

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented Jun 15, 2015

Specifically for any Iterator, you can call next, next, next, ... next k times until you see None, at which point unspecified behaviour occurs when you call next again. Replace any number of those next's with next_back and this also holds true for a DoubleEndedIterator.

Nothing else is guaranteed.

Chain is incorrectly implemented as:

impl<A, B> DoubleEndedIterator for Chain<A, B> where
    A: DoubleEndedIterator,
    B: DoubleEndedIterator<Item=A::Item>,
{
    #[inline]
    fn next_back(&mut self) -> Option<A::Item> {
        match self.b.next_back() {
            Some(x) => Some(x),
            None => self.a.next_back()
        }
    }
}

It should use the same strategy as next and use a boolean flag to avoid calling next_back after b has yielded None. I suggest a custom enum of BothAreFull | AIsEmpty | BIsEmpty | BothAreEmpty (which is necessary and sufficient to capture the correct semantics).

@Gankro Gankro changed the title DoubleEndedIterator should explicitly document what happens after None is returned Chain has an incorrect implementation of DoubleEndedIterator Jun 15, 2015

@bluss

This comment has been minimized.

Copy link
Contributor

bluss commented Jun 15, 2015

Just using .fuse() on both a and b should be enough, the logic is simpler that way.

Of course, putting the flags inside the chain struct itself is an optimization (packs the members better).

@Gankro

This comment has been minimized.

Copy link
Contributor

Gankro commented Jun 15, 2015

This is true (spoil-sport! :P)

bluss added a commit to bluss/rust that referenced this issue Aug 25, 2015

Correct iterator adaptor Chain
The iterator protocol specifies that the iteration ends with the return
value `None` from `.next()` and it is unspecified what further calls
return. The chain adaptor must account for this in its
DoubleEndedIterator implementation.

It has 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 rust-lang#26316

bluss added a commit to bluss/rust that referenced this issue Aug 25, 2015

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 has 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 rust-lang#26316

bluss added a commit to bluss/rust that referenced this issue Aug 25, 2015

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 rust-lang#26316

bluss added a commit to bluss/rust that referenced this issue Aug 25, 2015

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 rust-lang#26316

bluss added a commit to bluss/rust that referenced this issue Aug 25, 2015

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 rust-lang#26316

bluss added a commit to bluss/rust that referenced this issue Aug 25, 2015

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 rust-lang#26316

bors added a commit that referenced this issue Aug 26, 2015

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.