0

I have a type defined as follows:

pub struct State<T: Clone + Eq> {
    pub db: HashSet<T>,
}

And I have a function that looks like the following:

fn check(&self) -> Result<(), Error> {
   self.db.iter().for_each(|elem| elem.check());
   Ok(())
}

So I want to call check function for each of the element in HashSet. I just want to ensure that either all return () or there is some Error thrown. The above throws error as it expect () as output of elem.check(), but it returns Result. What's the proper way to handle this in Rust?

terett
  • 355
  • 1
  • 8

1 Answers1

2

Since (): FromIterator<()>, it's actually quite ergonomic to short-circuit “collect” multiple Result<(), E> into another Result<(), E>:

use std::collections::HashSet;
pub struct State<T: Clone + Eq> {
    pub db: HashSet<T>,
}

/// Checks for equality to 0
fn check(x: i32) -> Result<String, String> {
    if x == 0 {
        Ok("yay, got 0".into())
    } else {
        Err(format!("whoops, got {} != 0", x))
    }
}

impl State<i32> {
    fn check(&self) -> Result<(), String> {
        self.db
            .iter()
            .map(|elem| check(*elem).map(|_| ()))
            .collect()
    }
}

fn main() {
    let state1 = State {
        db: [0, 0, 0].into(),
    };
    let state2 = State {
        db: [0, 1, 2].into(),
    };

    println!("{:?}, {:?}", state1.check(), state2.check());
    // Ok(()), Err("whoops, got 1 != 0")
}

but it's not clear what advantage this offers over the imperative solution, which is much clearer.

impl State<i32> {
    fn check(&self) -> Result<(), String> {
        for x in &self.db {
            check(*x)?;
        }
        Ok(())
    }
}
BallpointBen
  • 9,406
  • 1
  • 32
  • 62
  • One question, if instead I had two fields, `db_1` and `db_2`, then that with `collect` won't work, as it is not consumed, any ideas about it? – terett Mar 30 '23 at 16:45
  • I think you would want to `std::iter::chain` the results of two checks. – BallpointBen Mar 30 '23 at 16:50