Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions library/core/src/iter/traits/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,8 +631,15 @@ pub const trait Iterator {
Zip::new(self, other.into_iter())
}

/// Creates a new iterator which places a copy of `separator` between adjacent
/// items of the original iterator.
/// Creates a new iterator which places a copy of `separator` between items
/// of the original iterator.
///
/// Specifically on fused iterators, it is guaranteed that the new iterator
/// places a copy of `separator` between adjacent `Some(_)` items. However,
/// for non-fused iterators, [`intersperse`] will create a new iterator that
/// is a fused version of the original iterator and place a copy of `separator`
/// between adjacent `Some(_)` items. This behavior for non-fused iterators
/// is subject to change.
///
/// In case `separator` does not implement [`Clone`] or needs to be
/// computed every time, use [`intersperse_with`].
Expand Down Expand Up @@ -663,6 +670,7 @@ pub const trait Iterator {
/// ```
///
/// [`Clone`]: crate::clone::Clone
/// [`intersperse`]: Iterator::intersperse
/// [`intersperse_with`]: Iterator::intersperse_with
#[inline]
#[unstable(feature = "iter_intersperse", issue = "79524")]
Expand All @@ -676,12 +684,19 @@ pub const trait Iterator {
}

/// Creates a new iterator which places an item generated by `separator`
/// between adjacent items of the original iterator.
/// between items of the original iterator.
///
/// The closure will be called exactly once each time an item is placed
/// between two adjacent items from the underlying iterator; specifically,
/// the closure is not called if the underlying iterator yields less than
/// two items and after the last item is yielded.
/// Specifically on fused iterators, it is guaranteed that the new iterator
/// places an item generated by `separator` between adjacent `Some(_)` items.
/// However, for non-fused iterators, [`intersperse_with`] will create a new
/// iterator that is a fused version of the original iterator and place an item
/// generated by `separator` between adjacent `Some(_)` items. This
/// behavior for non-fused iterators is subject to change.
///
/// The `separator` closure will be called exactly once each time an item
/// is placed between two adjacent items from the underlying iterator;
/// specifically, the closure is not called if the underlying iterator yields
/// less than two items and after the last item is yielded.
///
/// If the iterator's item implements [`Clone`], it may be easier to use
/// [`intersperse`].
Expand Down Expand Up @@ -723,6 +738,7 @@ pub const trait Iterator {
/// ```
/// [`Clone`]: crate::clone::Clone
/// [`intersperse`]: Iterator::intersperse
/// [`intersperse_with`]: Iterator::intersperse_with
#[inline]
#[unstable(feature = "iter_intersperse", issue = "79524")]
#[rustc_non_const_trait_method]
Expand Down
200 changes: 200 additions & 0 deletions library/coretests/tests/iter/adapters/intersperse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,203 @@ fn test_try_fold_specialization_intersperse_err() {
iter.try_for_each(|item| if item == "b" { None } else { Some(()) });
assert_eq!(iter.next(), None);
}

// FIXME(iter_intersperse): `intersperse` current behavior may change for
// non-fused iterators, so this test will likely have to
// be adjusted; see PR #152855 and issue #79524
// if `intersperse` doesn't change, remove this FIXME.
#[test]
fn test_non_fused_iterator_intersperse() {
#[derive(Debug)]
struct TestCounter {
counter: usize,
}

/// Given a counter of 0, this produces:
/// `None` -> `Some(2)` -> `None` -> `Some(4)` -> `None` -> `Some(6)`
/// -> and then `None` endlessly
impl Iterator for TestCounter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.counter > 6 {
None
} else if self.counter % 2 == 0 {
self.counter += 1;
None
} else {
self.counter += 1;
Some(self.counter)
}
}
}

let counter = 0;
// places a 2 between `Some(_)` items
let non_fused_iter = TestCounter { counter };
let mut intersperse_iter = non_fused_iter.intersperse(2);
// Since `intersperse` currently transforms the original
// iterator into a fused iterator, this intersperse_iter
// should always have `None`
for _ in 0..counter + 6 {
assert_eq!(intersperse_iter.next(), None);
}

// Extra check to make sure it is `None` after processing 6 items
assert_eq!(intersperse_iter.next(), None);
}

