Skip to content

Commit 2261154

Browse files
committed
core: add Peekable::next_if_map
1 parent 6b3ae3f commit 2261154

File tree

3 files changed

+189
-0
lines changed

3 files changed

+189
-0
lines changed

library/core/src/iter/adapters/peekable.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,108 @@ impl<I: Iterator> Peekable<I> {
317317
{
318318
self.next_if(|next| next == expected)
319319
}
320+
321+
/// Consumes the next value of this iterator and applies a function `f` on it,
322+
/// returning the result if the closure returns `Ok`.
323+
///
324+
/// Otherwise if the closure returns `Err` the value is put back for the next iteration.
325+
///
326+
/// The content of the `Err` variant is typically the original value of the closure,
327+
/// but this is not required. If a different value is returned,
328+
/// the next `peek()` or `next()` call will result in this new value.
329+
/// This is similar to modifying the output of `peek_mut()`.
330+
///
331+
/// If the closure panics, the next value will always be consumed and dropped
332+
/// even if the panic is caught, because the closure never returned an `Err` value to put back.
333+
///
334+
/// # Examples
335+
///
336+
/// Parse the leading decimal number from an iterator of characters.
337+
/// ```
338+
/// #![feature(peekable_next_if_map)]
339+
/// let mut iter = "125 GOTO 10".chars().peekable();
340+
/// let mut line_num = 0_u32;
341+
/// while let Some(digit) = iter.next_if_map(|c| c.to_digit(10).ok_or(c)) {
342+
/// line_num = line_num * 10 + digit;
343+
/// }
344+
/// assert_eq!(line_num, 125);
345+
/// assert_eq!(iter.collect::<String>(), " GOTO 10");
346+
/// ```
347+
///
348+
/// Matching custom types.
349+
/// ```
350+
/// #![feature(peekable_next_if_map)]
351+
///
352+
/// #[derive(Debug, PartialEq, Eq)]
353+
/// enum Node {
354+
/// Comment(String),
355+
/// Red(String),
356+
/// Green(String),
357+
/// Blue(String),
358+
/// }
359+
///
360+
/// /// Combines all consecutive `Comment` nodes into a single one.
361+
/// fn combine_comments(nodes: Vec<Node>) -> Vec<Node> {
362+
/// let mut result = Vec::with_capacity(nodes.len());
363+
/// let mut iter = nodes.into_iter().peekable();
364+
/// let mut comment_text = None::<String>;
365+
/// loop {
366+
/// // Typically the closure in .next_if_map() matches on the input,
367+
/// // extracts the desired pattern into an `Ok`,
368+
/// // and puts the rest into an `Err`.
369+
/// while let Some(text) = iter.next_if_map(|node| match node {
370+
/// Node::Comment(text) => Ok(text),
371+
/// other => Err(other),
372+
/// }) {
373+
/// comment_text.get_or_insert_default().push_str(&text);
374+
/// }
375+
///
376+
/// if let Some(text) = comment_text.take() {
377+
/// result.push(Node::Comment(text));
378+
/// }
379+
/// if let Some(node) = iter.next() {
380+
/// result.push(node);
381+
/// } else {
382+
/// break;
383+
/// }
384+
/// }
385+
/// result
386+
/// }
387+
///# assert_eq!( // hiding the test to avoid cluttering the documentation.
388+
///# combine_comments(vec![
389+
///# Node::Comment("The".to_owned()),
390+
///# Node::Comment("Quick".to_owned()),
391+
///# Node::Comment("Brown".to_owned()),
392+
///# Node::Red("Fox".to_owned()),
393+
///# Node::Green("Jumped".to_owned()),
394+
///# Node::Comment("Over".to_owned()),
395+
///# Node::Blue("The".to_owned()),
396+
///# Node::Comment("Lazy".to_owned()),
397+
///# Node::Comment("Dog".to_owned()),
398+
///# ]),
399+
///# vec![
400+
///# Node::Comment("TheQuickBrown".to_owned()),
401+
///# Node::Red("Fox".to_owned()),
402+
///# Node::Green("Jumped".to_owned()),
403+
///# Node::Comment("Over".to_owned()),
404+
///# Node::Blue("The".to_owned()),
405+
///# Node::Comment("LazyDog".to_owned()),
406+
///# ],
407+
///# )
408+
/// ```
409+
#[unstable(feature = "peekable_next_if_map", issue = "143702")]
410+
pub fn next_if_map<R>(&mut self, f: impl FnOnce(I::Item) -> Result<R, I::Item>) -> Option<R> {
411+
let unpeek = if let Some(item) = self.next() {
412+
match f(item) {
413+
Ok(result) => return Some(result),
414+
Err(item) => Some(item),
415+
}
416+
} else {
417+
None
418+
};
419+
self.peeked = Some(unpeek);
420+
None
421+
}
320422
}
321423

