Skip to content

Commit e200252

Browse files
authored
Rollup merge of rust-lang#72310 - jyn514:peekable-next-if, r=dtolnay
Add Peekable::next_if Prior art: `rust_analyzer` uses [`Parser::eat`](https://github.com/rust-analyzer/rust-analyzer/blob/50f4ae798b7c54d417ee88455b87fd0477473150/crates/ra_parser/src/parser.rs#L94), which is `next_if` specialized to `|y| self.next_if(|x| x == y)`. Basically every other parser I've run into in Rust has an equivalent of `Parser::eat`; see for example - [cranelift](https://github.com/bytecodealliance/wasmtime/blob/94190d57244b26baf36629c88104b0ba516510cf/cranelift/reader/src/parser.rs#L498) - [rcc](https://github.com/jyn514/rcc/blob/a8159c3904a0c950fbba817bf9109023fad69033/src/parse/mod.rs#L231) - [crunch](https://github.com/Kixiron/crunch-lang/blob/8521874fab8a7d62bfa7dea8bd1da94b63e31be8/crates/crunch-parser/src/parser/mod.rs#L213-L241) Possible extensions: A specialization of `next_if` to using `Eq::eq`. The only difficulty here is the naming - maybe `next_if_eq`? Alternatives: - Instead of `func: impl FnOnce(&I::Item) -> bool`, use `func: impl FnOnce(I::Item) -> Option<I::Item>`. This has the advantage that `func` can move the value if necessary, but means that there is no guarantee `func` will return the same value it was given. - Instead of `fn next_if(...) -> Option<I::Item>`, use `fn next_if(...) -> bool`. This makes the common case of `iter.next_if(f).is_some()` easier, but makes the unusual case impossible. Bikeshedding on naming: - `next_if` could be renamed to `consume_if` (to match `eat`, but a little more formally) - `next_if_eq` could be renamed to `consume`. This is more concise but less self-explanatory if you haven't written a lot of parsers. - Both of the above, but with `consume` replaced by `eat`.
2 parents acfc558 + 822ad87 commit e200252

File tree

3 files changed

+88
-0
lines changed

3 files changed

+88
-0
lines changed

src/libcore/iter/adapters/mod.rs

+63
Original file line numberDiff line numberDiff line change
@@ -1619,6 +1619,69 @@ impl<I: Iterator> Peekable<I> {
16191619
let iter = &mut self.iter;
16201620
self.peeked.get_or_insert_with(|| iter.next()).as_ref()
16211621
}
1622+
1623+
/// Consume the next value of this iterator if a condition is true.
1624+
///
1625+
/// If `func` returns `true` for the next value of this iterator, consume and return it.
1626+
/// Otherwise, return `None`.
1627+
///
1628+
/// # Examples
1629+
/// Consume a number if it's equal to 0.
1630+
/// ```
1631+
/// #![feature(peekable_next_if)]
1632+
/// let mut iter = (0..5).peekable();
1633+
/// // The first item of the iterator is 0; consume it.
1634+
/// assert_eq!(iter.next_if(|&x| x == 0), Some(0));
1635+
/// // The next item returned is now 1, so `consume` will return `false`.
1636+
/// assert_eq!(iter.next_if(|&x| x == 0), None);
1637+
/// // `next_if` saves the value of the next item if it was not equal to `expected`.
1638+
/// assert_eq!(iter.next(), Some(1));
1639+
/// ```
1640+
///
1641+
/// Consume any number less than 10.
1642+
/// ```
1643+
/// #![feature(peekable_next_if)]
1644+
/// let mut iter = (1..20).peekable();
1645+
/// // Consume all numbers less than 10
1646+
/// while iter.next_if(|&x| x < 10).is_some() {}
1647+
/// // The next value returned will be 10
1648+
/// assert_eq!(iter.next(), Some(10));
1649+
/// ```
1650+
#[unstable(feature = "peekable_next_if", issue = "72480")]
1651+
pub fn next_if(&mut self, func: impl FnOnce(&I::Item) -> bool) -> Option<I::Item> {
1652+
match self.next() {
1653+
Some(matched) if func(&matched) => Some(matched),
1654+
other => {
1655+
// Since we called `self.next()`, we consumed `self.peeked`.
1656+
assert!(self.peeked.is_none());
1657+
self.peeked = Some(other);
1658+
None
1659+
}
1660+
}
1661+
}
1662+
1663+
/// Consume the next item if it is equal to `expected`.
1664+
///
1665+
/// # Example
1666+
/// Consume a number if it's equal to 0.
1667+
/// ```
1668+
/// #![feature(peekable_next_if)]
1669+
/// let mut iter = (0..5).peekable();
1670+
/// // The first item of the iterator is 0; consume it.
1671+
/// assert_eq!(iter.next_if_eq(&0), Some(0));
1672+
/// // The next item returned is now 1, so `consume` will return `false`.
1673+
/// assert_eq!(iter.next_if_eq(&0), None);
1674+
/// // `next_if_eq` saves the value of the next item if it was not equal to `expected`.
1675+
/// assert_eq!(iter.next(), Some(1));
1676+
/// ```
1677+
#[unstable(feature = "peekable_next_if", issue = "72480")]
1678+
pub fn next_if_eq<R>(&mut self, expected: &R) -> Option<I::Item>
1679+
where
1680+
R: ?Sized,
1681+
I::Item: PartialEq<R>,
1682+
{
1683+
self.next_if(|next| next == expected)
1684+
}
16221685
}
16231686

16241687
/// An iterator that rejects elements while `predicate` returns `true`.

src/libcore/tests/iter.rs

+24
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,30 @@ fn test_iterator_peekable_rfold() {
813813
assert_eq!(i, xs.len());
814814
}
815815

816+
#[test]
817+
fn test_iterator_peekable_next_if_eq() {
818+
// first, try on references
819+
let xs = vec!["Heart", "of", "Gold"];
820+
let mut it = xs.into_iter().peekable();
821+
// try before `peek()`
822+
assert_eq!(it.next_if_eq(&"trillian"), None);
823+
assert_eq!(it.next_if_eq(&"Heart"), Some("Heart"));
824+
// try after peek()
825+
assert_eq!(it.peek(), Some(&"of"));
826+
assert_eq!(it.next_if_eq(&"of"), Some("of"));
827+
assert_eq!(it.next_if_eq(&"zaphod"), None);
828+
// make sure `next()` still behaves
829+
assert_eq!(it.next(), Some("Gold"));
830+
831+
// make sure comparison works for owned values
832+
let xs = vec![String::from("Ludicrous"), "speed".into()];
833+
let mut it = xs.into_iter().peekable();
834+
// make sure basic functionality works
835+
assert_eq!(it.next_if_eq("Ludicrous"), Some("Ludicrous".into()));
836+
assert_eq!(it.next_if_eq("speed"), Some("speed".into()));
837+
assert_eq!(it.next_if_eq(""), None);
838+
}
839+
816840
/// This is an iterator that follows the Iterator contract,
817841
/// but it is not fused. After having returned None once, it will start
818842
/// producing elements if .next() is called again.

src/libcore/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#![feature(leading_trailing_ones)]
4444
#![feature(const_forget)]
4545
#![feature(option_unwrap_none)]
46+
#![feature(peekable_next_if)]
4647

4748
extern crate test;
4849

0 commit comments

Comments
 (0)