Search code examples
rusterror-handling

Simple error handling for precondition/argument checking in Rust


Is there a way to write error handling as simply as you can with expect? I often want to validate program inputs and abort the program with an error message if the input is invalid.

Example with expect:

let url = Url::parse("sdfsf")
    .expect("Error parsing URL");
// Output:
// thread 'main' panicked at src\main.rs:41:35:
// Error parsing URL: RelativeUrlWithoutBase

From this answer I gather that I shouldn't use expect for actual user errors, so how can I replicate this simplicity with user-facing Display?

The best I've come up with is unwrap_or_else:

let url = Url::parse("sdfsf")
    .unwrap_or_else(|e| {
      eprintln!("Error parsing URL: {}", e);
      std::process::exit(1);
    });
// Output:
// Error parsing URL: relative URL without a base

I've read that exit doesn't call destructors, so maybe that will cause me problems later.


For more context, here's a complete program that parses arguments with clap and I validate them. Currently it panics on failure, but it should display nice error messages:

use clap::Parser;
use std::collections::HashMap;
use std::process::Command;
use url::Url;

#[derive(Parser, Debug)]
struct Args {
    /// Url with log information to view.
    #[arg(short, long)]
    url: String,
}

fn main() {
    let args = Args::parse();

    let url = Url::parse(args.url.as_str()).expect("Error parsing URL");

    let query: HashMap<String, String> = url.query_pairs().into_owned().collect();

    let server_url = query
        .get("server_url")
        .expect("No server_url specified in url.");

    let revision = query
        .get("revision")
        .expect("No revision specified in url.");

    Command::new("TortoiseProc.exe")
        .arg("/command:log")
        .arg(format!("/startrev:{revision}"))
        .arg(format!("/path:{server_url}"))
        .output()
        .expect("Failed to execute command.");
}

Related but unhelpful questions: Using unwrap_or_else for error handling in Rust, More compact, easier to read Rust error handling (neither actually answer their titular question). How to do error handling in Rust and what are the common pitfalls? doesn't address the "print error" part of my error handling.


Solution

  • The easiest way to do error propagation in Rust is using the ? operator. The anyhow library makes this really convenient by providing a type that wraps Box<dyn Error>, thus allowing propagating different errors into a simple one that just contains the error message. It also has a context() method that adds context to the error. Since main() can return a Result, example of usage could look like this:

    use url::Url;
    use anyhow::Context;
    
    fn main() -> anyhow::Result<()> {
        let url = Url::parse("sdsdf").context("Error parsing URL")?;
        println!("{}", url.scheme());
        Ok(())
    }