A new industrial study from STMicroelectronics, Polytechnic of Turin, Freie Universität Berlin, and Inria ran two firmware teams head-to-head — one in C, one in Rust — for the same IoT device over several months. The results challenge long-held assumptions about C’s dominance in the microcontroller space.

The Study at a Glance

The paper, Embedded Rust or C Firmware? Lessons from an Industrial Microcontroller Use Case with Ariel OS, examines the STAIoTCraft data logger — an edge AI toolkit targeting heterogeneous microcontrollers and sensors, built by STMicroelectronics. Two separate teams were tasked with implementing identical VDP (Vanilla Datalog Protocol) firmware functionality: one using bare-metal C with STM32CubeMX, the other using Rust with the open-source Ariel OS runtime.

The study unfolded in two phases. During Phase 1 (~6 weeks), teams worked in isolation and independently delivered working firmware. During Phase 2 (~4 additional weeks), the teams met weekly to review performance and drive iterative improvements. All measurements were conducted on real commercial hardware: the SensorTile.box Pro, based on an ARM Cortex-M33 core (STM32U585AI) running at 160 MHz, with 2 MiB Flash and 786 KiB SRAM.

~10 wks Total study duration
7680 Hz Max sensor ODR achieved by both
~45% Less RAM used by Rust firmware
~10% Smaller system runtime in Rust

Memory Footprint: Rust Wins on RAM

One of the most striking findings concerns total RAM consumption. The C firmware (VDL-C) relied on dynamic heap allocation through the Parson JSON library — using malloc and free — and consumed a peak of 44,656 bytes of total RAM. The Rust firmware (VDL-Rust) used exclusively static memory allocation and consumed only 24,640 bytes — roughly 45% less.

The difference stems primarily from Rust’s elimination of heap usage: the C firmware required ~25,600 bytes of heap for JSON tree nodes alone. VDL-Rust uses the serde-json-core crate, which deserializes directly into typed stack structs, avoiding dynamic allocation entirely. Notably, VDL-Rust’s total RAM usage is a static compile-time bound, while VDL-C’s figure is a runtime peak that depends on test coverage — meaning the true worst-case for C may be higher.

On the ROM (Flash) side, VDL-Rust’s total is about 10% larger than VDL-C (84,100 bytes vs. 76,744 bytes). However, this gap narrowed significantly through 10 iterative optimization steps, with the main culprits being monomorphization overhead and async state-machine inflation — both addressable with targeted refactoring.

Metric Rust (VDL-Rust) C (VDL-C)
Total ROM (Flash) 84,100 bytes 76,744 bytes +10% smaller
Total RAM 24,640 bytes ~45% less 44,656 bytes (peak)
Heap usage 0 bytes none ~25,600 bytes
System runtime size ~10% smaller (Ariel OS) Larger (newlib + startup)
Max ODR achieved 7680 Hz equal 7680 Hz equal
Memory fragmentation risk None (static) Possible (heap)

Execution Speed: A Close Race

Both firmware implementations successfully achieved the maximum sensor Output Data Rate of 7680 Hz, with an observed interrupt period of 133.75 µs (equivalent to 7477 Hz — the slight deviation is attributed to sensor-side timing inaccuracies rather than firmware limitations).

A notable finding was that Rust’s async/await model introduces some scheduling latency: the EXTI-to-Stream latency was 3.458 µs in Rust vs. 1.958 µs in C, and the UART transition latency was 4.417 µs vs. 2.542 µs in C. This extra overhead is due to Embassy’s two-stage interrupt dispatch architecture. Despite this, both implementations met all real-time deadlines — at the cost of a reduced idle scheduling window in Rust (6.5 µs vs. 13.875 µs in C).

The iterative Phase 2 evolution was revealing: early in the study, Rust briefly outperformed C (peaking at 4985 Hz vs. 3738 Hz for C), before architectural parity was established. By end of Phase 2, both teams converged on the same maximum throughput ceiling.

Counterintuitive finding

Early expectations held that C would outperform Rust on execution speed. Instead, initial measurements showed the opposite: VDL-Rust peaked at 2034 Hz while VDL-C only reached 1000 Hz. The disparity was architectural — VDL-Rust used non-blocking UART while VDL-C was using blocking UART writes. Only after both teams aligned on the same non-blocking architecture did the comparison become fair.

Code Portability: Rust Has a Structural Edge

One of Rust’s clearest practical advantages emerged in portability. VDL-Rust, built on Ariel OS (which itself builds on the Embassy framework), was successfully ported to a second microcontroller — an ST NUCLEO-F401RE with IKS4A1 MEMS expansion — with minimal code changes, mostly board-specific configuration details. Switching targets requires only a change to the build command.

