How embassy should be decoupled from logic in rust embedded project?

2026-06-09

Stack Overflow: View Question

Tags: rust, architecture, embedded

Score: 0 | Views: 28

The asker is designing a tiny LED-flash device modeled as a state machine (Idle / Flashing) driven by two commands (Flash / AbortFlash). The real question isn't the toy problem — it's where the seam goes between Embassy (async runtime, executors, Timer, Signal, GPIO futures) and the pure domain logic. They want unit-testable business rules on a host machine, with Embassy as a swappable shell.

Why this is interesting: Embedded Rust tends to grow Embassy types into every signature — async fn returning a future tied to a specific executor, Output<'d, AnyPin> in struct fields, Timer::after() sprinkled through logic. Once that happens, you can't compile the core for cfg(test) on x86, and the state machine becomes inseparable from PAC and HAL. This is the embedded version of the hexagonal-architecture / ports-and-adapters problem, but with extra constraints: no heap, no dyn Trait object-safety pain, and async traits that until recently required nightly.

A workable approach:

Gotchas:

The challenge: Embedded Rust async runtimes like Embassy invite themselves into every type signature, so the real skill is drawing a sans-IO boundary that keeps the executor at the edge without sacrificing zero-cost abstractions.

All newsletters