Proposal
Problem statement
Add the repeat_n<T>(value: T, n: usize) -> RepeatN<T> function and iterator type to iter.
This exists as itertools::repeat_n, but can easily be uplifted to core+std, like how iter::repeat_with has obviated itertools::repeat_call.
(This is a function, not a trait method, so doesn't have the resolution issues that make this hard for things like intersperse.)
Motivation, use-cases
The obvious alternative here is iter::repeat(value).take(n). But there's two gotchas with that approach:
It has to always clone value, never move it
If it's a string, for example, that buffer can never be reused (other than in specializations) since it needs to always keep the value around for the potentially-next iteration. (And even if it somehow knew it was the last element needed, it still can't move it in non-consuming calls as it'd make a follow-up .next() call UB, and the Drop would double-free.)
Vec has the secret from_elem for vec! to use to overcome this problem, as well as a bunch of extra internal stuff like ExtendElement for other places that want this functionality.
But other containers don't have this. For example,
iter::repeat(my_string).take(10).collect::<VecDeque<String>>()
will do 10 clones (instead of the 9 that are really needed) and there's no nice way to avoid that.
Even VecDeque::resize didn't bother doing better here, as it just calls .resize_with(n, || value.clone()), which will also do the extra clone.
Thus one would need to do some long dance like
let mut v = VecDeque::with_capacity(10);
v.extend(iter::repeat(&my_string).cloned().take(9));
v.push_back(my_string);
and that's enough of a pain that it's almost certainly not happening.
It would be nice to be able to just do iter::repeat_n(my_string, n).collect() and have that work great in containers, rather than needing them all to add from_elem-like methods or recreate extend specialization infrastructure for all of them like Vec is doing.
It's not ExactSizeIterator.
While that does have a perfect size_hint, iter::Repeat<_>: !ExactSizeIterator (and must not be) and thus the Take<…> isn't either:
iter::repeat(1).take(10).len();
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=74ea40842f56f87224a4bb2411967ec6
| iter::repeat(1).take(10).len();
| ^^^ method cannot be called on `std::iter::Take<std::iter::Repeat<{integer}>>` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`std::iter::Repeat<{integer}>: ExactSizeIterator`
which is required by `std::iter::Take<std::iter::Repeat<{integer}>>: ExactSizeIterator`
That could be made to work, but the "obvious" way would need both an InfiniteIterator trait and some #[marker] traits to express the bound, which combined make a pretty big hammer.
Whereas RepeatN<_> is non-infinite -- limited by the usize -- so is naturally ExactSizeIterator. Thus even for things like repeat(0).take(n) where buffer reuse is irrelevant, this is still useful. (Such as in -> impl ExactSizeIterator<Item = …> APIs.)
Solution sketches
So long as cloneing in this isn't considered a side effect, it can be made to work nicely. That should be uncontroversial, as the existing repeat doesn't either -- for example iter::repeat(NoisyClone).nth(10) only clones once: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b8455030a83615d6b4a1b92c25d65571
A straight-forward and natural implementation even allows LLVM to unify the "clone" and "move the last time" branches for trivial-to-clone types even if they're not Copy -- with no special needs_drop checks or specialization: https://rust.godbolt.org/z/drjzsdfoe
Links and related work
As mentioned, https://docs.rs/itertools/latest/itertools/fn.repeat_n.html
The suffix _n for "counted" versions of methods is used in Elements of Programming and common in C++ functions like copy_n.
What happens now?
This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.
Proposal
Problem statement
Add the
repeat_n<T>(value: T, n: usize) -> RepeatN<T>function and iterator type toiter.This exists as
itertools::repeat_n, but can easily be uplifted to core+std, like howiter::repeat_withhas obviateditertools::repeat_call.(This is a function, not a trait method, so doesn't have the resolution issues that make this hard for things like
intersperse.)Motivation, use-cases
The obvious alternative here is
iter::repeat(value).take(n). But there's two gotchas with that approach:It has to always clone
value, never move itIf it's a string, for example, that buffer can never be reused (other than in specializations) since it needs to always keep the value around for the potentially-next iteration. (And even if it somehow knew it was the last element needed, it still can't move it in non-consuming calls as it'd make a follow-up
.next()call UB, and the Drop would double-free.)Vechas the secretfrom_elemforvec!to use to overcome this problem, as well as a bunch of extra internal stuff likeExtendElementfor other places that want this functionality.But other containers don't have this. For example,
will do 10 clones (instead of the 9 that are really needed) and there's no nice way to avoid that.
Even
VecDeque::resizedidn't bother doing better here, as it just calls.resize_with(n, || value.clone()), which will also do the extra clone.Thus one would need to do some long dance like
and that's enough of a pain that it's almost certainly not happening.
It would be nice to be able to just do
iter::repeat_n(my_string, n).collect()and have that work great in containers, rather than needing them all to addfrom_elem-like methods or recreate extend specialization infrastructure for all of them like Vec is doing.It's not
ExactSizeIterator.While that does have a perfect
size_hint,iter::Repeat<_>: !ExactSizeIterator(and must not be) and thus theTake<…>isn't either:https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=74ea40842f56f87224a4bb2411967ec6
That could be made to work, but the "obvious" way would need both an
InfiniteIteratortrait and some#[marker]traits to express the bound, which combined make a pretty big hammer.Whereas
RepeatN<_>is non-infinite -- limited by theusize-- so is naturallyExactSizeIterator. Thus even for things likerepeat(0).take(n)where buffer reuse is irrelevant, this is still useful. (Such as in-> impl ExactSizeIterator<Item = …>APIs.)Solution sketches
So long as
cloneing in this isn't considered a side effect, it can be made to work nicely. That should be uncontroversial, as the existingrepeatdoesn't either -- for exampleiter::repeat(NoisyClone).nth(10)only clones once: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b8455030a83615d6b4a1b92c25d65571A straight-forward and natural implementation even allows LLVM to unify the "clone" and "move the last time" branches for trivial-to-clone types even if they're not
Copy-- with no specialneeds_dropchecks or specialization: https://rust.godbolt.org/z/drjzsdfoeLinks and related work
As mentioned, https://docs.rs/itertools/latest/itertools/fn.repeat_n.html
The suffix
_nfor "counted" versions of methods is used in Elements of Programming and common in C++ functions likecopy_n.What happens now?
This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.