Rust or C for Firmware? Here's How I'd Decide
When Rust earns its place in embedded firmware, when C is still the better choice, and the questions that settle it for a specific chip, team, and schedule.
I like Rust a lot. I also think “let’s rewrite it in Rust” is one of the more expensive sentences anyone can say in an engineering meeting.
The question worth asking is which language suits a particular microcontroller, team, and schedule, sometimes with a certification requirement on top. That answer changes from project to project, and sometimes it points back to C.
What Rust gets you on a microcontroller
The main benefit is memory safety without a garbage collector. In firmware terms, the compiler catches use-after-free bugs, buffer overruns, and data races between an interrupt handler and your main loop before the code is ever flashed. If you’ve spent days on a corruption bug that only appears when two interrupts arrive in an unlucky order, you know what that’s worth.
The type system helps in less obvious ways too. You can model hardware state with types, so code that writes to a pin configured as an input, or uses a peripheral that was never initialized, won’t compile. The embedded-hal traits also let a driver work across different chips without a pile of #ifdef blocks.
I think people underrate the tooling. Cargo handles builds and dependencies, which is a relief if you’ve maintained a vendor’s makefile. probe-rs makes flashing and debugging fairly painless on many common debug probes. Frameworks like Embassy (async) and RTIC (interrupt-driven) give you structured concurrency with the compiler checking your work.
Where C still makes more sense
I’d check vendor support first. Chip vendors ship C SDKs, C example projects, and C application notes. For popular parts the community Rust HAL crates are often solid, but for something new or obscure you may end up writing register access code yourself, and that time needs to be in the estimate.
Existing code comes next. If a product already has a large C codebase with years of testing behind it, or runs on a certified RTOS or protocol stack, replacing it rarely pays off. Qualified Rust toolchains such as Ferrocene now exist for several safety standards, but the toolchain is only one part of a certification effort.
Then there’s the team. Rust takes a while to get comfortable with, and the borrow checker tends to frustrate people most when the deadline is close. A team that writes very good C will probably ship better firmware in C this quarter than in a language it’s still learning.
The last case is very small parts. When you’re counting kilobytes, C gives you that control with less fuss. Rust can produce tiny binaries too, but you have to be deliberate about panic handling, formatting, and dependencies.
What I usually suggest
Instead of planning a rewrite, decide where to draw a boundary. Rust and C interoperate well, so you can keep a proven C driver layer or vendor SDK and write new application logic in Rust. I’d start with anything that parses data from outside the device, because parsers handle untrusted input and that’s where memory bugs do the most damage.
That puts Rust’s safety where it matters most and lets the team learn it on a contained part of the system, without betting the schedule on porting code that already works.
Questions to work through
If you’re weighing this for a real project, I’d start with these:
- Is there a maintained Rust HAL for the exact part you’re using, or would you be writing one?
- How much existing C has to stay, and how well tested is it?
- Is there a certification requirement, and does it constrain the toolchain?
- How much Rust experience does the team that will maintain it have?
- Where have the worst bugs in the current product come from?
If most of the answers point the same way, the decision is easy. If they’re split, you’ll probably end up with a mixed codebase, and that’s a reasonable place to be.
