diff --git a/library/core/src/iter/adapters/cloned.rs b/library/core/src/iter/adapters/cloned.rs index 71a5a4ea831ff..042fd234a7876 100644 --- a/library/core/src/iter/adapters/cloned.rs +++ b/library/core/src/iter/adapters/cloned.rs @@ -60,6 +60,21 @@ where self.it.map(T::clone).fold(init, f) } + #[inline] + fn count(self) -> usize { + self.it.count() + } + + #[inline] + fn last(self) -> Option { + self.it.last().cloned() + } + + #[inline] + fn advance_by(&mut self, n: usize) -> Result<(), usize> { + self.it.advance_by(n) + } + #[doc(hidden)] unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> T where @@ -96,6 +111,11 @@ where { self.it.map(T::clone).rfold(init, f) } + + #[inline] + fn advance_back_by(&mut self, n: usize) -> Result<(), usize> { + self.it.advance_back_by(n) + } } #[stable(feature = "iter_cloned", since = "1.1.0")] diff --git a/library/core/src/iter/adapters/skip.rs b/library/core/src/iter/adapters/skip.rs index ea1da8ba434ed..14417cccb95e0 100644 --- a/library/core/src/iter/adapters/skip.rs +++ b/library/core/src/iter/adapters/skip.rs @@ -33,7 +33,7 @@ where #[inline] fn next(&mut self) -> Option { if unlikely(self.n > 0) { - self.iter.nth(crate::mem::take(&mut self.n) - 1); + let _ = self.iter.advance_by(crate::mem::take(&mut self.n)); } self.iter.next() } diff --git a/library/core/src/iter/traits/double_ended.rs b/library/core/src/iter/traits/double_ended.rs index a6aed6d210beb..ec44e9a9177d3 100644 --- a/library/core/src/iter/traits/double_ended.rs +++ b/library/core/src/iter/traits/double_ended.rs @@ -107,8 +107,14 @@ pub trait DoubleEndedIterator: Iterator { /// outer iterator until it finds an inner iterator that is not empty, which then often /// allows it to return a more accurate `size_hint()` than in its initial state. /// + /// Implementations may elide side-effects such as calling closures or `clone` when they are + /// not necessary to determine by how many steps an iterator can be advanced. For example + /// [`Cloned::advance_back_by`] avoids unnecessary allocations by directly advancing its inner iterator + /// instead. + /// /// [`advance_by`]: Iterator::advance_by /// [`Flatten`]: crate::iter::Flatten + /// [`Cloned::advance_back_by`]: crate::iter::Cloned::advance_back_by /// [`next_back`]: DoubleEndedIterator::next_back /// /// # Examples diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index 9a9a844f41bb4..5a16e03a65677 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -250,7 +250,13 @@ pub trait Iterator { /// can advance its outer iterator until it finds an inner iterator that is not empty, which /// then often allows it to return a more accurate `size_hint()` than in its initial state. /// + /// Implementations may elide side-effects such as calling closures or `clone` when they are + /// not necessary to determine by how many steps an iterator can be advanced. For example + /// [`Cloned::advance_by`] avoids unnecessary allocations by directly advancing its inner iterator + /// instead. + /// /// [`Flatten`]: crate::iter::Flatten + /// [`Cloned::advance_by`]: crate::iter::Cloned::advance_by /// [`next`]: Iterator::next /// /// # Examples diff --git a/library/core/tests/iter/adapters/cloned.rs b/library/core/tests/iter/adapters/cloned.rs index 78babb7feab18..de52cdaf6d206 100644 --- a/library/core/tests/iter/adapters/cloned.rs +++ b/library/core/tests/iter/adapters/cloned.rs @@ -1,4 +1,6 @@ use core::iter::*; +use core::ops::Drop; +use core::sync::atomic::{AtomicUsize, Ordering}; #[test] fn test_cloned() { @@ -34,6 +36,40 @@ fn test_cloned_side_effects() { assert_eq!(count, 2); } +#[test] +fn test_cloned_clone_elision() { + static CLONE_COUNTER: AtomicUsize = AtomicUsize::new(0); + + struct CloneWitness; + + impl Clone for CloneWitness { + fn clone(&self) -> Self { + CloneWitness + } + } + + impl Drop for CloneWitness { + fn drop(&mut self) { + CLONE_COUNTER.fetch_add(1, Ordering::Relaxed); + } + } + + let ary: [CloneWitness; 5] = core::array::from_fn(|_| CloneWitness); + let iter = ary.iter(); + iter.clone().cloned().skip(4).next(); + assert_eq!(CLONE_COUNTER.swap(0, Ordering::Relaxed), 1); + iter.clone().cloned().last(); + assert_eq!(CLONE_COUNTER.swap(0, Ordering::Relaxed), 1); + iter.clone().cloned().nth(4); + assert_eq!(CLONE_COUNTER.swap(0, Ordering::Relaxed), 1); + iter.clone().cloned().take(2).next_back(); + assert_eq!(CLONE_COUNTER.swap(0, Ordering::Relaxed), 1); + iter.clone().cloned().count(); + assert_eq!(CLONE_COUNTER.swap(0, Ordering::Relaxed), 0); + let _ = iter.clone().cloned().advance_by(5); + assert_eq!(CLONE_COUNTER.swap(0, Ordering::Relaxed), 0); +} + #[test] fn test_cloned_try_folds() { let a = [1, 2, 3, 4, 5, 6, 7, 8, 9];