Why have main return Result?

For CLI apps, what's the advantage of having main return a Result<(), Error> as opposed to simply exiting with an exit status? Is it simply more concise?

It has been created to make example code in READMEs shorter. It doesn't give you anything special.

Handling errors and exits code "manually" in main is perfectly fine, and gives you full control, unlike the default Termination implementation that prints only debug version of the error.

What is an exit status?

As far as I recall, a convention in Linux is that returning a 0 the shell that ran the program is an indication of success. Returning anything else is an error with an indication of what happened.

I have no idea of what happens on Windows or other OS.

As such having Rust's main() return Result<(), Error> makes sense.

probably wrong but I regard it as an imitation of Haskell main function which tries to get every tiny behavior of the program typed:

main :: IO ()
main = do
  line <- getLine
  processIt line
  main

Same.

Well, I jumped through hoops and wrote my code to use system::process::exit but now it looks like I can jump through a different loop and use Announcing Rust 1.61.0 | Rust Blog and do it that way, more rusty.

The point is the number returned to the OS can be parsed by the calling shell and react accordingly. This is a great feature to have. Windows can return numbers also, it is part of the world of "c" and as far as I understand it, if there is a main() and we can envoke program from command line, then it very well may called from a script. Not being able to return a number from main() was one part of rust that irked me personally and I am glad a was a decision made to enable it.

3 Likes

I was really happy to see the stabilization of the Termination trait. It seems like a small thing, but for those of us who write plenty of CLI applications and want to use return codes as appropriate, it helped clean up some pretty annoying boilerplate.

For those who haven't screwed this one up (yet): The problem with std::process::exit() is that it is very literal, it just exits -- without giving Drop handlers the chance to fire.

I've been using an ugly pattern where main() exists only to call main2() and terminate with the appropriate exit code, while main2() exists only to ensure that all Drop handlers are run properly.

The Termination trait solves this. :tada::partying_face::champagne:

4 Likes

I made a wrapper macro. I put my programs in a function and call it with a main() that ended up like this. The macro fills in the "prog_name" and args() puts in the parsed clap struct.

fn main() {
    std::process::exit(submain(args(), $prog_name));
}

The submain calls the program function. The way I see it, maybe I am wrong, but when submain() is done, then everything it had is dropped, so the number it returns to main() is the only thing left. So that number is returned to exit() and there is nothing left to drop.

2 Likes

It's also a way to write more concise code, like

fn main() -> Result<(), Error> {
    my_faillible_function()?;
    Ok(())
}
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.