Rust on the ESP32 stopped being an experiment a while ago. Today the esp-rs
ecosystem is stable and surprisingly pleasant, with one huge advantage: the
compiler saves you from memory and concurrency bugs that cost you an afternoon of
debugging in C.
The trade-off: you have to learn Rust and set up a separate toolchain.
Two paths, not one
| Path | What it is | Best for |
|---|---|---|
esp-idf-hal | Rust on top of ESP-IDF, with std | Reusing ESP-IDF, Wi-Fi, larger projects |
esp-hal | no_std, without ESP-IDF | Total control, small binaries, async |
embassy | Async framework on esp-hal | Elegant concurrent tasks |
The rule: if you want easy Wi-Fi and Espressif APIs, esp-idf-hal. If you want
to squeeze everything and do not mind no_std, esp-hal + embassy.
Toolchain
The official installer is espup:
cargo install espup
espup install # installs Xtensa and RISC-V targets
. $HOME/export-esp.sh # or the script it generates
And to flash, cargo-espflash:
cargo install cargo-espflash
cargo espflash flash --monitor
Current HAL version: esp-hal 1.2.0.
Example: blink with esp-idf-hal (with std)
use esp_idf_svc::hal::delay::FreeRtos;
use esp_idf_svc::hal::gpio::PinDriver;
use esp_idf_svc::hal::peripherals::Peripherals;
fn main() -> anyhow::Result<()> {
esp_idf_svc::sys::link_patches();
let peripherals = Peripherals::take()?;
let mut led = PinDriver::output(peripherals.pins.gpio2)?;
loop {
led.set_high()?;
FreeRtos::delay_ms(1000);
led.set_low()?;
FreeRtos::delay_ms(1000);
}
}
Notice the detail: the compiler forces you to declare the pin as an output
before writing. In C, a forgotten pinMode is a silent bug; here it does not
compile.
Async with embassy
The best part of the no_std path is embassy: real asynchronous tasks, with no
loop() and no hand-written state machines.
#[embassy_executor::task]
async fn blink(mut led: Output<'static, Gpio2>) {
loop {
led.toggle();
Timer::after_millis(500).await;
}
}
Two tasks “sleeping” at the same time without blocking each other: that is the modern way to write concurrent firmware.
Real advantages
- Safety: no use-after-free, no data races, no null.
- Honest concurrency: if it compiles, it usually does not stomp memory.
- Small binaries in
no_std. cargo: dependencies, tests and docs in one place.
Downsides
- Learning curve for Rust (borrows,
async,no_std). - Younger ecosystem than Arduino: fewer ready-made libraries.
- Separate toolchain:
espup, targets, a specific Rust version. - Fewer examples and tutorials than in C/C++.
When to choose it?
Yes:
- You care about reliability and want fewer weird bugs.
- You already know Rust (or want to learn it properly).
- You need real async or very small binaries.
- It is a long-term project where maintenance matters.
No:
- It is your first contact with microcontrollers: start with Arduino or MicroPython.
- You need a specific library that only exists in C++.
- You are in a hurry.
Summary
- Two paths:
esp-idf-hal(withstd, on ESP-IDF) andesp-hal(no_std), plusembassyfor async. - Toolchain with
espupand flashing withcargo-espflash. - The compiler prevents errors that are silent in C.
esp-halis on 1.2.0 and the ecosystem is now mature.- It is worth it if you care about maintenance and safety.
Next step: a change of scene with the IDEs: which one to use and why. Or go back to the series hub.