Reputation: 4817
According to The Rust Reference,
If a
main
function 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 1
status when main()
returns Err(<value>)
?
the error message displayed when main()
returns Err(<value>)
is always of the form Error: <value>
?
Notes:
I want some sort of documented guarantee rather than an empirical explanation. This is why I added #language-lawyer
tag.
This question is not about When should I use ()
and when should I use Result<(), 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).
Upvotes: 28
Views: 4911
Reputation: 16495
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).
Upvotes: 10
Reputation: 222198
The behavior of different return values from main
is defined by the std::process::Termination
trait:
trait std::process::Termination
A 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_SUCCESS
to indicate a successful execution. In case of a failure,libc::EXIT_FAILURE
is 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()
}
}
Upvotes: 26