Avoiding deep nesting


#1

I’m only starting with Rust so please point me to all sorts of reading material if there’s anything relevant.

Anyway, currently I experiment with the language and the main goal is to make things happen, not to apply high grade of engineering. What I’ve noticed is that my code tends to be deeply nested. Result and Option needs constant branching to handle all possible cases and it ends up something like this:

let a = Something.do_something();
match a {
  Ok(result) => {
    let b = result.get_value();
    match b {
      Some(value) => {
        println!("{}", value);
      },
      None => {
        println!("{}", "Value not found");
      }
    }
  },
  Err(e) => {
    println!("{}", e);
  }
}

That’s a total nonsense, but I hope you get the idea.

And the longer the chain of transformations the more I drown in exception handling code. For example, I’m writing a little program that fetches a web page and inlines CSS. I didn’t even get to the inlining yet and I’m already 8 levels deep.

How do you deal with this? What do you do to make your code less nested? Is there a way to minimize exception handling code or at least make the important part of the code more prominent (or exception handling less noticeable)?


#2

Depends on the use case, but try!(), unwrap(), expect(), map(), and if let are all rather useful for handling options and results.


#3

Those let you focus on “the happy path” but it has a limited utility because it basically just lets you ignore the other branch. You’re back to what you have in languages with nil and exceptions. Just let it blow when something’s not right. Might me a viable solution when you’re putting something together quickly but it doesn’t improve reliability of the resulting software, does it?


#4

try!() lets you propagate errors, which isn’t ignoring the other branch since you have to handle the error eventually. unwrap/expect are panicky and shouldn’t be used that often, agreed. But the other functions on Option/Result (e.g. map or unwrap_or) are quite useful in making code around them concise.

You need to structure your code to make this work, though. E.g. the nested match above should really be in a function using try!(), and then call the function and match on Err to print the error once.


#5

Also:

    let a = do_something()
        .and_then(|res| res.get_value()
            .ok_or_else(|| "value not found".into()));
    match a {
        Ok(res) => println!("{}", res),
        Err(e) => println!("{}", e)
    }

#6

Also something like this:

fn foo(something: Something) -> Result<A, B> {
    let result = try!(something.do_something());
    let value = try!(result.get_value().ok_or_else(|| "value not found".into()));

    Ok(value)
}

fn main() {
    match foo(Something) {
        Ok(res) => println!("{}", res),
        Err(e) => println!("Error: {}", e)
    }
}

#7

See also the Rust book chapter on Error handling


#8

I had the same question and discussed it on Reddit https://www.reddit.com/r/rust/comments/3u5u1y/alternatives_to_christmas_trees_of_match/