vimalloc
vimalloc

Reputation: 4187

How can I reduce the verbosity of matching every call that returns a Result or Option?

I have a case where I need to pull some data out of a TOML file. It's working just fine, but the vast majority of the code is matching Results or Options.

use std::env;
use std::error::Error;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::process::exit;

extern crate toml;

fn main() {
    // Get the path of the config file
    let homedir = match env::home_dir() {
        Some(path) => path,
        None       => {
            println!("Error: Could not find home directory");
            exit(1);
        }
    };
    let mut config_path = PathBuf::from(homedir);
    config_path.push(".relay");
    config_path.set_extension("toml");

    // Open the config file
    let mut file = match File::open(&config_path) {
        Ok(file) => file,
        Err(why) => {
            println!("Error opening {}: {}", config_path.display(),
                                             Error::description(&why));
            exit(1);
        },
    };

    // Read the contents of the config file into memory
    let mut config_str = String::new();
    match file.read_to_string(&mut config_str) {
        Ok(_)    => (),
        Err(why) => {
            println!("Couldn't read {}: {}", config_path.display(),
                                             Error::description(&why));
            exit(1);
        }
    }

    // Parse the toml
    let config: toml::Value = match config_str.parse() {
        Ok(config) => config,
        Err(errs)   => {
            println!("Error Parsing config file:");
            for err in &errs {
                println!("  {}", Error::description(err));
            }
            exit(1);
        }
    };

    let host = match config.lookup("relay.host") {
        Some(host) => match host.as_str() {
            Some(s) => s.to_string(),
            None    => {
                println!("Error: 'host' option is not a valid string");
                exit(1);
            }
        },
        None       => {
            println!("Error: 'host' option not found under [relay] block");
            exit(1);
        }
    };
    println!("{}", host);
}

This seems quite verbose, and as I start pulling more data out of that files it's going to get worse. Is there something I'm missing that would make this cleaner? I know I could replace most of those match statements with unwrap() or expect(), but I would like to print some prettier error messages if something goes wrong, and avoid stuff like:

thread '<main>' panicked at '<actual error message>', ...
note: Run with `RUST_BACKTRACE=1` for a backtrace

Upvotes: 2

Views: 495

Answers (1)

Shepmaster
Shepmaster

Reputation: 431129

You should peruse the error handling section of The Rust Programming Language. The easiest thing to do is to extract the core of the logic to another method and pervasively use Result.

Become very familiar with the methods on Option and Result. Methods like map, map_err, ok_or, are extremely useful. The lynchpin is the ? operator (previously the try! macro).

use std::env;
use std::error::Error;
use std::fs::File;
use std::io::prelude::*;

extern crate toml;

fn inner_main() -> Result<(), Box<Error>> {
    let mut config_path = env::home_dir().ok_or("Could not find home directory")?;

    config_path.push(".relay");
    config_path.set_extension("toml");

    let mut file = File::open(&config_path)
        .map_err(|e| format!("Could not open {}: {}", config_path.display(), e))?;

    let mut config_str = String::new();
    file.read_to_string(&mut config_str)
        .map_err(|e| format!("Couldn't read {}: {}", config_path.display(), e))?;

    let config: toml::Value = config_str
        .parse()
        .map_err(|e| format!("Error parsing config file: {}", e))?;

    let relay = config.get("relay").ok_or("[relay] block not found")?;

    let host = relay
        .get("host")
        .ok_or("'host' option not found under [relay] block")?;

    let host = host.as_str()
        .map(ToString::to_string)
        .ok_or("'host' option is not a valid string")?;

    println!("{}", host);

    Ok(())
}

fn main() {
    inner_main().expect("Error")
}

Check out crates like quick-error that allow you to easily make your own error enumerations.

Upvotes: 5

Related Questions