DennyHiu
DennyHiu

Reputation: 6110

How to deal with Result<T,E> inside the closure function in Rust?

I have a nested object in mongodb that I want to deserialize/decode back into a struct. Here's the structure :

pub struct ItemUnit {
    /// unit's name like PCS, DOZEN, PACK, etc
    pub name: String,

    /// denote this unit value
    pub multiplier: f64,

    /// item's price in this unit
    pub price: Option<f64>,
}

pub struct Item {
    /// item's name
    pub name: String,

    /// available unit types available for this item, in an array format
    pub units: Vec<ItemUnit>,
}

as you can see the struct is nested and units is an array (Vec<ItemUnit>).

As Rust have a culture of "not letting you go with error possibility", this quickly becomes tricky. In Rust mongodb driver, you have to deserialize/decode them back from 'bson' document into "native type" such as String, f64, and so on. Each deserialize operation returns Result<> since there's a possibility of error in it.

I have a problem with nested object with an array of another object in it:

// doc is the data from a single `Item`
// since `get_array` might throw a ValueAccess error, I map it into my own custom Error type
// so `units_bson` is now a `Vec<mongodb::bson::Bson>` type
let units_bson = doc.get_array("units").map_err(Self::map_mongodb_err)?;

// in the next step, I need to decode the mongodb::bson::Bson for each element in this array
/// here's I use map() with closure
let units: Vec<ItemUnit> = units_bson.iter().map(|u| { 
   
   // get_str() returns Result<T,E> but since ItemUnit only accepts a string not Result<> type
   // I had to handle the error possibilities here in the closure/map function
   // but the ? operator only available on a function that returns `Result` or `Option` 
   //  (or another type that implements `Try`)
   let name = u.as_document().unwrap().get_str("name").map_err(Self::map_mongodb_err);
   return ItemUnit { name, multiplier, p_buy };

}).collect();

So my question is how do you catch an error inside a closure?

Or is there any other workaround like try-catch block that can also catch any error inside a closure ?

Upvotes: 2

Views: 980

Answers (1)

Silvio Mayolo
Silvio Mayolo

Reputation: 70337

The first thing you need to do is propagate the error into the Vec.

units_bson.iter().map(|u| {
  // This question-mark returns from the closure, not the whole function.
  let name = code_and_stuff.map_err(...)?;
  // We're returning Result values now, so wrap the value in Ok.
  Ok(ItemUnit { name, ... })
});

At this point, we have an iterator over values of type Result<ItemUnit, SomeError>. If we were to call collect now, naively we might expect to get a Vec<Result<ItemUnit, SomeError>>, and indeed that is one correct result. But we can also request a Result<Vec<ItemUnit>, SomeError>, because Result<A, E> has a FromIterator instance for this exact use case.

units_bson.iter().map(|u| {
  // This question-mark returns from the closure, not the whole function.
  let name = code_and_stuff.map_err(...)?;
  // We're returning Result values now, so wrap the value in Ok.
  Ok(ItemUnit { name, ... })
}).collect::<Result<Vec<_>, _>>()?;

Then, with one more question mark after our collect call, we propagate the error to our outer function's result.

Welcome to the wonderful world of Rust error values. I promise this sort of thing becomes second nature the more you do it; you won't even notice yourself adding the little question marks in a handful of places.

Upvotes: 2

Related Questions