Simplifying iterator method implementations

⚓ rust    📅 2025-05-27    👤 surdeus    👁️ 4      

surdeus

Warning

This post was published 33 days ago. The information described in this article may have changed.

On the chance of starting a bike shedding topic, I wanted to ask a simple, but serious question: Why are most methods of Iterator so complex?

I mean, they're not hugely complex, but let's look at find_map() for example:

fn find_map<B, F>(&mut self, f: F) -> Option<B>
where
    Self: Sized,
    F: FnMut(Self::Item) -> Option<B>,
{
    #[inline]
    fn check<T, B>(mut f: impl FnMut(T) -> Option<B>) -> impl FnMut((), T) -> ControlFlow<B> {
        move |(), x| match f(x) {
            Some(x) => ControlFlow::Break(x),
            None => ControlFlow::Continue(()),
        }
    }

    self.try_fold((), check(f)).break_value()
}

I can understand what's going on here. And I understand if you like functional programming, then yes, most utility functions defined on the Iterator trait are expressible as try_fold().

But wouldn't the following be much simpler still?

fn find_map<B, F>(&mut self, f: F) -> Option<B>
where
    Self: Sized,
    F: FnMut(Self::Item) -> Option<B>,
{
    while let Some(x) = self.next() {
        if let Some(x) = match f(x) {
            return Some(x);
        }
    }
    None
}

No messing around with ControlFlow, no internal closure, no call to another method. Just very plain control flow that you can see almost instantly is correct.

I can understand that maybe the code was written that way one day, and no one saw a reason the rewrite it. After all, with zero cost abstractions, it might not really matter.

But does it really not matter? In my debug builds, I can see that try_fold() is a separate function call in my stack traces. Those builds could probably be sped up with a simpler implementation. And maybe it could improve compile times too. Maybe it would be negligible on the whole, but there's a whole bunch of methods that are like this: all(), any(), find(), find_map() itself, position(), rposition(), try_find(), comparison methods, maybe more? Seeing how pervasively iterators are used in Rust, maybe simplifying this code is worthwhile, even if the gains are minuscule?

But it begs the question: Am I missing some reason for why using try_fold() is actually better?

4 posts - 2 participants

Read full topic

🏷️ rust_feed