Recently, the ability to have a main function without the standard library has been removed, since that the previous implementation never works. However, having a main function in no_std targets would improve programming experience and allows language features to be more unified. This Pre-RFC issues a solution, aiming to have a working implementation of customized program "actual" entry point with the main function.
Changing The Way How The main Function Is Called
Currently, the program's "actual" entry point, which usually has symbol name main, for example, on Linux and most other Unix platforms, is generated by rustc_codegen_ssa. The generated entry point has knowledge about the Rust main function and the start language item, then things like the main function pointer are passed to the start implementation. Since the compiler cannot generate a customized entry point, this implementation is blocking us to implement the start language item manually, and make it work in a desired environment, which is unknown to the compiler.
This Pre-RFC changes the way the Rust main function is called. After the change, we will have an "actual" program entry point entirely implemented in the standard library. We will introduce a new function generated by the compiler, which has symbol name rust_main. The generated function, in Rust, would look like:
// type RealTermination = (return value of the main function);
// extern "Rust" fn main(); (the Rust main function)
#[unsafe(no_mangle)]
unsafe extern "Rust" fn rust_main() -> &'static mut dyn Termination {
static RET: SyncUnsafeCell<MaybeUninit<RealTermination>> = /* ... */
unsafe {
(*RET.get()).as_ptr_mut().write(main());
(&mut *(*RET.get()).as_ptr_mut()) as &mut dyn Termination
}
}
To ensure safety, the rust_main function is limited only to be invoked once on program startup.
The new implementation of the start language item will not have a fixed signature. However, the #[start] attribute will only work as a flag that indicates its existence and does conditional compilation, to avoid linkage errors when we are not compiling a bin target. For example, on Linux, we can:
#[start]
#[unsafe(no_mangle)]
unsafe extern "C" fn main(argc: c_int, arga: *const *const c_char) -> c_int {
unsafe extern "Rust" {
fn rust_main() -> &'static mut dyn Termination;
}
unsafe {
rust_main();
// handle termination...
}
0
}
For dropping the returned Termination, we may change &'static mut dyn Termination on the signature to &'static mut ManuallyDrop<dyn Termination>.