I'm pretty new to Rust, coming from a background in mostly C. After reading the Rust Book and following along in my editor, then doing most of the Rustlings course, I decided the best way to really learn was basically jumping in and writing some programs for myself.
My current program has a function that takes a String as an argument and parses it to a u64 before performing operations on it. Currently my error handling terminates the program if this is unsuccessful.
Code snippet:
let value: u64 = match value.trim().parse() {
Ok(num) => num,
Err(c) => {
eprintln!("Error: {}", c);
process::exit(1);
}
};
This works but is not ideal. Ideally what I want to happen is that the program will continue processing any additional arguments and just record the failure, then exit later with failure. In C, I would create a global variable for the exit value, initialize it to 0 and just change it to 1 upon failure. I have read about the static keyword and it looks like I could implement this basically the same way with a static variable that is changed upon any failure, but I was wondering if there is any better way, as Rust so far seems to be full of interesting ideas that solve a lot of common C pitfalls more gracefully.
If I'm getting this right, then I could return an error from my function, and if I declared main as fn main() -> Result<(), Box<dyn Error>> then I can put off handling the error until after I have processed all arguments through my other function? Because that's exactly what I want to be able to do.
If you want to keep processing args, you'll have to detect the error, record the fact that it happened, and then continue (as you originally outlined). Globals for such a purpose are less idiomatic in Rust.
What exactly this looks like depends on the context. To modify your given example one way could be:
let mut erred = false;
let value: u64 = match value.trim().parse() {
Ok(num) => num,
Err(c) => {
erred = true;
eprintln!("Error: {}", c);
0 // <- if you're going to continue you have to give `value` a value
}
};
// ...later
if erred {
process::exit(1);
}
If you want to continue without losing the ergonomics of the ? operator, perhaps it makes sense to refactor the handling of each arg:
use core::num::ParseIntError;
use std::env;
use std::ffi::OsString;
fn process_arg(arg: OsString, parsed: &mut Vec<u64>) -> Result<(), ParseIntError> {
let value = arg.to_str().unwrap_or("invalid").trim().parse()?;
// ... do stuff with value ...
parsed.push(value);
Ok(())
}
fn process_args() -> Result<Vec<u64>, String> {
let mut arg_errors = 0;
let mut parsed_args = Vec::new();
// args() panics on non-utf8 arguments, so args_os() is required in order
// to continue from that case as well
for arg in env::args_os().skip(1) {
if process_arg(arg, &mut parsed_args).is_err() {
arg_errors += 1;
}
}
match arg_errors {
0 => Ok(parsed_args),
_ => Err(format!("Failed to parse {} argument(s)", arg_errors)),
}
}
fn main() -> Result<(), String> {
let args = process_args()?;
// do stuff with args...
Ok(())
}
let maybe_value: Result<u32, _> = s.parse();
// and later:
let value = maybe_value?;
// or
if maybe_value.is_err() { … }
// or match, etc.
Result also has convenience methods like and_then, or_else that replace common match boilerplate.
There's .ok() that converts Result to Option, which might be useful if the things you're parsing are optional.
If the things you parse have the same type, you can collect them in Vec<Result<u32, ParseIntError>>> and check them in a loop, or use into_iter().collect::<Result<Vec<u32>, _>() to flip errors inside-out.