[Pre-RFC] no_entry: Portable specification of -nostartfiles

When developing certain types of embedded, kernel, firmware, or other system targets, projects need to supply specific linker arguments, notably -nostartfiles to omit the standard C start function (such as _start) and associated code.

Right now, this requires creating a .cargo/config file, and specifying target-specific non-portable linker arguments. I'd like to create a portable way to do this, inline with the project code.

This goes hand in hand with #![no_main], but goes further than that, allowing writing your own entry point for a binary.

Rough proposal

Add a new top-level attribute, no_entry. When present, Rust will omit its own entry-point code, and will pass any necessary target-specific arguments to the toolchain to omit the target's entry-point code (such as -nostartfiles) .

Binaries built with this attribute set will typically not link unless the user supplies their own entry-point symbol.

Libraries cannot use this attribute; attempting to do so will produce a compile-time error stating that no_entry only applies to binaries.

Future extensions

I'd like to provide an #[entry] attribute to portably specify the entry-point symbol. The symbol itself may still be non-portable, but having #[entry] would allow using exclusively Rust cfg to handle portability, rather than also needing linker arguments. This would also potentially make it possible for Rust library crates to provide entry points. (This differs from #[start], which provides a semi-portable entry point that still has Rust code run before it.)

I'd also like to add an attribute to pass -nodefaultlibs to the linker. Unlike no_entry, this would typically require no_std on many targets, as std often depends on the C library.

However, I don't want to combine either of those with no_entry, as I'd like to keep this proposal as simple and straightforward as possible.

Next steps

If this sounds reasonable, I'm happy to write it up as an RFC. I don't expect it to require substantial effort to implement; we should be able to have a reasonable default for each linker flavor, and then a way for targets to override that if they need extra logic.

2 Likes

Naming bikeshed time!

To recap, on GNU-ish platforms, a Rust binary starts up like this:

  1. the ELF entry point, named start or _start (part of crt0, disabled with -nostartfiles), calls
  2. the C main (the thing you can disable with #[no_main]), which calls
  3. the Rust start function, either a function annotated with #[start] or std::rt::lang_start, which calls
  4. the Rust main function (the thing you can override with #[main]).

This is... really confusing. And unfortunately, it's mostly too late to do anything about it. As an exception, #[main] and #[no_main] are both unstable, so we could at least change the attribute names to avoid the inconsistency.

Anyway, you're proposing to add #[no_entry] and potentially #[entry] to disable function #1. On one hand, it's true that this is the entry point, and existing tooling at least sometimes refers to it as such - e.g. --entry option to ld, or ENTRY() in linker scripts. On the other hand... the symbol name is start, and the effect of #[no_entry] would be to pass -nostartfiles.

...Now that I've written it out, it does seem like #[entry] is the best option. I can't think of an alternative that wouldn't be even more confusing. But I hope we can clearly document that there are two starts and two mains.

3 Likes

You don't actually need to mess with .cargo/config for this, it can be done with just the target json. I added -nostdlib (which implies -nostartfiles) to the pre-link-args section and it works as expected.

1 Like

Target json files are unstable and nighty only, so that doesn't help.

#[main] is unstable. #![no_main] is stable and works just fine on a stable compiler. The following compiles and runs on 1.35:

#![no_main]

#[no_mangle] pub extern "C" fn main() -> i32 {
    println!("Hello, world!");
    0
}

Also, thanks for the detailed analysis on naming!

Oops, my mistake.

I just posted this as an RFC: https://github.com/rust-lang/rfcs/pull/2735