0

I want to reduce the boilerplate code when handling errors when multiple lines are following each other. I have the following code, working, but I want to know if it is possible to reduce it:

let mut sto = match Storage::new() {
    Ok(v) => v,
    Err(e) => {
        // if needed the error message could be the same as in the next
        // bloc, but I want to stop the process here
        error!("Failed to open the connection.");
        return Err(e);
    },
};

// If Ok(), do not care about
if let Err(e) = sto.set("key", item) {
    error!("Failed to save the item.");
    return Err(e);
}

Note: the error! macro is a logger macro.

Would it be possible to have something like this ?

if let Err(e) = Storage::new().MAGICAL_FUNC().set("key", item) {
    error!("Failed to save the item.");
    return Err(e);
}

My searches:

  • unwrap as MAGICAL_FUNC cause a panic, so it is not recoverable.
  • Using and_then with a closure will prevent if let from "catching" the Err of the set function in the closure.
  • map_err will not "unwrap" Ok(). So a new line should be added to "unwrap".

Edit: precise error! macro behaviour

➡️Solution: accepted answer + comment of Masklinn

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
Taknok
  • 717
  • 7
  • 16
  • What does this `error!` macro do? – trent Sep 23 '20 at 23:03
  • trentcl: It is a logger macro, I update my question – Taknok Sep 23 '20 at 23:18
  • Is it the `or_else` combinator - https://doc.rust-lang.org/stable/std/result/enum.Result.html#method.or_else - the thing you want? – Cerberus Sep 24 '20 at 02:09
  • Cerberus: `or_else` return a Result (so an Ok()) and I have to "unwrap" it. An without "unwrap" line is already heavy: `Storage::new().or_else(|e| {error!("oups"); return Err(e)}).set("key", item).or_else(|e| {error!("My second oups"); return Err(e)});` – Taknok Sep 24 '20 at 09:22

1 Answers1

2

You can use the ? operator to make your error-handling code more concise. Example:

struct Storage;

enum Error {
    ConnectionFailure,
    SaveFailure,
}

impl Storage {
    fn new() -> Result<Self, Error> {
        Ok(Storage)
        // but could possible return
        // Err(Error::ConnectionFailure)
    }
    fn set<T>(&self, key: &'static str, item: T) -> Result<(), Error> {
        Ok(())
        // but could possible return
        // Err(Error::SaveFailure)
    }
}

fn save_item<T>(key: &'static str, item: T) -> Result<(), Error> {
    let storage = Storage::new()?;
    storage.set(key, item)?;
    Ok(())
}

fn main() {
    match save_item("key", 123) {
        Err(Error::ConnectionFailure) => {
            // handle connection failure error
        },
        Err(Error::SaveFailure) => {
            // handle save failure error
        },
        _ => {
            // everything went fine catch-all
        }
    }
}

playground

See also:

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
  • That should do the trick, but I was hopping to have a solution without another function and handle the error in the same function (here `main` handles the error while `save_item` do the "work"). If nothing else pop up. I will accept this answer. – Taknok Sep 23 '20 at 23:26
  • 1
    There... is no additional function though? `?` desugars to `match val { Ok(v) => v, Err(e) => return Err(e) }`, so you can just `Storage::new()?.set("key", item)?`, @pretzelhammer is just providing a complete example as you did not provide a working program to work with. Incidentally you can `map_err` before the `?` in order to log the error (though that's somewhat odd since the caller would also do so themselves) or manipulate it (which would probably be better). – Masklinn Sep 24 '20 at 05:39
  • You can also look at the answers to [this question](https://stackoverflow.com/questions/55755552/what-is-the-rust-equivalent-to-a-try-catch-statement) – Jmb Sep 24 '20 at 06:55
  • Masklinn: I didn't know that `?` can be used in the middle of a line. I thought it has to be at the end of an statement. This is what I was searching. Thank you. – Taknok Sep 24 '20 at 09:25