'In Rust, what happens if main function returns Err?
According to The Rust Reference,
If a
mainfunction is present, (snip), and its return type must be one of the following:
()
Result<(), E> where E: Error
but it doesn't say what happens when main() returns (), Ok(()) or Err(<value>).
As far as I tested,
() |
Ok(()) |
Err(<value>) |
|
|---|---|---|---|
| Exit Status | 0 | 0 | 1 |
| Additional Behavior | - | - | Error: <value> is printed to stderr |
Are these behaviors defined, explicitly explained or guaranteed in some documentation? In particular, can I assume
a program always exits with
1status whenmain()returnsErr(<value>)?the error message displayed when
main()returnsErr(<value>)is always of the formError: <value>?
Notes:
I want some sort of documented guarantee rather than an empirical explanation. This is why I added
#language-lawyertag.This question is not about When should I use
()and when should I useResult<(), E>? or such. One can find answers (or at least hints or criteria) to such questions in many documentations or tutorials, as you know.
Updates:
Termination trait is finally stabilized in Rust 1.61.0 (source).
Solution 1:[1]
The behavior of different return values from main is defined by the std::process::Termination trait:
trait std::process::TerminationA trait for implementing arbitrary return types in the main function.
This trait is documented to return libc::EXIT_SUCCESS on success and libc::EXIT_FAILURE on error.
The default implementations are returning
libc::EXIT_SUCCESSto indicate a successful execution. In case of a failure,libc::EXIT_FAILUREis returned.
But those values aren't guaranteed to be 0 and 1 on non-POSIX systems.
As for printing the error message, Termination requires E: Debug and does print the Debug impl to stderr, but I don't believe it's guaranteed to stay exactly the same.
impl<E: fmt::Debug> Termination for Result<!, E> {
fn report(self) -> ExitCode {
let Err(err) = self;
eprintln!("Error: {:?}", err);
ExitCode::FAILURE.report()
}
}
Solution 2:[2]
This behavior is controlled by the std::process::Termination Trait, which was added in RFC 1937. In particular, the "hidden" lang_start() function - which calls main() - looks roughly like:
fn lang_start<T: Termination>(
main: fn() -> T,
argc: isize,
argv: *const *const u8
) -> !
that is, main() can return any T: Termination. There are implementations of Termination in std for !, (), std::process:ExitCode, and some Result<T, E>-variants where E: Debug. This is why you can return (), Ok(()) and others from main().
To your question, language lawyer mode: The exact behavior of any program that relies on Termination is not strictly specified by the language itself. It is part of the std implementation, not part of the language reference. That means that the same program might behave differently when compiled with different versions of the compiler (binding to different std versions). The exact behavior for printing errors in the Err case is documented but not specified. As RFC 1937 explicitly looks for POSIX-like behavior, you can be reasonably assured that the program will not behave in wildly suprising ways (e.g. exit with status 0 in an Err-case).
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Dogbert |
| Solution 2 | ynn |
