Rust bursts into the embedded world: why C is no longer king

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: The era of "hardware" is changing the language

\n\n

If you've ever written firmware for a microcontroller in C, you know that feeling. The feeling when, after three hours of debugging, you find a crash — a pointer flew into the sky, and the watchdog endured it all. Embedded development has long been the territory of C and assembly. But in the last couple of years, a new player has entered the arena — Rust. And it's not just "another language," but a real breakthrough for embedded systems.

\n\n

In this article, we'll break down why embedded development in Rust is not hype, but a necessity. We'll look at real code examples, compare it with C, and understand where to start. If you write for STM32, ESP32, or ARM Cortex-M — this is for you.

\n\n

Why Rust for embedded is an ideal match

\n\n

Embedded systems are a world of constraints. Little memory, no OS, no heap, no exceptions. A compile-time error is good, a runtime error is bad. Rust was designed precisely for such conditions.

\n\n

Memory safety without a garbage collector

\n\n

The main pain of an embedded developer in C is use-after-free, double memory freeing, and buffer overflows. Rust solves this at the type level. The ownership and borrowing model works even in a no_std environment — without an allocator, without an OS, without GC.

\n\n

Imagine you are writing a driver for an LED. In C you would write:

\n\n
// C code\nvoid led_on(GPIO_TypeDef *gpio, uint16_t pin) {\n    gpio->BSRR = pin;\n}
\n\n

Everything is fine until someone passes a null pointer. Rust won't let you do that:

\n\n
// Rust code\nfn led_on(gpio: &mut GpioRegisters, pin: u16) {\n    gpio.bsrr.write(|w| w.bits(pin.into()));\n}
\n\n

Here, &mut GpioRegisters is a guarantee that the reference is valid and access to the registers is exclusive. The compiler simply won't allow dangerous code.

\n\n

Zero-cost abstractions

\n\n

One of the main fears of embedded developers is that a high-level language will "eat" processor cycles. Rust adheres to the principle of zero-cost abstractions: you only pay for what you use. Iterators, closures, traits — all of this compiles into machine code as efficient as manually unrolled loops in C.

\n\n

Moreover, in the Rust embedded ecosystem, there are crates that work at the level of individual instructions. For example, cortex-m-rt generates the interrupt vector and startup code without a single extra instruction.

\n\n

Real project: blinking an LED in Rust (STM32)

\n\n

Let's write a simple program for the STM32F3Discovery board. We'll use cortex-m-quickstart and the HAL from the embedded rust community.

\n\n

Step 1: Project setup

\n\n

Make sure you have Rust and the toolchain for ARM Cortex-M installed:

\n\n
rustup target add thumbv7em-none-eabihf\ncargo install cargo-generate
\n\n

Create a project from the template:

\n\n
cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
\n\n

Step 2: Write the code

\n\n

Open src/main.rs and write the classic Blinky:

\n\n
#![no_std]\n#![no_main]\n\nuse cortex_m_rt::entry;\nuse panic_halt as _;\nuse stm32f3xx_hal::{pac, prelude::*};\n\n#[entry]\nfn main() -> ! {\n    // Gain access to peripherals\n    let dp = pac::Peripherals::take().unwrap();\n    let mut rcc = dp.RCC.constrain();\n    let mut gpioe = dp.GPIOE.split(&mut rcc.ahb);\n\n    // Configure pin PE9 as output (LED LD1)\n    let mut led = gpioe\n        .pe9\n        .into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper);\n\n    loop {\n        led.set_high().unwrap();\n        cortex_m::asm::delay(8_000_000);\n        led.set_low().unwrap();\n        cortex_m::asm::delay(8_000_000);\n    }\n}
\n\n

What's happening here?

\n
    \n
  • #![no_std] — we a

Blogs

Book Recommendations