Log Entry

Why Rust is the Future of Embedded Systems

5 min read

C has dominated embedded systems for decades. But there’s a new language that’s challenging that dominance—and for good reason.

Rust brings memory safety to embedded systems without the overhead of a garbage collector. In a domain where every byte and every cycle matters, that’s revolutionary.

The Problem with C

Don’t get me wrong—C is amazing. It’s fast, minimal, and gives you complete control. But that control comes with a price:

Memory unsafety.

In embedded systems, common C bugs include:

  • Buffer overflows - Writing past array bounds
  • Use-after-free - Accessing memory that’s been freed
  • Null pointer dereferences - The billion-dollar mistake
  • Data races - Concurrent access without synchronization

These aren’t just theoretical problems. They’re the source of most embedded system crashes and security vulnerabilities.

Enter Rust

Rust’s ownership system catches these bugs at compile time. Not at runtime. Not in production. During compilation.

fn main() {
    let data = vec![1, 2, 3];
    let reference = &data[0];
    
    drop(data); // Compiler error: cannot move out of `data` because it is borrowed
    
    println!("{}", reference);
}

This code won’t compile. The compiler knows you’re trying to use freed memory and stops you.

In C, this compiles fine. It crashes at runtime. Maybe. If you’re lucky. Or it corrupts memory silently and crashes later in a completely unrelated part of your code.

Real-World Embedded Rust

Here’s a simple example of embedded Rust for a microcontroller:

#![no_std]
#![no_main]

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

#[entry]
fn main() -> ! {
    let peripherals = stm32::Peripherals::take().unwrap();
    let gpioa = peripherals.GPIOA.split();
    
    let mut led = gpioa.pa5.into_push_pull_output();
    
    loop {
        led.set_high();
        cortex_m::asm::delay(1_000_000);
        led.set_low();
        cortex_m::asm::delay(1_000_000);
    }
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

This is bare-metal Rust. No operating system. No standard library. Just you and the hardware.

Notice:

  • Type safety - The HAL (Hardware Abstraction Layer) ensures you can’t misuse GPIO pins
  • No malloc - Stack allocation only, predictable memory usage
  • Zero cost abstractions - This compiles to the same assembly as equivalent C

The Ownership Model

Rust’s killer feature for embedded: ownership without garbage collection.

Every value has exactly one owner. When the owner goes out of scope, the value is automatically dropped. No manual free(). No garbage collector pauses.

{
    let buffer = [0u8; 1024]; // Allocated on stack
    do_something(&buffer);
} // buffer automatically dropped here

The compiler tracks lifetimes. You can’t accidentally keep a reference to freed memory. You can’t have data races between interrupts.

Zero-Cost Abstractions

In C, abstractions often mean performance overhead. Function pointers, vtables, indirection.

In Rust, abstractions compile away:

// High-level iterator code
let sum: u32 = data.iter()
    .filter(|x| **x > 0)
    .map(|x| x * 2)
    .sum();

// Compiles to tight loop, same as hand-written C

The Rust compiler performs aggressive optimization. Iterator chains become simple loops. Generics become monomorphized (no runtime dispatch). Traits resolve at compile time.

Embedded Async

Rust’s async/await works on embedded systems. No heap allocation required.

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    let button = Input::new(p.PIN_0, Pull::Up);
    let mut led = Output::new(p.PIN_1, Level::Low);
    
    loop {
        button.wait_for_low().await;
        led.set_high();
        Timer::after(Duration::from_millis(500)).await;
        led.set_low();
    }
}

This is cooperative multitasking with zero overhead. The async runtime is compiled away. No RTOS needed for simple concurrent tasks.

The Ecosystem

Rust’s embedded ecosystem is growing fast:

  • embedded-hal - Hardware abstraction traits
  • Embassy - Modern async embedded framework
  • RTIC - Real-Time Interrupt-driven Concurrency
  • probe-rs - Debugging and flashing tools
  • defmt - Efficient logging for embedded

Plus support for ARM Cortex-M, RISC-V, AVR, ESP32, and more.

Challenges

Rust for embedded isn’t perfect:

  1. Learning curve - Ownership takes time to internalize
  2. Compile times - Slower than C (but improving)
  3. Ecosystem gaps - Some chip vendors still lack Rust support
  4. Binary size - Can be larger than hand-optimized C (but often comparable)
  5. Debugging - DWARF debug info is verbose

But these are engineering trade-offs, not fundamental blockers.

When to Use Rust

Use Rust for embedded when:

  • Safety matters - Medical devices, automotive, aerospace
  • Complexity is high - Large codebases benefit from compiler checks
  • Concurrency is required - Fearless concurrency is real
  • Long-term maintenance - Type safety prevents regressions

Stick with C when:

  • Legacy integration - Massive existing C codebases
  • Extreme size constraints - <4KB flash (though Rust can fit)
  • Team expertise - If your team knows C and not Rust
  • Vendor tools - Some vendors only provide C SDKs

The Future

Major companies are adopting Rust for embedded:

  • Google - Rust in Android firmware
  • Microsoft - Rust in Windows drivers
  • Amazon - Rust in AWS embedded systems
  • Meta - Rust in data center infrastructure

The Linux kernel now supports Rust modules. Firmware is next.

Getting Started

Want to try embedded Rust?

  1. Install Rust - rustup target add thumbv7em-none-eabihf
  2. Get hardware - STM32 Nucleo, ESP32, Raspberry Pi Pico
  3. Read the book - The Embedded Rust Book
  4. Join the community - #rust-embedded on Matrix

Start with blinky. Move to interrupts. Build a real project.

My Experience

At Databuoy, switching to Rust for our bootloader logic eliminated an entire class of bugs. The U-boot integration required unsafe code, but we isolated it to a tiny surface area.

The result: zero memory corruption bugs in production. The type system caught errors during development that would have been nightmares to debug in the field.

Compile time went up. Development time went down. Reliability went way up.

The Bottom Line

Rust isn’t replacing C overnight. But for new embedded projects where safety and reliability matter, it’s increasingly the right choice.

Memory safety without garbage collection. Zero-cost abstractions. Fearless concurrency.

The future of embedded systems is safe. And it’s written in Rust.

Are you using Rust for embedded? I’d love to hear about your experience. Reach out.