Writing no_std Rust firmware requires different habits than server Rust. Memory is constrained, timing is strict, and panic strategy must be deliberate. This guide covers practical patterns that scale from prototypes to maintainable firmware.

1. Crate architecture

Keep modules separate by responsibility:

  • hal_adapter: hardware binding
  • domain: pure logic and state transitions
  • io: serialization and protocol framing
  • app: orchestration loop

The domain layer should compile without hardware dependencies. This makes logic testable on host.

2. Allocation policy

Avoid dynamic allocation in hot paths. Prefer:

  • fixed-size buffers
  • static ring buffers
  • compile-time capacity types

If heap is required, isolate it and monitor high-water marks.

3. Panic and fault behavior

Default panic behavior is rarely appropriate for deployment. Choose and document strategy:

  • panic -> log minimal info -> reset
  • panic -> enter safe state and blink fault code

The choice depends on safety profile of the device.

4. Time and scheduling

Use monotonic timers and explicit deadlines. Busy waiting is acceptable only when measured and bounded. Cooperative scheduling with short tasks keeps latency predictable.

5. Error design

Typed errors are valuable even in no_std. Keep enums compact and map transport errors clearly.

#[derive(Debug, Clone, Copy)]
pub enum SensorError {
    Timeout,
    Crc,
    NotReady,
}

Avoid string-heavy error paths in constrained targets.

6. Peripheral ownership model

Use ownership to prevent unsafe shared access:

  • one owner per peripheral when possible
  • controlled split APIs when sharing is necessary
  • critical sections only around minimal operations

Race bugs in embedded systems are expensive to detect and reproduce.

7. Diagnostics in constrained environments

Even with limited bandwidth, include lightweight diagnostics:

  • reset reason code
  • error counters in RAM or persistent storage
  • compact event log ring buffer

A few bytes of diagnostics can save days of blind debugging.

8. Host-side testing

You can test more than expected without hardware:

  • domain logic unit tests
  • parser round-trip tests
  • property tests for edge input cases

Reserve hardware-in-loop for timing and peripheral integration validation.

Final note

no_std Rust is not about removing features. It is about designing explicit constraints into architecture. When ownership, memory, and error paths are intentional, firmware quality increases sharply.