Error management reading args

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