Jump to content

Inside the no std Underground: How Rust Devs Are Escaping libc

From JOHNWICK

There’s a secret corner of the Rust ecosystem that doesn’t get much love from the mainstream Rustaceans. No async runtimes. No std::fs. No heap allocations unless you make them happen. Welcome to the no_std underground — a strange and exciting place where developers are deliberately giving up the Rust standard library. It’s not masochism. It’s freedom. This is the world of embedded systems, kernels, and bare-metal firmware — where libc is a luxury and every byte counts. What “no_std” Actually Means By default, Rust links against the standard library — the thing that gives you all the nice stuff: Vec, HashMap, println!, threading, and so on. But when you build for environments without an OS — like a microcontroller, WebAssembly runtime, or even a kernel — those things don’t exist. So developers use this:

  1. ![no_std]

use core::panic::PanicInfo;

  1. [panic_handler]

fn panic(_info: &PanicInfo) -> ! {

   loop {}

} fn main() {

   // This won't even compile - no I/O, no heap, no std!

} You’re telling the compiler: “Don’t pull in std. I know what I’m doing. I’ll provide the environment.” Instead, Rust falls back to a smaller crate called core, which includes only the most fundamental things: traits like Copy, Result, and Option. The Real Architecture Behind no_std When you go no_std, your architecture changes completely. Let’s visualize it: +---------------------------------------------+ | Application | | (your Rust logic, data, etc.) | +---------------------------------------------+ | HAL / Board Support | | (GPIO, timers, UART, SPI, I2C, etc.) | +---------------------------------------------+ | Core (no_std) | | (Option, Result, iterators, primitives) | +---------------------------------------------+ | Hardware / OS | +---------------------------------------------+ Instead of relying on std abstractions, you build (or use) Hardware Abstraction Layers (HALs) that provide platform-specific access to low-level hardware. For example, on a Raspberry Pi Pico, you might have: use rp2040_hal::{pac, gpio::Pins, watchdog::Watchdog};


fn init_peripherals() {

   let mut pac = pac::Peripherals::take().unwrap();
   let mut watchdog = Watchdog::new(pac.WATCHDOG);
   let pins = Pins::new(pac.IO_BANK0, pac.PADS_BANK0, &mut pac.RESETS);

} No heap. No threads. Just direct control over the metal. Escaping libc: The Why Rust’s std library is deeply tied to libc — the C standard library that acts as the glue between userland and the OS kernel. But in embedded land, or when you’re building OS kernels, hypervisors, game consoles, or even space firmware, there is no libc. That’s where the no_std revolution started: devs realized they could use Rust without pulling in any legacy baggage. Projects like:

  • TockOS — an embedded operating system written entirely in no_std Rust.
  • Embassy — async executors for microcontrollers.
  • Rust for Linux — portions of the kernel written with zero dependency on libc.

Code Flow: From Reset to Main To understand how deep the rabbit hole goes, here’s the boot flow of a no_std firmware: +--------------------+ | Power On / Reset | +--------------------+

+--------------------+ | Assembly Startup | | - Set stack pointer| | - Init memory | | - Jump to Rust | +--------------------+

+--------------------+ | Rust Entry (_start)| | - Call main() | | - Panic handler | +--------------------+ Here’s what _start might look like in Rust:

  1. [no_mangle]

pub extern "C" fn _start() -> ! {

   unsafe {
       init_memory();
   }
   main();
   loop {}

} No runtime. No OS. Just raw control. Example: A Minimal no_std Blinky Here’s a tiny example from a real-world STM32 board:

  1. ![no_std]
  2. ![no_main]


use cortex_m_rt::entry; use stm32f4xx_hal::{pac, prelude::*};

  1. [entry]

fn main() -> ! {

   let dp = pac::Peripherals::take().unwrap();
   let gpiod = dp.GPIOD.split();
   let mut led = gpiod.pd12.into_push_pull_output();
   loop {
       led.toggle();
       cortex_m::asm::delay(8_000_000);
   }

} No heap. No threads. No std.
But it makes an LED blink — and that’s magic. The Real Reason Devs Love no_std Going no_std isn’t about being fancy. It’s about determinism.
You know exactly what runs, when, and how. There’s no syscall latency, no scheduler surprises, and no background allocator doing who-knows-what. In embedded and real-time systems, predictability beats convenience every single time. And in an age of massive frameworks and layers of abstraction, there’s something refreshing — almost rebellious — about writing code this close to the metal. The Hard Parts Nobody Talks About Of course, it’s not all fun.

  • You can’t use async runtimes easily.
  • Error handling is verbose without std::error::Error.
  • You need to write your own panic handler.
  • Debugging is a nightmare without println!.

Here’s a sample panic handler just to show how much work you do manually:

  1. [panic_handler]

fn panic(info: &core::panic::PanicInfo) -> ! {

   unsafe {
       let uart = 0x4000_1000 as *mut u8;
       for byte in b"Panic!\n" {
           core::ptr::write_volatile(uart, *byte);
       }
   }
   loop {}

} That’s right — you print directly to hardware registers. The Future of no_std Rust The no_std underground isn’t fringe anymore.
Frameworks like Embassy, RTIC, and Drone OS are turning it into a real ecosystem. Even the Rust Embedded Working Group is pushing for better tooling, standardized HALs, and portable device crates. And here’s the crazy part:
Some high-performance systems are starting to go no_std on purpose — even outside embedded.
Why? To escape libc, improve determinism, and reduce binary size in sandboxed runtimes like WebAssembly or unikernels. Final Thoughts When you go no_std, you lose a lot — but you gain something even more valuable: control. Every byte, every cycle, every line of code is yours.
No runtime. No hidden allocations. No abstraction tax. It’s raw, honest, and a little scary — the way programming was always meant to be. no_std isn’t just a technical feature. It’s a mindset.
A quiet rebellion against bloated abstractions — where Rust devs choose to build everything themselves. And maybe, just maybe, that’s where the purest form of Rust still lives.

Read the full article here: https://medium.com/@theopinionatedev/inside-the-no-std-underground-how-rust-devs-are-escaping-libc-d7e43056b1d2