Porting VDL-C required creating a new STM32CubeIDE project for the target board and copying application logic with preprocessor #define directives for pin mappings. As the number of supported hardware targets grows, this approach becomes increasingly brittle. Rust’s approach consolidates everything in a single project, using #[cfg] feature gates for board-specific details.

Code portability — switching MCU targets in Rust (Ariel OS)
// Porting to a new board requires only a build flag change:
// cargo build --features board-nucleo-f401re

// Board-specific config handled declaratively:
#[cfg(feature = "board-sensortile-boxpro")]
const I2C_TIMING: u32 = 0x10909CEC;

#[cfg(feature = "board-nucleo-f401re")]
const I2C_TIMING: u32 = 0x2000090E;

// Application logic is identical across both targets

Lessons Learned: Rust Pitfalls to Avoid

The paper’s most practical contribution is its catalogue of Rust-specific pitfalls discovered during development — a roadmap for teams considering the migration:

Async/await state machine inflation. Each async fn compiles into a state machine whose size grows with local variables held across await points, compounding with nested async calls and quietly inflating static RAM. The team discovered this late, requiring several optimization passes to recover ~8,192 bytes of RAM.

Enum size amplification. Rust enums allocate space equal to their largest variant. When an enum is live across a suspension point, that full size is reserved for every await point in scope — a multiplicative effect that is easy to miss.

Embassy peripheral defaults. Unlike STM32CubeMX, Embassy prioritizes cross-MCU compatibility over raw performance. Default peripheral configurations are not tuned to a specific MCU’s optimal timings, wait states, or cache settings. Teams must explicitly cross-check and tune these for performance-sensitive applications.

Experience gap matters. The Rust team was substantially less experienced than the C team, yet both delivered functional firmware within the same Phase 1 timeframe. The C team leveraged a mature, auto-generating vendor toolchain (CubeMX); the Rust team relied on newer open-source building blocks.

The Ariel OS Factor

A surprising finding was Ariel OS’s system runtime footprint: despite being a full library operating system providing threading, IPC, and synchronization primitives, its system runtime code is approximately 10% smaller than the equivalent bare-metal C runtime (newlib + startup code). This runs counter to the intuition that “more OS = more overhead.”

Ariel OS also provides IPC primitives (signals, channels, atomic flags) that are enforced at the type level by the Rust compiler — meaning concurrency bugs that require runtime discovery in C are simply rejected at compile time in Rust. VDL-C manages concurrency by manually enabling and disabling interrupts; VDL-Rust gets structured, type-safe concurrency for free.

Study Verdict

Hardware analysis and measurements show no compelling reason to prefer C over Rust for microcontroller firmware on the basis of memory footprint or execution speed. Rust’s Ariel OS runtime delivers a smaller system footprint than the traditional bare-metal C stack, and Rust’s static memory model eliminates an entire class of heap-related vulnerabilities. The study concludes: Rust is a sound choice today for firmware development in the embedded IoT domain.

Context and Caveats

The study is candid about its limits. The Rust team was less experienced than the C team, and the C firmware leveraged STMicroelectronics’ own mature, auto-generating toolchain (STM32CubeMX) — advantages that are not inherent to the C language. A more experienced Rust team, or a less vendor-friendly C toolchain, might shift the balance further toward Rust.

The study also notes that Rust’s async/await overhead creates tighter scheduling margins at high sensor ODRs. For applications with even more extreme real-time constraints, synchronous Rust code (without async) is a viable and unexplored alternative that the team acknowledges would have been worth testing.

Additionally, the ROM size gap — Rust’s binary being ~10% larger — is real, though it was narrowed significantly through iterative optimization. Teams with extremely tight Flash budgets should account for this in their planning.

Why This Matters Now

The study arrives at a critical inflection point. The EU Cyber Resilience Act is raising the bar for software security in connected devices, and memory-safety vulnerabilities — the class that C’s manual memory model is most prone to — are directly in scope. C/C++ firmware has dominated embedded development for decades, but mounting evidence from Android (where Rust adoption in security-sensitive code dramatically reduced memory-safety bugs) is now reaching the microcontroller segment.

For firmware teams evaluating the switch, this study offers the most thorough industrial head-to-head comparison available in the academic literature. The takeaway is not that Rust is effortlessly superior — it requires learning curve investment, careful attention to async ergonomics, and explicit tuning of peripheral configurations. But the fundamental case is strong: comparable performance, better memory safety, superior portability, and a system runtime that is already competitive in size.

Source: B. Thapa, D. Alfonso, L. Bini, L. Mapelli, K. Schleiser, R. Fouquet, E. Baccelli — Embedded Rust or C Firmware? Lessons from an Industrial Microcontroller Use Case with Ariel OS. arXiv:2604.25679, 2025. STMicroelectronics · Polytechnic of Turin · Freie Universität Berlin · Inria.
Full paper: https://arxiv.org/pdf/2604.25679