Skip to content

Instantly share code, notes, and snippets.

@kaidokert
Last active July 7, 2025 03:27
Show Gist options
  • Save kaidokert/08bbc4903366028177d57761a55e792f to your computer and use it in GitHub Desktop.
Save kaidokert/08bbc4903366028177d57761a55e792f to your computer and use it in GitHub Desktop.

Rust Interrupt Data Sharing Patterns

In embedded Rust, sharing data between the main thread and interrupt handlers requires careful consideration to ensure memory safety and avoid data races, particularly in single-core systems like Cortex-M. This document summarizes three common patterns—static Atomic<T>, static Option<T>, and Mutex<RefCell<Option<T>>>—comparing their use cases, safety, and complexity, and evaluates whether the "move to context" trick applies.

Comparison Table

Pattern Data Types Requires unsafe Synchronization Overhead Use Case Move to Context Trick
static Atomic<T> Primitives (bool, u32) No Atomic operations (lock-free) Minimal (no critical sections) Flags, counters, simple state No
static Option<T> Any (e.g., peripherals) Yes None (manual or unsafe) Low but risky Resource hand-off, complex types Yes
Mutex<RefCell<Option<T>>> Any (e.g., peripherals) No Critical sections (Mutex) Higher (critical sections) Shared access, complex types No

Detailed Patterns

a) static Atomic<T>

  • Description: Uses atomic types (AtomicBool, AtomicU32) for lock-free, safe access to primitive data. Best for simple state like flags or counters, with no unsafe or critical sections.
  • Example:
    use core::sync::atomic::{AtomicBool, Ordering};
    
    static FLAG: AtomicBool = AtomicBool::new(false);
    
    fn main() {
        loop {
            FLAG.store(true, Ordering::Relaxed); // Set flag atomically
        }
    }
    
    #[interrupt]
    fn TIMER0() {
        if FLAG.load(Ordering::Relaxed) {
            FLAG.store(false, Ordering::Relaxed); // Reset flag
            // Handle interrupt
        }
    }

b) static Option<T>

  • Description: Uses a static mut Option<T> to store a resource (e.g., a peripheral), typically for one-time hand-off to an interrupt handler. Requires unsafe for access/mutation, suitable when interrupts are enabled after initialization.
  • Example:
    use core::option::Option;
    
    static mut UART: Option<Uart> = None;
    
    fn main() {
        unsafe {
            UART = Some(Uart::new()); // Initialize UART
        }
        enable_uart_interrupt();
        loop {}
    }
    
    #[interrupt]
    fn UART0() {
        unsafe {
            if let Some(uart) = UART.as_mut() {
                uart.handle_interrupt(); // Use UART
            }
        }
    }
  • Move to Context Trick: Yes, this pattern supports the "move to context" trick, where the interrupt handler uses Option::take() to move the resource into its own static for exclusive use. This reduces the scope to unsafe {} to initial setup and one-time access in interrupt handler, e.g.:
    #[interrupt]
    fn UART0() {
      static mut UART_LOCAL: Option<Uart> = None;
    
      // Move to local storage if not already moved
      cortex_m::interrupt::free(|cs| {
          unsafe {
              UART_LOCAL.replace(UART.take().unwrap());
          }
      });
    
      // Use as a regular Option<T>
      if let Some(uart) = UART_LOCAL.as_mut() {
          uart.handle_interrupt();
      }
    }

c) Mutex<RefCell<Option<T>>>

  • Description: Uses a Mutex for critical sections and RefCell for runtime borrow checking, allowing safe shared access to complex types. Ideal for resources needing concurrent access or uncertain initialization timing.
  • Example:
    use cortex_m::interrupt::{self, Mutex};
    use core::cell::RefCell;
    use core::option::Option;
    
    static UART: Mutex<RefCell<Option<Uart>>> = Mutex::new(RefCell::new(None));
    
    fn main() {
        interrupt::free(|cs| {
            UART.borrow(cs).replace(Some(Uart::new())); // Initialize UART
        });
        enable_uart_interrupt();
        loop {}
    }
    
    #[interrupt]
    fn UART0() {
        interrupt::free(|cs| {
            if let Some(uart) = UART.borrow(cs).borrow_mut().as_mut() {
                uart.handle_interrupt(); // Use UART
            }
        });
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment