What is the canonical way to implement is_empty fo

2020-02-07 00:55发布

问题:

I have something that implements std::iter::Iterator and I want to know if there are > 0 elements. What is the standard way to do it? count() > 0 looks too expensive.

I see two candidates: any(|_| true) and nth(0).is_some(), but which one should I pick so a future reader can understand on sight what I'm checking here?

回答1:

I would write iter.next().is_some().

However, you need to be aware that doing this advances the iterator.

fn main() {
    let scores = [1, 2, 3];
    let mut iter = scores.iter();

    println!("{}", iter.next().is_some()); // true
    println!("{}", iter.next().is_some()); // true
    println!("{}", iter.next().is_some()); // true
    println!("{}", iter.next().is_some()); // false
}

In many cases I'd use Peekable:

fn main() {
    let scores = [1, 2, 3];
    let mut iter = scores.iter().peekable();

    println!("{}", iter.peek().is_some()); // true
    println!("{}", iter.peek().is_some()); // true
    println!("{}", iter.peek().is_some()); // true
    println!("{}", iter.peek().is_some()); // true
}

so a future reader can understand on sight

I'd add a method on iterator named is_empty.



回答2:

The canonical way to implement is_empty for Iterator is not to do so. An Iterator is lazy, so by definition cannot know if it has any more elements without iterating.

Logically, it might seem as if it should be simple for an Iterator to know whether it has any more elements, but that can only be the case (without iterating) if its size in known. And indeed, ExactSizeIterator implements is_empty.

To check whether an Iterator is empty, you must attempt to iterate and check if you receive None, however (as mentioned by @Shepmaster), you can only do so without advancing the Iterator when you have a Peekable Iterator. You can peek() at the next element and check if it is_none():

let mut iterator = vec![1,2,3].into_iter().peekable();
println!("is_empty: {}", iterator.peek().is_none());

This becomes more obvious when considering that an to implement Iterator, one only needs to provide a next function that returns either Some or None to indicate whether further elements exist. We could provide an implementation of next that randomly decides whether the next element exists, which makes it clear that we can't know without executing next whether our iterator is empty:

struct RandomThings {}
impl Iterator for RandomThings {
    type Item = bool;
    fn next(&mut self) -> Option<bool> {
        let has_next = rand::random::<bool>();
        if has_next {Some(true)} else {None}
    }
}

So creating a Peekable Iterator and calling .peek().is_none() is actually very explicit, and should be easily understood by future readers. If you are only dealing with iterators of known size, then you can constrain your type further to ExactSizeIterator and use is_empty. If you were to add is_empty for an ordinary Iterator you would only be hiding the fact that its next function must be called to determine whether it is empty.



标签: rust