Tracking issue for #[start]
, which indicates that a function should be used as the entry point, overriding the "start" language item. In general this forgoes a bit of runtime setup that's normally run before and after main.
I believe the semantics of this today are that the compiler will generate a function with the symbol main
which calls the #[start]
function, if present, in an executable. This skips the #[lang = "start"]
implementation, if any, in an upstream library (the standard library provides this to set up the first catch_panic
among a few other minor things).
The signature for this function is also fn(isize, *const *const u8) -> isize
, where the isize
may no longer be "the most correct". Additionally, the *const *const u8
may not be the most appropriate option for Windows (although it works). I'm not 100% sure what "the best" signature on Windows is.
On Windows the executable entry point does not take any arguments. Currently we let the CRT act as the executable entry point which then calls our Rust entry wrapper which invokes the start function which is either #[lang = "start"]
which then invokes the user's main function pointer provided to it or a user provided #[start]
function. Which executable entry point the linker decides to use depends on the /SUBSYSTEM
and which main function it can find (https://msdn.microsoft.com/en-us/library/f9t8842e.aspx). All information provided to the main function by the CRT can be obtained through alternative means. Note that if we eventually provide an option for /SUBSYSTEM:Windows
that main function takes a very different set of (useless) arguments than the traditional main (https://msdn.microsoft.com/en-us/library/windows/desktop/ms633559%28v=vs.85%29.aspx).
html5ever uses in an ugly hack that overrides the main
function used by cargo test
in order to generate thousands of tests dynamically. (Tests with the same code but parameterized on (input, expected result)
.)
This would be better solved by some way to override the test harness used by cargo test
(Is there an issue/RFC for that already?)
@SimonSapin the use case for that with Cargo should in theory be harness = false
, but I'm curious how that interacts with #[start]
?
The #[start]
trick doesn’t work anymore, but it looked like this: https://github.com/servo/html5ever/commit/df8e749e697afd3398ef411a0bcba250d20a2697
Is there a tracking issue for harness = false
?
Oh that's already implemented today, if a test target is listed as harness = false
then Cargo just won't pass --test
when compiling it and expects it to be a binary.
(this may be a bit off-topic from #[start]
though so feel free to ping me on IRC)
The current signature for the lang item is
fn lang_start(main: *const u8, argc: isize, argv: *const *const u8) -> isize {
which is called by a generated main function. Instead, the signatures of both should be arbitrary and the symbols translate to main
directly. This allows the main
function to be platform dependent. A pointer to the user's main function can be obtained via an intrinsic.
Note that on windows it really shouldn't always be main
. If the user sets the subsystem to windows instead of console, then the CRT expects to find WinMain
which results in a linker error because it wasn't defined.
https://github.com/rust-lang/rust/issues/20064 suggests that the signature here is wrong, we should consider this before making this feature stable.
Just to clarify, it's not just a question of what signature lang_start
should have; rustc currently generates C entry points (main
) that only work "by accident" (because on 32-bit platforms isize
= c_int
and on 64-bit the calling conventions happen to work out). On my Mac, I get:
define i64 @main(i64, i8**) unnamed_addr {
Of course this would be an easy fix.
Just don't stabilize this until consideration is taken for subsystems, which change the entry point completely from main
to WinMain
and are really important for Rust to support if it wants to be used in the Windows world (several people have spoken to me and said this is one of the issues getting in the way of them using Rust in production on Windows)
Entry point name is irrelevant for windows apps actually. Ability to specify subsystem is one of important things to create application, because most of them are gui with "windows" subsystem.
Pinging this thread since it seems inactive. I wanted to express interest in this feature being stabilized. I was really excited when I discovered that you _could_ replace the entry point in Rust, and then real bummed when I found out that you could only do it on nightly.
It's also necessary to write #![no_std] programs.
I would expect #[start]
to pass the /ENTRY
argument to link.exe
, but it apparently doesn't. Although it's worth noting that the function signature for an entry point on Windows is different anyways, so one would need a different #[start]
function there.
It's also necessary to write #![no_std] programs.
Speaking of which… is there any reason for this? We could make a version of Termination
for libcore and remove all of the system-specific stuff from the shim. We'd also want some form of env::Args
for libcore, but I'd argue that this is reasonable, especially if it's very bare-bones.
Personally, instead of offering #[start]
, I think that it makes more sense to be able to prune down the existing start
shim by removing some of its guarantees. For example, aborting on panics, disallowing multithreading, and disabling stack protection would be enough.
@glandium The binary entry point is completely different from #[start]
. Unless you don't want to link to the CRT at all, you'll probably want the binary entry point to remain the mainCRTStartup
provided by the CRT, otherwise the C RunTime won't be initialized!
@clarcharr Adding some form of env::Args
to libcore is a bad idea, as on Windows it requires calling system functions and heap allocating which is firmly in the realm of libstd. On the other hand, because it is only a system function, Windows users don't need to ask libcore/libstd for the args and can just go through winapi themselves. Unix users would still need to get argc/argv somehow...
I've just been using #[no_main]
in my no_std
programs. This completely eliminates the default entry point logic.
I just define my main function with #[no_mangle]
and have it called by my initialization code.
@retep998 Oh, I didn't know that. I think that in that case, it makes sense to simply have a MainArgs
opaque struct which encapsulates argc
and argv
or nothing if they're not available.
That was initially the idea but I didn't realise how windows did things.
I've just been using
#[no_main]
in myno_std
programs. This completely eliminates the default entry point logic.
Indeed, it surprisingly works on linux, mac and even windows, with a #[no_mangle]
, pub extern "C" fn main(...) -> isize
. (edit: maybe actually i32
?)
#[no_mangle]
is not type safe so I don't consider it a proper user facing way to set the entry point. It also doesn't require unsafe which makes it not obvious that it's extremely dangerous to get the type signature wrong. This is "safe" and segfaults:
#![no_main]
#[no_mangle]
pub fn main(args: Vec<String>) {
for arg in args {
println!("{}", arg);
}
}
The current signature for #[start]
functions isn't great in embedded contexts either. There's nothing to pass arguments to a Game Boy Advance game or read a return value—the only way execution is going to end is if the player switches the game off.
The current signature for
#[start]
functions isn't great in embedded contexts either. There's nothing to pass arguments to a Game Boy Advance game or read a return value—the only way execution is going to end is if the player switches the game off.
I wouldn't write it off so easily - it could provide a neat way to inject "kernel" arguments through interfaces like semihosting, which could prove very useful for doing automated testing of embedded software. You could always just main(0, &[core::ptr::null()] as *const *const u8);
if you don't have a use for them.
True, but even if you do want the entry point to have arguments, there's no reason to force it to have those exact argument types. On the other hand, it is useful to diagnose when the user gets the type wrong by accident. Perhaps having a #[start]
of the "wrong" type should be a lint instead of a hard error?
@CJKay I don't understand your point at all. No Rust code calls to main
on the GBA, nor does Rust have any useful place to return to, so the signature should be main() -> !
. That's not necessarily correct for all contexts, that's just what it should be for GBA. It probably varies by target, but we could probably narrow it to a handful of signatures to select from or just not limit the signature at all and leave it up to the user reading their platform's docs.
@comex Seems reasonable.
@Lokathor I don't know the impact for GBA development, but for bare-metal Cortex-M style development it could prove useful to retain the ability for the runtime to send arguments to and return values from the main function.
But you said "if there's no arguments to main then write main(0, &[core::ptr::null()] as *const *const u8);
", which is Rust code. And no Rust code runs before or after main
on GBA. So your suggestion is nonsense.
That is why I said that each platform should just be allowed to just use a platform appropriate signature of its own devising.
To be as absolutely clear as possible: one signature for all platforms is clearly the wrong decision and would be an improper design to eventually stabilize
Most helpful comment
The current signature for
#[start]
functions isn't great in embedded contexts either. There's nothing to pass arguments to a Game Boy Advance game or read a return value—the only way execution is going to end is if the player switches the game off.