Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions library/core/src/iter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,8 @@ pub use self::traits::FusedIterator;
pub use self::traits::InPlaceIterable;
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::traits::Iterator;
#[unstable(feature = "peekable_iterator", issue = "132973")]
pub use self::traits::PeekableIterator;
#[unstable(issue = "none", feature = "trusted_fused")]
pub use self::traits::TrustedFused;
#[unstable(feature = "trusted_len", issue = "37572")]
Expand Down
3 changes: 3 additions & 0 deletions library/core/src/iter/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod double_ended;
mod exact_size;
mod iterator;
mod marker;
mod peekable;
mod unchecked_iterator;

#[unstable(issue = "none", feature = "inplace_iteration")]
Expand All @@ -12,6 +13,8 @@ pub use self::marker::InPlaceIterable;
pub use self::marker::TrustedFused;
#[unstable(feature = "trusted_step", issue = "85731")]
pub use self::marker::TrustedStep;
#[unstable(feature = "peekable_iterator", issue = "132973")]
pub use self::peekable::PeekableIterator;
pub(crate) use self::unchecked_iterator::UncheckedIterator;
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::{
Expand Down
25 changes: 25 additions & 0 deletions library/core/src/iter/traits/peekable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#[unstable(feature = "peekable_iterator", issue = "132973")]
/// Iterators which inherently support `peek()` without needing to be wrapped by a `Peekable`.
pub trait PeekableIterator: Iterator {
/// executes the closure with an Option containing `None` if the iterator is exhausted or Some(&Self::Item)
fn peek_with<T>(&mut self, func: impl for<'a> FnOnce(Option<&'a Self::Item>) -> T) -> T;

/// executes the closure on the next element without advancing the iterator, or returns None if the iterator is exhausted.
fn peek_map<T>(&mut self, func: impl for<'a> FnOnce(&'a Self::Item) -> T) -> Option<T> {
self.peek_with(|x| x.map(|y| func(y)))
}

/// returns the `next()` element if a predicate holds true
fn next_if(&mut self, func: impl FnOnce(&Self::Item) -> bool) -> Option<Self::Item> {
self.peek_map(func).and_then(|_| self.next())
}

/// move forward and return the `next()` item if it is equal to the expected value
fn next_if_eq<T>(&mut self, expected: &T) -> Option<Self::Item>
where
Self::Item: PartialEq<T>,
T: ?Sized,
{
self.next_if(|x| x == expected)
}
}
3 changes: 2 additions & 1 deletion library/core/src/slice/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ mod macros;
use super::{from_raw_parts, from_raw_parts_mut};
use crate::hint::assert_unchecked;
use crate::iter::{
FusedIterator, TrustedLen, TrustedRandomAccess, TrustedRandomAccessNoCoerce, UncheckedIterator,
FusedIterator, PeekableIterator, TrustedLen, TrustedRandomAccess, TrustedRandomAccessNoCoerce,
UncheckedIterator,
};
use crate::marker::PhantomData;
use crate::mem::{self, SizedTypeProperties};
Expand Down
28 changes: 28 additions & 0 deletions library/core/src/slice/iter/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,34 @@ macro_rules! iterator {
}
}

#[unstable(feature = "peekable_iterator", issue = "132973")]
impl<'a, T> PeekableIterator for $name<'a, T> {
fn peek_with<U>(&mut self, func: impl for<'b> FnOnce(Option<&'b Self::Item>) -> U) -> U {
let ptr = self.ptr;
let end_or_len = self.end_or_len;

// SAFETY: See inner comments.
unsafe {
if T::IS_ZST {
let len = end_or_len.addr();
if len == 0 {
return func(None);
}
} else {
// SAFETY: by type invariant, the `end_or_len` field is always
// non-null for a non-ZST pointee. (This transmute ensures we
// get `!nonnull` metadata on the load of the field.)
if ptr == crate::intrinsics::transmute::<$ptr, NonNull<T>>(end_or_len) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// SAFETY: by type invariant, the `end_or_len` field is always
// non-null for a non-ZST pointee. (This transmute ensures we
// get `!nonnull` metadata on the load of the field.)
if ptr == crate::intrinsics::transmute::<$ptr, NonNull<T>>(end_or_len) {
if ptr.as_ptr() == end_or_len {

Copy link
Contributor Author

@wmstack wmstack Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I was not able to write it this way as Rust determines the types differ in their mutability. In general, this code was based on the next() method generic over both Iter and IterMut

Copy link
Contributor

@tgross35 tgross35 Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly was the error? Seems possible to resolve by adding a $as_ptr metavar that is either as_ptr or as_mut_ptr depending on which one is being implemented (like into_ref).

next is a bit magical, others should try to be simpler if possible. cloning and advancing should be fine, it's cheap.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cloning actually wouldn't work for IterMut since it doesn't implement it. But that actually brings up a good point; the safety comments need to say why it is sound to hand out a&mut T.

return func(None);
}
}
// SAFETY: Now that we know it wasn't empty
// we can give out a reference to it.
let tmp = {ptr}.$into_ref();
func(Some(&tmp))
}
}
}
#[stable(feature = "default_iters", since = "1.70.0")]
impl<T> Default for $name<'_, T> {
/// Creates an empty slice iterator.
Expand Down
Loading