Error management reading args

#1

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>()?;

0 Likes

#2

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 })
}
0 Likes

#3

Is there a way of returning the error message given by parse() instead of a self-written message?

0 Likes

#4

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).

0 Likes

#5

Okay, so it’s a better practice using the solution you provided?

0 Likes

#6

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

#7

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