Search code examples
rustprogram-entry-pointentry-pointrust-macros

What is the built-in `#[main]` attribute?


I have been using the #[tokio::main] macro in one of my programs. After importing main and using it unqualified, I encountered an unexpected error.

use tokio::main;

#[main]
async fn main() {}
error[E0659]: `main` is ambiguous
 --> src/main.rs:3:3
  |
3 | #[main]
  |   ^^^^ ambiguous name
  |
  = note: ambiguous because of a name conflict with a builtin attribute
  = note: `main` could refer to a built-in attribute

I've been scouring the documentation but I haven't been able to find this built-in #[main] attribute described anywhere. The Rust Reference contains an index of built-in attributes. The index doesn't include #[main], though it does include an attribute named #[no_main].

I did a search of the rustlang/rust repository, and found some code that seems related, but it seems to use a pair of macros named #[start] and #[rustc_main], with no mention of #[main] itself. (Neither of those macros appears to be usable on stable, with #[start] emitting an error that it's unstable, and #[rustc_main] emitting an error that it's only meant for internal use by the compiler.)

My guess from the name would have been that it's meant to mark a different function as an entry-point instead of main, but it also doesn't seem to do that:

#[main]
fn something_else() {
    println!("this does not run");
}

fn main() {
    println!("this runs");
}

Rust Analyzer didn't have much to offer:

a screenshot showing Rust Analyzer's contextual help for the #[main] attribute, devoid of any details

What does the built-in #[main] attribute do, aside from conflicting with my imports? 😉


Solution

  • #[main] is an old, unstable attribute that was mostly removed from the language in 1.53.0. However, the removal missed one line, with the result you see: the attribute had no effect, but it could be used on stable Rust without an error, and conflicted with imported attributes named main. This was a bug, not intended behaviour. It has been fixed as of nightly-2022-02-10 and 1.59.0-beta.8. Your example with use tokio::main; and #[main] can now run without error.

    Before it was removed, the unstable #[main] was used to specify the entry point of a program. Alex Crichton described the behaviour of it and related attributes in a 2016 comment on GitHub:

    Ah yes, we've got three entry points. I.. think this is how they work:

    • First, #[start], the receiver of int argc and char **argv. This is literally the symbol main (or what is called by that symbol generated in the compiler).
    • Next, there's #[lang = "start"]. If no #[start] exists in the crate graph then the compiler generates a main function that calls this. This functions receives argc/argv along with a third argument that is a function pointer to the #[main] function (defined below). Importantly, #[lang = "start"] can be located in a library. For example it's located in the standard library (libstd).
    • Finally, #[main], the main function for an executable. This is passed no arguments and is called by #[lang = "start"] (if it decides to). The standard library uses this to initialize itself and then call the Rust program. This, if not specified, defaults to fn main at the top.

    So to answer your question, this isn't the same as #[start]. To answer your other (possibly not yet asked) question, yes we have too many entry points.