I just finished reading chapter 12 of the Rust Language Book, and I have a question.
The function that reads the cli args looks like this:
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("Not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config {
query,
filename,
case_sensitive,
})
}
After this chapter I wanted to try out a little bit Error management by doing a simple program that calculates the Fibonacci sequence by reading the length of the sequence to be calculated, from the command line arguments.
To read the arguments I wrote this function:
fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 2 {
return Err("Not enough arguments");
}
let k = match args[1].parse::<i32>() {
Ok(v) => v,
Err(e) => {
return Err("Argument is not a number!")
}
};
Ok(Config {
k
})
}
Is there a way so that I can return Err("Not enough arguments")
and instead of using match
do like:
let k = args[1].parse::<i32>()?;
You can use map_err
:
let k = args[1]
.parse::<i32>()
.map_err(|_| "Argument is not a number!")?;
I think that you probably want something like this:
fn new(args: &[String]) -> Result<Config, &'static str> {
let k = args
.get(1)
.ok_or("Not enough arguments")
.and_then(|arg| arg.parse::<i32>().map_err(|_| "Argument is not a number!"))?;
Ok(Config { k })
}
Is there a way of returning the error message given by parse()
instead of a self-written message?
ParseIntError
implements Display
, so you can use it to create a custom error message, but at this point you will need a String
(or a Cow
) instead of a &'static str
(you could also leak a Box
, but it is not a good practice).
Okay, so it's a better practice using the solution you provided?
TBH, you should return an Error
as error type. If you are able to use an external crate like failure
, you can return something like Result<Config, failure::Error>
and you can return any kind of struct that impl failure::Fail
(if a struct impl std::error::Error
you can generally use into()
.
To make things clearer, here a complete example:
use failure::{Error, Fail};
use std::{env, num::ParseIntError};
#[derive(Debug)]
struct Config {
k: i32,
}
#[derive(Fail, Debug)]
enum ConfigError {
#[fail(display = "Not enough arguments")]
NotEnoughArgs,
#[fail(display = "Argument is not a number! ({})", inner)]
ParseError { inner: ParseIntError },
}
fn new(args: &[String]) -> Result<Config, Error> {
let k = args
.get(1)
.ok_or_else(|| ConfigError::NotEnoughArgs)
.and_then(|arg| {
arg.parse::<i32>()
.map_err(|err| ConfigError::ParseError { inner: err })
})?;
Ok(Config { k })
}
fn main() {
// This allocates, and it has an avoidable cost. But it is useful for our purpose
let args: Vec<String> = env::args().skip(1).collect();
let out = new(&args).unwrap_or_else(|err| {
eprintln!("ERROR: {}", err);
std::process::exit(1);
});
println!("{:?}", out);
}
Sorry if I did not use the playground, but it looks like failure
is not in the top 100 crates anymore.
2 Likes
I just applied it to my program and it works perfectly. I will play a little bit in the next days with this way of managing error.
Thank you very much
1 Like