Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an alternative to `all` that returns Result instead of bool?

Tags:

iteration

rust

Consider an example where one needs to iterate over a sequence with all but the output yes/no requires unwrapping somehow.

pub fn interesting(i : u32) -> Result<bool, ()> {
    if i < 42 {
       return Ok(i % 2 == 0);
    }
    Err(todo!())
}

pub fn bar() -> Result<bool, ()> {
    let numbers = vec![1, 2, 3, 4, 5, 100];
    // Check if all numbers are greater than 0
    let all_good = numbers.iter().all(|&x| interesting(x).unwrap_or(false));
    Ok(all_good)
}

I want to avoid using unwrap and use ? instead to propagate the error (if any) to the caller.

like image 622
A. K. Avatar asked Dec 02 '25 01:12

A. K.


2 Answers

You're looking for something like Iterator::try_all(), which doesn't exist, neither in Iterator nor in Itertools. You could use try_fold() to emulate it with fairly obvious code, but that doesn't get you short-circuiting (iteration stopping on the first false). Short-circuiting can be implemented with try_fold() using ControlFlow, but it makes the code inelegant because you can no longer use ?.

If you insist on a "functional" approach making use of iterator combinators, look no further than the find_map() solution by @yshavit. But it still requires some thinking to understand, and calls for an explanatory comment. To actually solve the problem in production code, I humbly suggest that in this case you forgo a functional solution, and write a simple and readable for loop, like this:

pub fn bar() -> Result<bool, ()> {
    let numbers = vec![1, 2, 3, 4, 5, 100];
    for &x in &numbers {
        if !interesting(x)? {
            return Ok(false);
        }
    }
    Ok(true)
}

Playground

like image 75
user4815162342 Avatar answered Dec 04 '25 05:12

user4815162342


You could use find_map, where the f returns an Option<Result<bool>> where:

  • Some is a short-circuited answer: either false or an error
  • None means "this element is matching, so keep looking"

Then, you just need to (a) write a closure to describe that Some(true) doesn't short-circuit, and (b) end it with an unwrap_or to turn a None intoSome(true). That's not quite as convenient as what you may be looking for, but it's not bad:

pub fn bar(numbers: &[u32]) -> Result<bool, ()> {
    numbers.iter().find_map(|&x| match interesting(x) {
        Ok(true) => None,     // element matches, so keep looking
        other => Some(other), // short-circuit a false or an error
    }).unwrap_or(Ok(true))    // no elements were false or error, so it's true
}

See it on Rust Playground

(If you wanted try_any instead of try_all, that would work much the same way, just with both trues flipped to falses.)

like image 42
yshavit Avatar answered Dec 04 '25 05:12

yshavit



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!