322424
#[unstable(feature = "trusted_len", issue = "37572")]

library/coretests/tests/iter/adapters/peekable.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,89 @@ fn test_peekable_non_fused() {
271271
assert_eq!(iter.peek(), None);
272272
assert_eq!(iter.next_back(), None);
273273
}
274+
275+
#[test]
276+
fn test_peekable_next_if_map_mutation() {
277+
fn collatz((mut num, mut len): (u64, u32)) -> Result<u32, (u64, u32)> {
278+
let jump = num.trailing_zeros();
279+
num >>= jump;
280+
len += jump;
281+
if num == 1 { Ok(len) } else { Err((3 * num + 1, len + 1)) }
282+
}
283+
284+
let mut iter = once((3, 0)).peekable();
285+
assert_eq!(iter.peek(), Some(&(3, 0)));
286+
assert_eq!(iter.next_if_map(collatz), None);
287+
assert_eq!(iter.peek(), Some(&(10, 1)));
288+
assert_eq!(iter.next_if_map(collatz), None);
289+
assert_eq!(iter.peek(), Some(&(16, 3)));
290+
assert_eq!(iter.next_if_map(collatz), Some(7));
291+
assert_eq!(iter.peek(), None);
292+
assert_eq!(iter.next_if_map(collatz), None);
293+
}
294+
295+
#[test]
296+
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
297+
fn test_peekable_next_if_map_panic() {
298+
use core::cell::Cell;
299+
use std::panic::{AssertUnwindSafe, catch_unwind};
300+
301+
struct BitsetOnDrop<'a> {
302+
value: u32,
303+
cell: &'a Cell<u32>,
304+
}
305+
impl<'a> Drop for BitsetOnDrop<'a> {
306+
fn drop(&mut self) {
307+
self.cell.update(|v| v | self.value);
308+
}
309+
}
310+
311+
let cell = &Cell::new(0);
312+
let mut it = [
313+
BitsetOnDrop { value: 1, cell },
314+
BitsetOnDrop { value: 2, cell },
315+
BitsetOnDrop { value: 4, cell },
316+
BitsetOnDrop { value: 8, cell },
317+
]
318+
.into_iter()
319+
.peekable();
320+
321+
// sanity check, .peek() won't consume the value, .next() will transfer ownership.
322+
let item = it.peek().unwrap();
323+
assert_eq!(item.value, 1);
324+
assert_eq!(cell.get(), 0);
325+
let item = it.next().unwrap();
326+
assert_eq!(item.value, 1);
327+
assert_eq!(cell.get(), 0);
328+
drop(item);
329+
assert_eq!(cell.get(), 1);
330+
331+
// next_if_map returning Ok should transfer the value out.
332+
let item = it.next_if_map(Ok).unwrap();
333+
assert_eq!(item.value, 2);
334+
assert_eq!(cell.get(), 1);
335+
drop(item);
336+
assert_eq!(cell.get(), 3);
337+
338+
// next_if_map returning Err should not drop anything.
339+
assert_eq!(it.next_if_map::<()>(Err), None);
340+
assert_eq!(cell.get(), 3);
341+
assert_eq!(it.peek().unwrap().value, 4);
342+
assert_eq!(cell.get(), 3);
343+
344+
// next_if_map panicking should consume and drop the item.
345+
let result = catch_unwind({
346+
let mut it = AssertUnwindSafe(&mut it);
347+
move || it.next_if_map::<()>(|_| panic!())
348+
});
349+
assert!(result.is_err());
350+
assert_eq!(cell.get(), 7);
351+
assert_eq!(it.next().unwrap().value, 8);
352+
assert_eq!(cell.get(), 15);
353+
assert!(it.peek().is_none());
354+
355+
// next_if_map should *not* execute the closure if the iterator is exhausted.
356+
assert!(it.next_if_map::<()>(|_| panic!()).is_none());
357+
assert!(it.peek().is_none());
358+
assert_eq!(cell.get(), 15);
359+
}

library/coretests/tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
#![feature(next_index)]
7979
#![feature(numfmt)]
8080
#![feature(pattern)]
81+
#![feature(peekable_next_if_map)]
8182
#![feature(pointer_is_aligned_to)]
8283
#![feature(portable_simd)]
8384
#![feature(ptr_metadata)]

0 commit comments

Comments
 (0)