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

Unable to call reverse on a Zip Iterator zipping a std::iter::Repeat #104729

Open
IceTDrinker opened this issue Nov 22, 2022 · 5 comments
Open

Unable to call reverse on a Zip Iterator zipping a std::iter::Repeat #104729

IceTDrinker opened this issue Nov 22, 2022 · 5 comments
Labels
A-iterators Area: Iterators C-enhancement Category: An issue proposing an enhancement or a PR with one. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@IceTDrinker
Copy link

Hello, first off sorry if this is not the right issue type, but I could not choose one that seemed to better fit. I tried to search in existing issues with "zip repeat rev" and nothing came up in GitHub. I don't know if it qualifies as a bug, but at least the behavior feels highly unintuitive.

I tried this code:

fn main() {
    let vec_1 = vec![1; 10];
    
    let zipped_iter = vec_1.iter().zip( std::iter::repeat(0));
    
    println!("Forward");
    
    for (one, zero) in zipped_iter {
        println!("one: {one}, zero: {zero}");
    }
    
    let rev_vec_iter = vec_1.iter().rev();
    let rev_repeat_iter = std::iter::repeat(0).rev();
    
    // Manual reversed zip
    let rev_zipped_iter = rev_vec_iter.zip(rev_repeat_iter);
    
    println!("Backwards");
    
    for (one, zero) in rev_zipped_iter {
        println!("one: {one}, zero: {zero}");
    }
    
    println!("Now it gets tricky");
    
    // This is illegal with the current next_back implementation of Zip
    // Which requires ExactSizeIterator, which is not implemented by
    // std::iter::repeat, so won't compile
    
    let zipped_iter = vec_1.iter().zip( std::iter::repeat(0));
    
    // Cannot call rev here for automatic reversed zip constuction
    for (one, zero) in zipped_iter.rev() {
        println!("one: {one}, zero: {zero}");
    }
}

Rust playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=967385983c0fb72c27183385720fea8e

I expected to see this happen: the last iterator construction reversing the Zip iterator works the same as if I zipped two reverse iterator.

