Skip to content

Commit 60fba60

Browse files
Rollup merge of rust-lang#42167 - scottmcm:iter-stepby-sizehint, r=alexcrichton
Override size_hint and propagate ExactSizeIterator for iter::StepBy Generally useful, but also a prerequisite for moving a bunch of unit tests off `Range*::step_by`. A small non-breaking subset of rust-lang#42110 (which I closed). Includes two small documentation changes @ivandardi requested on that PR. r? @alexcrichton
2 parents caa2327 + fcb3a71 commit 60fba60

File tree

4 files changed

+96
-1
lines changed

4 files changed

+96
-1
lines changed

Diff for: src/doc/unstable-book/src/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
- [io](library-features/io.md)
155155
- [ip](library-features/ip.md)
156156
- [iter_rfind](library-features/iter-rfind.md)
157+
- [iterator_step_by](library-features/iterator-step-by.md)
157158
- [libstd_io_internals](library-features/libstd-io-internals.md)
158159
- [libstd_sys_internals](library-features/libstd-sys-internals.md)
159160
- [libstd_thread_internals](library-features/libstd-thread-internals.md)

Diff for: src/libcore/iter/mod.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ impl<I> Iterator for Cycle<I> where I: Clone + Iterator {
520520
#[unstable(feature = "fused", issue = "35602")]
521521
impl<I> FusedIterator for Cycle<I> where I: Clone + Iterator {}
522522

523-
/// An iterator that steps by n elements every iteration.
523+
/// An adapter for stepping iterators by a custom amount.
524524
///
525525
/// This `struct` is created by the [`step_by`] method on [`Iterator`]. See
526526
/// its documentation for more.
@@ -553,8 +553,27 @@ impl<I> Iterator for StepBy<I> where I: Iterator {
553553
self.iter.nth(self.step)
554554
}
555555
}
556+
557+
#[inline]
558+
fn size_hint(&self) -> (usize, Option<usize>) {
559+
let inner_hint = self.iter.size_hint();
560+
561+
if self.first_take {
562+
let f = |n| if n == 0 { 0 } else { 1 + (n-1)/(self.step+1) };
563+
(f(inner_hint.0), inner_hint.1.map(f))
564+
} else {
565+
let f = |n| n / (self.step+1);
566+
(f(inner_hint.0), inner_hint.1.map(f))
567+
}
568+
}
556569
}
557570

571+
// StepBy can only make the iterator shorter, so the len will still fit.
572+
#[unstable(feature = "iterator_step_by",
573+
reason = "unstable replacement of Range::step_by",
574+
issue = "27741")]
575+
impl<I> ExactSizeIterator for StepBy<I> where I: ExactSizeIterator {}
576+
558577
/// An iterator that strings two iterators together.
559578
///
560579
/// This `struct` is created by the [`chain`] method on [`Iterator`]. See its

Diff for: src/libcore/tests/iter.rs

+73
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,79 @@ fn test_iterator_step_by_zero() {
171171
it.next();
172172
}
173173

174+
#[test]
175+
fn test_iterator_step_by_size_hint() {
176+
struct StubSizeHint(usize, Option<usize>);
177+
impl Iterator for StubSizeHint {
178+
type Item = ();
179+
fn next(&mut self) -> Option<()> {
180+
self.0 -= 1;
181+
if let Some(ref mut upper) = self.1 {
182+
*upper -= 1;
183+
}
184+
Some(())
185+
}
186+
fn size_hint(&self) -> (usize, Option<usize>) {
187+
(self.0, self.1)
188+
}
189+
}
190+
191+
// The two checks in each case are needed because the logic
192+
// is different before the first call to `next()`.
193+
194+
let mut it = StubSizeHint(10, Some(10)).step_by(1);
195+
assert_eq!(it.size_hint(), (10, Some(10)));
196+
it.next();
197+
assert_eq!(it.size_hint(), (9, Some(9)));
198+
199+
// exact multiple
200+
let mut it = StubSizeHint(10, Some(10)).step_by(3);
201+
assert_eq!(it.size_hint(), (4, Some(4)));
202+
it.next();
203+
assert_eq!(it.size_hint(), (3, Some(3)));
204+
205+
// larger base range, but not enough to get another element
206+
let mut it = StubSizeHint(12, Some(12)).step_by(3);
207+
assert_eq!(it.size_hint(), (4, Some(4)));
208+
it.next();
209+
assert_eq!(it.size_hint(), (3, Some(3)));
210+
211+
// smaller base range, so fewer resulting elements
212+
let mut it = StubSizeHint(9, Some(9)).step_by(3);
213+
assert_eq!(it.size_hint(), (3, Some(3)));
214+
it.next();
215+
assert_eq!(it.size_hint(), (2, Some(2)));
216+
217+
// infinite upper bound
218+
let mut it = StubSizeHint(usize::MAX, None).step_by(1);
219+
assert_eq!(it.size_hint(), (usize::MAX, None));
220+
it.next();
221+
assert_eq!(it.size_hint(), (usize::MAX-1, None));
222+
223+
// still infinite with larger step
224+
let mut it = StubSizeHint(7, None).step_by(3);
225+
assert_eq!(it.size_hint(), (3, None));
226+
it.next();
227+
assert_eq!(it.size_hint(), (2, None));
228+
229+
// propagates ExactSizeIterator
230+
let a = [1,2,3,4,5];
231+
let it = a.iter().step_by(2);
232+
assert_eq!(it.len(), 3);
233+
234+
// Cannot be TrustedLen as a step greater than one makes an iterator
235+
// with (usize::MAX, None) no longer meet the safety requirements
236+
trait TrustedLenCheck { fn test(self) -> bool; }
237+
impl<T:Iterator> TrustedLenCheck for T {
238+
default fn test(self) -> bool { false }
239+
}
240+
impl<T:TrustedLen> TrustedLenCheck for T {
241+
fn test(self) -> bool { true }
242+
}
243+
assert!(TrustedLenCheck::test(a.iter()));
244+
assert!(!TrustedLenCheck::test(a.iter().step_by(1)));
245+
}
246+
174247
#[test]
175248
fn test_filter_map() {
176249
let it = (0..).step_by(1).take(10)

Diff for: src/libcore/tests/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@
3232
#![feature(slice_patterns)]
3333
#![feature(sort_internals)]
3434
#![feature(sort_unstable)]
35+
#![feature(specialization)]
3536
#![feature(step_by)]
3637
#![feature(step_trait)]
3738
#![feature(test)]
39+
#![feature(trusted_len)]
3840
#![feature(try_from)]
3941
#![feature(unicode)]
4042
#![feature(unique)]

0 commit comments

Comments
 (0)