rusterror-handlingprogram-entry-point

How to return a string as an error from main()?


Been trying to write a function just to do this.

fn main() -> Result<i32, i32> {
    let clargs: Vec<String> = env::args().collect();

    // validate command line arguments
    if clargs.len() != 3 {
        // clargs[0] is the name of the executable
        print_usage(&clargs[0]);
        return Err(1);
    }

    return Ok(0);
}

fn print_usage(exe_name: &String) {
    eprintln!("Usage: {exe_name} <word_file> <hash_sha1>");
}

I also tried this.

fn main() -> Result<(), std::io::Error> {
    let clargs: Vec<String> = env::args().collect();

    // validate command line arguments
    if clargs.len() != 3 {
        // clargs[0] is the name of the shacker1 executable
        print_usage(&clargs[0]);
        return Err("error: invalid number of arguments.");
    }

    return Ok(());
}

fn print_usage(exe_name: &String) {
    eprintln!("Usage: {exe_name} <word_file> <hash_sha1>");
}

But I can't figure out how to transform a string in to an error. Can't find it in the docs. Can't find examples.

How does one return anything like what I'm doing here?


Solution

  • There are a couple possible ways to go about returning a specific error code from a Rust program. One is to return something that implements the Termination trait. That can be ExitCode, which represents an exit status; a Result<T, E> where T implements Termination and E implements Debug; or one of a few other things.

    So, the simplest way to simply return a status code is this:

    use std::env;
    use std::process::ExitCode;
    
    fn main() -> ExitCode {
        let clargs: Vec<String> = env::args().collect();
    
        // validate command line arguments
        if clargs.len() != 3 {
            // clargs[0] is the name of the executable
            print_usage(&clargs[0]);
            return 2.into();
        }
    
        0.into()
    }
    
    fn print_usage(exe_name: &String) {
        eprintln!("Usage: {exe_name} <word_file> <hash_sha1>");
    }
    

    ExitCode implements From<u8>, so you can simply call .into on a u8 and that will work. Note that this gives you the most flexibility, because it allows you to return any unsuccessful status you want.

    You can also do something like this (only main is shown):

    fn main() -> Result<(), &'static str> {
        let clargs: Vec<String> = env::args().collect();
    
        // validate command line arguments
        if clargs.len() != 3 {
            // clargs[0] is the name of the executable
            print_usage(&clargs[0]);
            return Err("invalid number of arguments");
        }
    
        Ok(())
    }
    

    That works because &'static str implements Debug. Note that this will not give you as much control over the exit status, which may or may not be a problem.

    Finally, you can simply call std::process::exit. This can be done anywhere, but this shows how you might do it if you want (only main and do_main shown):

    fn do_main() -> i32 {
        let clargs: Vec<String> = env::args().collect();
    
        // validate command line arguments
        if clargs.len() != 3 {
            // clargs[0] is the name of the executable
            print_usage(&clargs[0]);
            return 2;
        }
    
        0
    }
    
    fn main() {
        std::process::exit(do_main());
    }
    

    Note that i32 doesn't implement Termination, so you can't use Result<i32, i32> as the return type. You could use Result<ExitCode, i32>, but that would print the error i32, which you probably wouldn't want, so simply returning ExitCode would be more along the lines of what you're going for.