Early exit when handling error code

Hi

I'm a newbie, so please be kind :slight_smile:
When I want to handle error in C/python, I typically write code like this:

# code: python (non nested)
res = do_something()
if res is None:
   return

res = do_something2(res)
if res is None:
   return

I don't want to have multiple levels of nesting, for example I don't want the code above to be written as:

# python (nested)
res = do_something()
if res is not None:
   res = do_something2(res)
   if res is not None:
      # and so on

The non-nested version is easier to read. In Rust, how do I implement the non-nested version? I've thought of 2 ways, but I don't know which is best or if there's a better version.

// Rust (non nested, Version 1)
let res = do_something();
if res == Err(e) {
   eprintln!("Got error: {}", e);
   return ();
}

let v = res.unwrap();
let res = do_something2(v);
if res == Err(e) {
   eprintln!("Got error: {}", e);
   return ();
}

let v2 = res.unwrap();
// ... and so on

There are 2 things about this code:

  1. the call to unwrap() is misleading. Someone who reads the code may conclude the code may panic, even though it will never since we catch the Err(e) with the if-statement
  2. unwrap() internally checks if res == Err(e), so there is some redundancy in the code. Will the compiler be able to remove the if-statement in unwrap() during compilation, since it's not needed?

One other way to code the non-nested version in Rust is as follows:

// Rust (non nested, Version 2)
let res = match do_something() {
  Err(e) => { eprintln!("Got error: {}", e); return ();},
  Ok(v) => v,
};

let res2 = match do_something2(res) {
  Err(e) => { eprintln!("Got error: {}", e); return ();},
  Ok(v) => v,
};

/// ... and so on

I feel Version 2 is better, but I'd like to hear from you experts !

Thanks so much!

The first version looks a lot of like Go error handling (in my opinion it's disgusting what they do in go :P). If you didn't read about the question mark operator ? you can start here: https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html it should help you achieve what you want :slight_smile:

fn doit() -> Result<etc, etc> {
  do_something2(do_something()?)
}

fn main() {
   if let Err(e) = doit() {
      eprintln!("{}", e);
      std::process::exit(1);
   }
}

Can't we even rewrite this as

do_something().and_then(do_something2)?

Or what was the equivalent of monadic bind?

Thanks for the quick replies! I've heard of the question mark operator, but my understanding is that it returns an error. What of if the function returns nothing, eg

// Rust (non nested, Version 2)
fn myfunction() {
   let res = match do_something() {
     Err(e) => { eprintln!("Got error: {}", e); return ();},
     Ok(v) => v,
   };

/// ... and so on
}
fn do_smth() -> Result<()> {
  do_stuff()?;
  Ok(())
}

Where () is unit operator, an empty tuple, nothing. So in this case you return Result of nothing () and error Err.

1 Like

The correct solution is to change the function to return Result. Don't swallow errors. Let the caller handle them.

1 Like

mhh, I see. Does this just move the problem to the caller then :thinking:? Maybe I'm just being pedantic...

Yes, it moves it to the caller. And the caller may then propagate it too, and if necessary all the way up to main which may display an error and quit.

But if you propagate the error, the caller has also a choice how to handle it, e.g. ignore or try again.

OK. Thanks all for your help! I will try to follow your advice, i.e., return a Result<something>.

1 Like