Instead, this happened: code does not compile because next_back implementation from Zip (ZipImpl::next_back) has ExactSizeIterator bounds for both iterator being zipped. std::iter::Repeat does not implement that trait. I'm not familiar with Zip internals or iterator internals but it seems it has to do with zip_impl_general_defaults which states:

        #[inline]
        default fn next_back(&mut self) -> Option<(A::Item, B::Item)>
        where
            A: DoubleEndedIterator + ExactSizeIterator,
            B: DoubleEndedIterator + ExactSizeIterator,
        {
            // The function body below only uses `self.a/b.len()` and `self.a/b.next_back()`
            // and doesn’t call `next_back` too often, so this implementation is safe in
            // the `TrustedRandomAccessNoCoerce` specialization

Fails for all rustc versions in playground at the moment

image

Meta

rustc --version --verbose:

rustc --version --verbose
rustc 1.65.0 (897e37553 2022-11-02)
binary: rustc
commit-hash: 897e37553bba8b42751c67658967889d11ecd120
commit-date: 2022-11-02
host: x86_64-unknown-linux-gnu
release: 1.65.0
LLVM version: 15.0.0
Backtrace

Compiling playground v0.0.1 (/playground)
error[[E0277]](https://doc.rust-lang.org/stable/error-index.html#E0277): the trait bound `std::iter::Repeat<{integer}>: ExactSizeIterator` is not satisfied
  --> src/main.rs:33:24
   |
33 |     for (one, zero) in zipped_iter.rev() {
   |                        ^^^^^^^^^^^ --- required by a bound introduced by this call
   |                        |
   |                        the trait `ExactSizeIterator` is not implemented for `std::iter::Repeat<{integer}>`
   |
   = help: the following other types implement trait `ExactSizeIterator`:
             &mut I
             Args
             ArgsOs
             ArrayChunksMut<'_, T, N>
             ArrayWindows<'_, T, N>
             Box<I, A>
             Chunks<'_, T>
             ChunksExact<'_, T>
           and 108 others
   = note: required for `Zip<std::slice::Iter<'_, {integer}>, std::iter::Repeat<{integer}>>` to implement `DoubleEndedIterator`
note: required by a bound in `rev`

error[[E0277]](https://doc.rust-lang.org/stable/error-index.html#E0277): the trait bound `std::iter::Repeat<{integer}>: ExactSizeIterator` is not satisfied
  --> src/main.rs:33:24
   |
33 |     for (one, zero) in zipped_iter.rev() {
   |                        ^^^^^^^^^^^^^^^^^ the trait `ExactSizeIterator` is not implemented for `std::iter::Repeat<{integer}>`
   |
   = help: the following other types implement trait `ExactSizeIterator`:
             &mut I
             Args
             ArgsOs
             ArrayChunksMut<'_, T, N>
             ArrayWindows<'_, T, N>
             Box<I, A>
             Chunks<'_, T>
             ChunksExact<'_, T>
           and 108 others
   = note: required for `Zip<std::slice::Iter<'_, {integer}>, std::iter::Repeat<{integer}>>` to implement `DoubleEndedIterator`
   = note: required for `Rev<Zip<std::slice::Iter<'_, {integer}>, std::iter::Repeat<{integer}>>>` to implement `Iterator`
   = note: required for `Rev<Zip<std::slice::Iter<'_, {integer}>, std::iter::Repeat<{integer}>>>` to implement `IntoIterator`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to 2 previous errors

@IceTDrinker IceTDrinker added the C-bug Category: This is a bug. label Nov 22, 2022
@jdahlstrom
Copy link

jdahlstrom commented Nov 22, 2022

I think the problem is that a reversed Zip ought to return the exact same tuples as the unreversed one, just in a reversed order:

[1,2,3].iter().zip(&[1, 2, 3, 4]).rev() // yields (3, 3), (2, 2), (1, 1)

But that becomes impossible if there's not enough information to know how many elements to skip from the end of the longer iterator (if it even has an end at all!). In the special case of Repeat, it happens to not matter, but for almost any other indefinite-length iterator it does.

In the general case the only way to make something like the following work would be to materialize (ie. collect) a part of the iterator to a temporary storage, and Rust doesn't like to do such things implicitly:

[1,2,3].iter()
    .zip(std::iter::successors(Some(1), |x| Some(x+1)))
    .rev() // Where to even begin???

@cuviper
Copy link
Member

cuviper commented Nov 24, 2022

It should work with repeat_n though -- #104434.

@IceTDrinker
Copy link
Author

It should work with repeat_n though -- #104434.

Looks interesting, thanks

@IceTDrinker
Copy link
Author

For people coming across this and waiting on repeat_n in rust you have itertools https://docs.rs/itertools/latest/itertools/fn.repeat_n.html repeat_n and it has a permissive license

mina86 added a commit to mina86/rust that referenced this issue Sep 2, 2023
…ith>

Repeat iterator always returns the same element and behaves the same way
backwards and forwards.  Take iterator can trivially implement backwards
iteration over Repeat inner iterator by simply doing forwards iteration.

DoubleEndedIterator is not currently implemented for Take<Repeat<T>>
because Repeat doesn’t implement ExactSizeIterator which is a required
bound on DEI implementation for Take.

Similarly, since Repeat is an infinite iterator which never stops, Take
can trivially know how many elements it’s going to return.  This allows
implementing ExactSizeIterator on Take<Repeat<T>>.

While at it, observe that ExactSizeIterator can also be implemented for
Take<RepeatWhile<F>> so add that implementation too.  Since in contrast
to Repeat, RepeatWhile doesn’t guarante to always return the same value,
DoubleEndedIterator isn’t implemented.

Those changes render core::iter::repeat_n somewhat redundant.

Issue: rust-lang#104434
Issue: rust-lang#104729
@fmease fmease added C-enhancement Category: An issue proposing an enhancement or a PR with one. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. A-iterators Area: Iterators and removed needs-triage-legacy C-bug Category: This is a bug. labels Jan 25, 2024
bors added a commit to rust-lang-ci/rust that referenced this issue Aug 16, 2024
…lnay

Implement DoubleEnded and ExactSize for Take<Repeat> and Take<RepeatWith>

Repeat iterator always returns the same element and behaves the same way
backwards and forwards.  Take iterator can trivially implement backwards
iteration over Repeat inner iterator by simply doing forwards iteration.

DoubleEndedIterator is not currently implemented for Take<Repeat<T>>
because Repeat doesn’t implement ExactSizeIterator which is a required
bound on DEI implementation for Take.

Similarly, since Repeat is an infinite iterator which never stops, Take
can trivially know how many elements it’s going to return.  This allows
implementing ExactSizeIterator on Take<Repeat<T>>.

While at it, observe that ExactSizeIterator can also be implemented for
Take<RepeatWhile<F>> so add that implementation too.  Since in contrast
to Repeat, RepeatWhile doesn’t guarante to always return the same value,
DoubleEndedIterator isn’t implemented.

Those changes render core::iter::repeat_n somewhat redundant.

Issue: rust-lang#104434
Issue: rust-lang#104729

- [ ] ACP: rust-lang/libs-team#120 (this is actually ACP for repeat_n but this is nearly the same functionality so hijacking it so both approaches can be discussed in one place)
bors pushed a commit to rust-lang-ci/rust that referenced this issue Aug 17, 2024
…ith>

Repeat iterator always returns the same element and behaves the same way
backwards and forwards.  Take iterator can trivially implement backwards
iteration over Repeat inner iterator by simply doing forwards iteration.

DoubleEndedIterator is not currently implemented for Take<Repeat<T>>
because Repeat doesn’t implement ExactSizeIterator which is a required
bound on DEI implementation for Take.

Similarly, since Repeat is an infinite iterator which never stops, Take
can trivially know how many elements it’s going to return.  This allows
implementing ExactSizeIterator on Take<Repeat<T>>.

While at it, observe that ExactSizeIterator can also be implemented for
Take<RepeatWhile<F>> so add that implementation too.  Since in contrast
to Repeat, RepeatWhile doesn’t guarante to always return the same value,
DoubleEndedIterator isn’t implemented.

Those changes render core::iter::repeat_n somewhat redundant.

Issue: rust-lang#104434
Issue: rust-lang#104729
bors added a commit to rust-lang-ci/rust that referenced this issue Aug 17, 2024
…lnay

Implement DoubleEnded and ExactSize for Take<Repeat> and Take<RepeatWith>

Repeat iterator always returns the same element and behaves the same way
backwards and forwards.  Take iterator can trivially implement backwards
iteration over Repeat inner iterator by simply doing forwards iteration.

DoubleEndedIterator is not currently implemented for Take<Repeat<T>>
because Repeat doesn’t implement ExactSizeIterator which is a required
bound on DEI implementation for Take.

Similarly, since Repeat is an infinite iterator which never stops, Take
can trivially know how many elements it’s going to return.  This allows
implementing ExactSizeIterator on Take<Repeat<T>>.

While at it, observe that ExactSizeIterator can also be implemented for
Take<RepeatWhile<F>> so add that implementation too.  Since in contrast
to Repeat, RepeatWhile doesn’t guarante to always return the same value,
DoubleEndedIterator isn’t implemented.

Those changes render core::iter::repeat_n somewhat redundant.

Issue: rust-lang#104434
Issue: rust-lang#104729

- [ ] ACP: rust-lang/libs-team#120 (this is actually ACP for repeat_n but this is nearly the same functionality so hijacking it so both approaches can be discussed in one place)
rezwanahmedsami pushed a commit to rezwanahmedsami/rust that referenced this issue Aug 17, 2024
…ith>

Repeat iterator always returns the same element and behaves the same way
backwards and forwards.  Take iterator can trivially implement backwards
iteration over Repeat inner iterator by simply doing forwards iteration.

DoubleEndedIterator is not currently implemented for Take<Repeat<T>>
because Repeat doesn’t implement ExactSizeIterator which is a required
bound on DEI implementation for Take.

Similarly, since Repeat is an infinite iterator which never stops, Take
can trivially know how many elements it’s going to return.  This allows
implementing ExactSizeIterator on Take<Repeat<T>>.

While at it, observe that ExactSizeIterator can also be implemented for
Take<RepeatWhile<F>> so add that implementation too.  Since in contrast
to Repeat, RepeatWhile doesn’t guarante to always return the same value,
DoubleEndedIterator isn’t implemented.

Those changes render core::iter::repeat_n somewhat redundant.

Issue: rust-lang#104434
Issue: rust-lang#104729
@scottmcm
Copy link
Member

PR to stabilize repeat_n is up at #129294 too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-iterators Area: Iterators C-enhancement Category: An issue proposing an enhancement or a PR with one. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants