I am attempting to write some small Unix-style command line programs and I am having trouble with error reporting, particularly when the program might suffer both fatal errors (cannot continue after the error is encountered) and non-fatal errors (it is useful to keep going after the first error is encountered, if only to report more errors, but the ultimate process exit status should be unsuccessful).
When all errors are fatal, I can use an outermost skeleton like this:
#[derive(Debug, StructOpt)] struct CommandArgs { ... }
fn main() {
if let Err(e) = inner_main(CommandArgs::from_args()) {
eprintln!("program-name: {}", e);
std::process::exit(1);
}
}
fn inner_main(args: CommandArgs) -> Result<(), Error> {
// in here we can use the ? operator to report fatal errors
}
But when all errors are not fatal, inner_main
needs to do some of the error reporting itself, and then somehow tell main
to exit unsuccessfully without printing further errors, perhaps like this:
#[derive(Debug, StructOpt)] struct CommandArgs { ... }
fn main() {
use std::process::exit;
match inner_main(CommandArgs::from_args()) {
Ok(true) => exit(0),
Ok(false) => exit(1),
Err(e) => {
eprintln!("program-name: {}", e);
exit(1);
}
}
}
fn inner_main(args: CommandArgs) -> Result<bool, Error> {
let mut failed = false;
// use ? to report fatal errors as before
// report non-fatal errors ourselves, then set failed to true
Ok(failed)
}
So far so good. The problem I'm having is with actually reporting the non-fatal errors, because I can't use ?
to do it. Most of the stock Option and Result combinators are also not helpful. I find myself writing things like this:
if let Some(db) = extradata_db {
for (addr, result) in results {
match result {
Err(e) => {
failed = true;
eprintln!("program-name: {}: {}", addr, e);
},
Ok(addr2) => {
out.write_record(&[addr.to_string(),
addr2.to_string()])
.unwrap_or_else(|e| {
failed = true;
eprintln!("program-name: {}: {}",
out_name, e);
});
}
}
}
} else {
for (addr, result) in results {
match result {
Err(e) => {
failed = true;
eprintln!("program-name: {}: {}", addr, e);
},
Ok(addr2) => {
match lookup_extra(db, addr, addr2) {
Err(e) => {
failed = true;
eprintln!("program-name: {}: {}", addr2, e);
},
Ok(record) => {
out.write_record(&record)
.unwrap_or_else(|e| {
failed = true;
eprintln!("program-name: {}: {}",
out_name, e);
});
}
}
}
}
}
}
and I can't help thinking there must be a better way...