Skip to content

Chain has an incorrect implementation of DoubleEndedIterator #26316

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

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

Chain has an incorrect implementation of DoubleEndedIterator #26316

eefriedman opened this issue Jun 15, 2015 · 4 comments
Labels
P-low Low priority

Comments

@eefriedman
Copy link
Contributor

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
Copy link
Member

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

@Gankra
Copy link
Contributor

Gankra 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).

@Gankra Gankra changed the title DoubleEndedIterator should explicitly document what happens after None is returned Chain has an incorrect implementation of DoubleEndedIterator Jun 15, 2015
@Gankra Gankra added A-libs P-low Low priority labels Jun 15, 2015
@bluss
Copy link
Member

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).

@Gankra
Copy link
Contributor

Gankra commented Jun 15, 2015

This is true (spoil-sport! :P)

bluss added a commit to bluss/rust that referenced this issue Aug 25, 2015
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
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
Labels
P-low Low priority
Projects
None yet
Development

No branches or pull requests

4 participants