Reputation: 4187
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 Result
s or Option
s.
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
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