Return type of main in user-defined start fn can be generics or not

Is there any way to use main: fn() -> T even with user-defined start fn? When creating a user program in a no_std environment, I defined lang_start in the lib as follows:

#[lang = "start"].
fn lang_start<T>(main: fn() -> T, ....)

However, in the no_std environment, the return type of the main() function does not seem to allow anything other than (), resulting in a compile error.

example:

#![no_std]

use ulib::usys::{exec, Result};
use ulib::Termination;

fn main() -> ! {
    //exec(INIT, &ARGV)
    loop {}
}

error:


  error[E0580]: `main` function has wrong type
   --> src\user\initcode.rs:9:1
    |
  9 | fn main() -> ! {
    | ^^^^^^^^^^^^^^ expected `()`, found `!`
    |
    = note: expected fn pointer `fn()`
               found fn pointer `fn() -> !`

  For more information about this error, try `rustc --explain E0580`.

I am wondering if this rustc code rust/base.rs at master · rust-lang/rust · GitHub is related to this behavior, can it be improved?

There are three ways to have a main function for an executable in rust:

  • #![no_main]: This actually skips the requirement to have a main function rustc knows about and instead requires you to define your own function with a signature compatible with the OS. For example #[no_mangle] fn main(argc: c_int, argv: *const *const c_char) -> c_int { ... }.
  • #[start]: This makes rustc generate the main function and forwards all arguments and return values to the OS. The function annotated with #[start] must have the signature fn(usize, *const *const u8) -> isize.
  • #[lang = "start"] + fn main(): This method is what is used by default when you are using libstd. This makes rustc generate the main function like #[start] except instead of calling the function annotated with #[start], it calls the function annotated with #[lang = "start"] and passes a function pointer to the fn main() defined in the binary. In this case the #[lang = "start"] function must have the signature fn<T: Termination>(main: fn() -> T, argc: usize, argv: *const *const u8) -> isize and the fn main() must return some type implementing Termination. Termination here is a trait annotated with #[lang = "terminated"].

I'm not sure which of the methods you are using, but in case of #[lang = "start"] it will work

2 Likes

thanks! I've tried third method.
The code is as follows. But I've got same sompiler error: error[E508]: main function has wrong type Can't We use the third method with nostd? I need it can be used in nostd for my own OS and its user library written in Rust: GitHub - o8vm/octox: xv6-riscv like OS written in Rust

I tried annotating trait Termination with #[lang = "terminated"], but was told that the lang item "terminated" does not exist.

src/lib.rs

#![no_std]
#![feature(lang_items, never_type)]

#[lang = "start"]
fn lang_start<T: Termination + 'static>(main: fn() -> T, _argc: isize, argv: *const *const u8, _not_use: u8) -> isize {
    main().report()
}

pub trait Termination {
    fn report(self) -> i32;
}

impl Termination for () {
    fn report(self) -> i32 {
        0
    }
}

impl Termination for ! {
    fn report(self) -> i32 {
        0
    }
}

src/main.rs:

#![no_std]

fn main() -> ! {
    loop {}
}

Error:

error[E0580]: `main` function has wrong type
   --> src/user/initcode.rs:9:1
    |
  9 | fn main() -> ! {
    | ^^^^^^^^^^^^^^ expected `()`, found `!`
    |
    = note: expected fn pointer `fn()`
               found fn pointer `fn() -> !`

rustc --version
rustc 1.68.0-nightly (4781233a7 2023-01-16)

There has been a typo in the suggestion, it's "termination". See the source in std for comparison process.rs - source.

2 Likes

@bjorn3 @steffahn Thanks to your help, the compilation was successful.

#[lang = "termination"]
pub trait Termination {
    fn report(self) -> i32;
}
$ cargo build --target riscv64gc-unknown-none-elf
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s