In this lesson, you will learn how to encapsulate raw, hardware-adjacent memory operations within safe abstractions to prevent undefined behavior in specialized runtime targets.
When working with memory-mapped peripherals or custom DMA buffers, the compiler cannot verify the validity of your pointers. The core principle of memory safety in these contexts is invariant enforcement: you must use unsafe to perform the raw interaction, but wrap it in a struct or function that maintains safety invariants before and after the operation.
Auditing Memory-Mapped Writes
Consider a memory-mapped status register located at a fixed address. A naive implementation might expose a raw pointer, which invites race conditions or invalid access. Instead, we wrap the access in a hardware-specific driver.
pub struct StatusRegister(*mut u32);
impl StatusRegister {
/// Safety: The provided address must be a valid,
/// volatile-accessible memory-mapped register.
pub unsafe fn new(addr: usize) -> Self {
Self(addr as *mut u32)
}
pub fn read_status(&self) -> u32 {
// We use core::ptr::read_volatile to prevent the compiler
// from optimizing away repeated hardware reads.
unsafe { core::ptr::read_volatile(self.0) }
}
}
The Audit Checklist
When writing these blocks, you must audit against three specific risks:
- Aliasing: Does another part of the system (or hardware) write to this memory? If so, your safe wrapper must account for synchronization or atomic primitives taught in previous modules.
- Alignment: Does the hardware require strict 4-byte alignment? If your
unsafeblock assumes*u32works on an unalignedusize, you risk a hard fault. - Validity: Is the address static to the firmware, or is it dynamically mapped? If dynamic, the
unsafeblock must ensure the mapping exists throughout the lifetime of the object.
By burying the unsafe keyword behind an interface that only accepts valid, pre-checked configurations, you shift the burden of safety from the user of the driver to the author of the driver.
Key Takeaway: The goal of unsafe in embedded contexts is to localize hardware-induced risks into small, auditable modules that expose only sound, idiomatic Rust interfaces to the rest of the application.