Course contentsShow
← Rust
Lesson 139 of 1,9102. The Ownership ModelFree lesson

Ownership Patterns for Hardware Abstraction

Map ownership models to memory-mapped IO registers in embedded contexts.

In this lesson, you will learn how to use Rust’s ownership model to ensure safe, exclusive access to hardware registers in embedded systems.

Hardware as a Resource

In embedded programming, hardware peripherals (like a UART controller or a GPIO pin) are represented by memory-mapped registers at specific addresses. Traditionally, in languages like C, these registers are global variables accessible by any part of the program. This often leads to "data races" where two parts of your code try to configure the same hardware simultaneously, causing unpredictable behavior.

Rust allows us to treat hardware peripherals as owned resources. If you represent a peripheral as a struct, you can move that struct into a driver. Because the driver now "owns" the hardware, the compiler prevents any other code from touching that peripheral, effectively enforcing exclusive access at compile time.

The Peripheral Ownership Pattern

To safely abstract hardware, we often define a struct that wraps the raw pointer to the memory-mapped address. By using unsafe blocks only during the initialization phase, we can create a "Peripheral" object that owns the hardware:

struct Uart {
    base_address: *mut u32,
}

impl Uart {
    // We only create this once during initialization
    pub fn new() -> Self {
        Self { base_address: 0x4000_1000 as *mut u32 }
    }

    pub fn write_byte(&mut self, byte: u8) {
        // Safe access to the register because we own the struct
        unsafe { self.base_address.write_volatile(byte as u32); }
    }
}

By moving this Uart struct into a function, you transfer the authority to control that hardware. If you attempt to use the peripheral in two places, the borrow checker will flag it as an error because you cannot move the same "hardware resource" twice.

This model essentially turns complex hardware synchronization problems into simple ownership tracking. If you need to share access, you can utilize the RefCell or Cell patterns we discussed earlier to safely share the resource, but the compiler will still force you to handle the runtime borrow rules, preventing the classic "hidden global state" bugs common in bare-metal development.

Key Takeaway: By modeling hardware as owned structs, Rust turns dangerous concurrent access to memory-mapped registers into a compile-time error.