Ensuring an iterator yields only one item

Greetings!

Forgive me if there's some obvious answer to this.

Sometimes I find myself with an iterator for which I want to yield exactly one element. I'll often use this pattern:

fn exactly_one_item(iter: impl Iterator<Item=Something>) -> Result<Something, Error> {
    match (iter.next(), iter.next()) {
        (None, _) => Error::None,
        (Some(x), None) => Ok(x),
        (Some(_), Some(_)) => Error::MoreThanOne,
    }

Or, a similar pattern where I match on two successive calls to .next().

Are there other common patterns for this use case? I always feel this is a case of "good, but could be better" whenever I use it, but I can't think of a better pattern.

Thoughts?

1 Like

This is common enough that it’s in the itertools crate, as exactly_one. There is a variant for at most one item (zero allowed, two not).

9 Likes

Interesting. Not the first time I've reinvented something that was already done the same or better by itertools.

Thanks!

Just for fun, expressing the logic with only combinators:

iter.next()
    .ok_or(Error::None)
    .and_then(|f| iter.next().is_none().then_some(f).ok_or(Error::MoreThanOne))

Playground.

1 Like

I saw the same thing in cargo-apk recently: cargo-apk/cargo-apk/src/main.rs at caa806283dc26733ad8232dce1fa4896c566f7b8 · rust-mobile/cargo-apk · GitHub

This pattern can be written in terms of Iterator and Option:

let only_one = iter.take(2).fold(None, |opt, item| opt.xor(Some(item)))

Or as an extension trait: Rust Playground

edit: Here's a version which retains the Error type, if that is important: Rust Playground (The identity transformation err => err can also be unreachable_unchecked() for possible optimizations.)

Be careful not to call next after None is returned, unless the iterator is fused or at least documented not to panic when that happens.

1 Like

Nitpick: A non-fused iterator would be one which sometimes returns Some after None. Fusedness says nothing about panic behavior.

3 Likes

Yeah that one was confusing me. I agree that my original implementation was flawed due to always calling .next() twice, even when not needed. All the solutions proposed here avoid that (that I can tell). But that's because I may end up with an iterator which might return Some(..) at some point after returning None. I can't assume that the iterator I get is fused, or behaves as such.

That being said, barring catastrophic events, are there situations where calling .next() should/would panic? I feel that would be an anti-pattern. Even if a tcp socket closes and later resumes, or a file system is filled but later gets some space freed, or we're reading some kind event queue in a concurrent system.

Even a non-fusing iterator would, I assume, always just continue to yield None. Are there examples of iterator implementations that don't behave this way?

Realistically, not ones you should worry about, no.

It is possible, because it's legal for any Rust evaluation whatsoever to panic, but you are not expected to prepare for that. If someone passes your code a panicking iterator, and it panics, they get to keep both pieces.

Even if a tcp socket closes and later resumes

This should usually be modelled using an iterator over Result, rather than an iterator that panics, but it is a good example of how your design may end up exploring this corner. Good eye.

Even a non-fusing iterator would, I assume, always just continue to yield None . Are there examples of iterator implementations that don't behave this way?

It's not hard to accidentally build one out of from_fn, unless you're careful about that specific behaviour. The example in the docs will return a lot of None values, but in release builds (where integer overflow does not panic), will eventually start returning Some(value) again, initially with large negative numbers.

2 Likes