Rust nostd

What is it ?

I always wondered what the minimal nostd rust code was.

Why leave the stdlib world? It provides so many features. If you don’t know which ones, just take a look at core and imagine that stdlib provides everything that you usually use but is not there.

But if you are in the business of writing something barebone, like a kernel, an embedded component, or the stdlib itself, you don’t have the luxury it provides. So let’s find how it looks like to live in that world.

How is it ?

Stdlib does a lot of things in the background, but what really?

Well, first, it generates a C main wrapper that runs the rust main, so we need to do it ourselves.

Then it provides a panic handler, to handle the panic cases, like when you unwrap an error.

So here it is (let’s call it test.rs)

#![no_std]
#![no_main]

// no_mangle and extern c make sure that this function can be accessed as is by C code
#[no_mangle]
pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize {
    0
}

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    // not easy to have a sane default panic when we have nothing
    loop {}
}

It can be compiled with

# -lc to link with libc, this is mandatory when you have a C main
rustc -C panic=abort -lc test.rs

As you can see, we still link to libc, this means we still have this dependency but we also still have access to some high level functions from C.

Let’s do it again with cargo

Here is the Cargo.toml:

[package]
name = "nostd"
edition = "2021"

[profile.dev]
panic = "abort"

We need to make sure rustc detects the libc dependency and links with it. One way to do it is to add this line in rust code:

extern crate libc;

Let’s call some libc

Since we have kept a dependency on libc, lets make it an advantage and use it.

But don’t forget, in libc everything is unsafe!

#![no_std]
#![no_main]

use libc::printf;
use libc::exit;

#[no_mangle]
pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize {
    unsafe { printf("Hello world\n\0".as_ptr() as *const i8); }

    // Let's trigger a panic to see what happens
    let x : Result<(),()> = Err(());
    x.unwrap();

    0
}

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
   unsafe { printf("Paaaaaniiiic ===\\\\o \n\0".as_ptr() as *const i8); }
   unsafe { exit(1); }
}

This time, we need a dependency on the libc crate that contains everything needed to call libc from rust.

So we directly add this dependency to Cargo.toml, and we don’t need anymore the extern crate libc we had:

[dependencies]
libc = { version = "0.2", default-features = false, features = ["extra_traits"] }

Cool, it panics!


Next time we’ll try to remove this nasty libc dependency.