Should You Choose Rust or C for Your Next Firmware Project?
A new industrial case study from STMicroelectronics, Inria, and FU Berlin compared parallel Rust and C implementations of the same firmware on an STM32U585. Two teams, ten weeks each, building VDP-protocol-compliant data loggers. Read the full paper here: arxiv.org/pdf/2604.25679. Here is the practical takeaway you can apply this week.
The headline finding. There is no longer a strong technical reason to prefer C over Rust for microcontroller firmware in this class of MCU. Both implementations hit the same hardware throughput ceiling. Rust used 45% less RAM (no-heap discipline beats malloc-based JSON parsing). ROM was within 10%. The Rust team was less experienced and still held its own.
Where Rust clearly wins. Portability. Moving the Rust code to a different board (NUCLEO-F401RE) required only config changes in a single project, gated by #[cfg] features. The C equivalent required a new CubeIDE project, copied code, and per-target #define pin mappings. If you support more than two boards from one codebase, C scales painfully and Rust scales naturally.
Where you need to be careful. Async/await is not free. Each async fn becomes a state machine sized by the largest set of locals held across await points. Nested async calls compound this. At 160 MHz the per-task-switch overhead (around 272 cycles) is fine. At 48 to 84 MHz on smaller M0+ and M4 parts, that overhead starts eating into deadline budget. Treat synchronous code as a first-class citizen in Rust embedded, not a fallback.
A decision framework you can use today:
- Greenfield, modern MCU (Cortex-M4F and up), supporting multiple boards? Rust deserves a serious look. Portability and memory-safety gains compound.
- Tight margins on a small MCU (M0+, low-clock M4)? Stay with C unless you have Rust expertise to optimize around async overhead.
- Existing C codebase, mature toolchain, single target? Don’t rewrite. The case for migration on its own terms is weak.
- Flight software, safety-critical, or strict no-heap discipline? Rust’s type system enforces what C codebases enforce with lint rules and code review. That is a real, durable advantage.
One trick worth stealing regardless of language. The biggest single ROM win in the Rust team’s iteration was replacing worst-case fixed array sizes with compile-time-configurable sizes. That alone saved about 18 KB. Works in C too with macros or templates. The general lesson: build-time configurability costs nothing and often pays for itself in flash.
The full paper is worth your time if you are anywhere near this decision. The pitfalls section reads like it was written by people who actually hit the walls – async sizing, peripheral defaults that prioritize portability over performance, and the StaticCell pattern for putting large structs in .bss instead of stack.