// FIXME(iter_intersperse): `intersperse` current behavior may change for
// non-fused iterators, so this test will likely have to
// be adjusted; see PR #152855 and issue #79524
// if `intersperse` doesn't change, remove this FIXME.
#[test]
fn test_non_fused_iterator_intersperse_2() {
#[derive(Debug)]
struct TestCounter {
counter: usize,
}

// Given a counter of 0, this produces:
// `Some(1)` -> `Some(2)` -> `None` -> `Some(4)` -> `Some(5)` ->
// and then `None` endlessly
impl Iterator for TestCounter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.counter < 5 {
self.counter += 1;
if self.counter % 3 != 0 {
return Some(self.counter);
} else {
return None;
}
}
self.counter += 1;
None
}
}

let counter = 0;
// places a 2 between `Some(_)` items
let non_fused_iter = TestCounter { counter };
let mut intersperse_iter = non_fused_iter.intersperse(2);
// Since `intersperse` currently transforms the original
// iterator into a fused iterator, this interspersed iter
// will be `Some(1)` -> `Some(2)` -> `Some(2)` -> and then
// `None` endlessly
let mut items_processed = 0;
for num in 0..counter + 6 {
if num < 3 {
if num % 2 != 0 {
assert_eq!(intersperse_iter.next(), Some(2));
} else {
items_processed += 1;
assert_eq!(intersperse_iter.next(), Some(items_processed));
}
} else {
assert_eq!(intersperse_iter.next(), None);
}
}

// Extra check to make sure it is `None` after processing 6 items
assert_eq!(intersperse_iter.next(), None);
}

// FIXME(iter_intersperse): `intersperse_with` current behavior may change for
// non-fused iterators, so this test will likely have to
// be adjusted; see PR #152855 and issue #79524
// if `intersperse_with` doesn't change, remove this FIXME.
#[test]
fn test_non_fused_iterator_intersperse_with() {
#[derive(Debug)]
struct TestCounter {
counter: usize,
}

// Given a counter of 0, this produces:
// `None` -> `Some(2)` -> `None` -> `Some(4)` -> `None` -> `Some(6)`
// -> and then `None` endlessly
impl Iterator for TestCounter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.counter > 6 {
None
} else if self.counter % 2 == 0 {
self.counter += 1;
None
} else {
self.counter += 1;
Some(self.counter)
}
}
}

let counter = 0;
let non_fused_iter = TestCounter { counter };
// places a 2 between `Some(_)` items
let mut intersperse_iter = non_fused_iter.intersperse_with(|| 2);
// Since `intersperse` currently transforms the original
// iterator into a fused iterator, this intersperse_iter
// should always have `None`
for _ in 0..counter + 6 {
assert_eq!(intersperse_iter.next(), None);
}

// Extra check to make sure it is `None` after processing 6 items
assert_eq!(intersperse_iter.next(), None);
}

// FIXME(iter_intersperse): `intersperse_with` current behavior may change for
// non-fused iterators, so this test will likely have to
// be adjusted; see PR #152855 and issue #79524
// if `intersperse_with` doesn't change, remove this FIXME.
#[test]
fn test_non_fused_iterator_intersperse_with_2() {
#[derive(Debug)]
struct TestCounter {
counter: usize,
}

// Given a counter of 0, this produces:
// `Some(1)` -> `Some(2)` -> `None` -> `Some(4)` -> `Some(5)` ->
// and then `None` endlessly
impl Iterator for TestCounter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.counter < 5 {
self.counter += 1;
if self.counter % 3 != 0 {
return Some(self.counter);
} else {
return None;
}
}
self.counter += 1;
None
}
}

let counter = 0;
// places a 2 between `Some(_)` items
let non_fused_iter = TestCounter { counter };
let mut intersperse_iter = non_fused_iter.intersperse(2);
// Since `intersperse` currently transforms the original
// iterator into a fused iterator, this interspersed iter
// will be `Some(1)` -> `Some(2)` -> `Some(2)` -> and then
// `None` endlessly
let mut items_processed = 0;
for num in 0..counter + 6 {
if num < 3 {
if num % 2 != 0 {
assert_eq!(intersperse_iter.next(), Some(2));
} else {
items_processed += 1;
assert_eq!(intersperse_iter.next(), Some(items_processed));
}
} else {
assert_eq!(intersperse_iter.next(), None);
}
}

// Extra check to make sure it is `None` after processing 6 items
assert_eq!(intersperse_iter.next(), None);
}
Loading