[solved] What's the proper way to bubble up errors from within closures?


#1

Consider this snippet:

pub fn list<E: Executor>(e: E) -> Box<Future<Item = Vec<Volume>, Error = Error>> {
  let mut cmd = Command::new("lvs");
  ...      
  Box::new(e.run_command(cmd).and_then(|out| {
    let items = String::from_utf8_lossy(&out.stdout).trim().split('\n').map(|line| {
      ...

      Volume {
        size: kv.get("LVM2_LV_SIZE").expect("LVM2_LV_SIZE").parse::<u64>().expect("LVM2_LV_SIZE as int"),
      }
    }).collect();

    future::ok(items)
  }))
}

I’m using expect to try the code out now, but that code should really return failures. What would be the proper way to do that, though? The fist obvious problem is that those are within a map() closure, so I cannot just use ? or return to return a failure. The second problem is that I cannot use ? in a function that returns a future either way.

I’m looking for some idiomatic rust code that wouldn’t increase on the overall nesting of statements.


#2

https://doc.rust-lang.org/stable/std/iter/trait.FromIterator.html#implementors

pub fn list<E: Executor>(e: E) -> Box<Future<Item = Vec<Volume>, Error = Error>> {
    let mut cmd = Command::new("lvs");
    // ...      
    Box::new(e.run_command(cmd).and_then(|out| {
        let items = String::from_utf8_lossy(&out.stdout).trim().split('\n').map(|line| {
            // ...
            Ok(Volume {
                size: kv.get("LVM2_LV_SIZE").expect("LVM2_LV_SIZE")
                         .parse::<u64>()?, // 1. When the closure returns `Err` ...
            })
        }).collect::<Result<Vec<_>, _>>(); // 2. ... `collect` will return the `Err` 

        match items {
            Ok(items) => future::ok(items),
            Err(err) => future::err(err),
        }
    }))
}

#3

Thanks! I’ve found this option a few minutes after posting the question—I guess I’ve just used the forum as a rubber duck.

For anyone following me up with this, there’s futures::future::result to turn Result into a future too.


#4

You probably want to avoid the expect on the inner Option there. Instead, turn a None into a Result::Err via Option::ok_or and only create a Volume once the lookup and parse have succeeded.


#5

Result’s FromIterator impl is insanely useful, though in the general case working with arbitrary callback-taking APIs, those APIs do generally need to be written with explicit consideration for errors in mind.

Whenever I write a utility function or algorithm that takes a callback, I always try to consider whether there is a use case for try_ and opt_ variants that short-circuit on failure:

mod arr3 {
    fn from_fn<T, F>(mut f: F) -> [T; 3]
    where F: FnMut(usize) -> T;

    fn try_from_fn<T, E, F>(mut f: F) -> Result<[T; 3], E>
    where F: FnMut(usize) -> Result<T, E>;

    fn opt_from_fn<T, F>(mut f: F) -> Option<[T; 3]>
    where F: FnMut(usize) -> Option<T>;
}

Addendum:

Just now after writing this I just ran into this really hard with Option. Filling an option or chaining an option with a block of code that can produce an Err is a nightmare.

or I could read the thread