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>
.