Skip to content

Generalize ExactSizeIterator to QuantifiedIterator #659

@414owen

Description

@414owen

Proposal

Problem statement

I use bumpalo to amortize allocations.
One of the main functions I call is alloc_slice_fill_iter.
This only works on iterators which implement ExactSizeIterator.

I have iterators which have a known size, but don't implement this trait.

Motivating examples or use cases

Here are some iterators whose exact size is known at runtime, but which don't implement ExactSizeIterator.

  • (0..).map(|a| a + 3).take(20)
  • [1, 2].into_iter().zip(repeat(3))
  • repeat(a).copied().take(3)
  • once(42).chain((0..).filter(|a| a % 2 == 0)).take(2)

These all combine iterators which never return None (hereby refered to as infinite), with a limit from a call to .take(), or by .zip()ping with an ExactSizeIterator.

Solution sketch

My previous attempt introduced an InfiniteIterator trait, which can be used in combination with .take(), or .zip() to obtain an ExactSizeIterator.

This approach encountered problematic instances, for example on Zip, where I had implemented ExactSizeIterator for the sub-iterator combinations (Exact, Infinite), and (Infinite, Exact), on top of the existing implementation for (Exact, Exact). I tried solving this with negative bounds, which are probably unsound.

There is, however, a solution which works fine in stable rust, and covers all combinations of infinite and exact size iterator adapters.

Unfortunately, it somewhat generalizes and subsumes ExactSizeIterator, hence the ACP.

struct Exact {}
struct Infinite {}

trait QuantifiedIterator: Iterator {
    type Quantity;
}

The idea here, is to classify iterators into either Exact, or Infinite. Other quantifications are possible, but these are the two that seems immediately useful.

With this setup, we only need one implementation of QuantifiedIterator for Zip:

trait ZipQuantity {
    type Result;
}

impl ZipQuantity for (Exact, Exact) {
    type Result = Exact;
}

impl<A> ZipQuantity for (Infinite, Exact) {
    type Result = Exact;
}

impl ZipQuantity for (Exact, Infinite) {
    type Result = Exact;
}

impl<A, B> QuantifiedIterator for Zip<A, B>
where
    A: QuantifiedIter,
    B: QuantifiedIter,
    (A::Quantity, B::Quantity): ZipQuantity,
{
    type Quantity = <(A::Quantity, B::Quantity) as ZipQuantity>::Result;
}

ZipQuantity is a type-level function which maps two quantities to a resulting quantity.

A similar setup works for Chain (note that I've macroized the type-level function.

Another important adapter is Take. Its implementation is here.

Most iterator adapters have instances (an example of one which doesn't is take_while).

All sources of infinite iteration and exact iteration will have concrete implementations of QuantifiedIterator.

ExactSizeIterator can still exist, and can be implemented in terms of QuantifiedIterator.

The incompatibility comes from this (or at least, one source I've wrapped my head around):

At the moment, the standard library iterator adapters will work for third-party iterators, if they implement ExactSizeIterator.
With this change, at least some iterator adapters in the standard library will be implemented in terms of QuantifiedIterator (at least Zip, Chain, and Take).
This will break the ExactSizeIterator instance for existing third-party iterators, adapted by the stdlib.

Alternatives

Adding concrete instances piecemeal

I initially did this here.

Obviously, you can't cover every combination of iterator adapters like this, and it would be foolish to try.

At the moment, I know of two concrete instance in core, which are for Take<Repeat>, and Take<RepeatWith>, here

Adding a simple InfiniteIterator trait

I tried this here.

This used negative bounds to deal with the overlapping instance for Zip. It's probably unsound. I certainly ran into places where I couldn't figure out why it wasn't choosing my new instance.

Links and related work

Links to my previous attempts are above, under Alternatives.

here is another attempt.

Some discussion on discourse

I have an implementation of this proposal here, with all core and std iterators, and iterator adapters, updated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    S-blockedStatus: Blocked on another featureT-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions