Course contentsShow
← Rust
Lesson 1877 of 1,91016. Specialized Runtime TargetsFree lesson

Error Propagation in Constrained Systems

Design lightweight error propagation mechanisms that avoid the overhead of heavy result objects.

In this lesson, you will learn how to design lightweight error propagation mechanisms that minimize binary bloat and execution overhead in memory-constrained environments.

The Cost of Standard Error Handling

In standard Rust, Result<T, E> is a powerful abstraction, but it can be heavy. When using complex error enums or Box<dyn Error>, you often incur significant costs: stack frame growth, binary size increases due to monomorphization, and heap allocation. In a system with only a few kilobytes of RAM or strict timing requirements, these overheads are unacceptable.

Designing Constrained Error Protocols

Instead of rich error types, we shift toward Error Discriminants—using simple integers or repr(u8) enums that map to hardware-specific error states. By forcing an error to fit into a single machine word or byte, we ensure the compiler can return the error directly in a register rather than pushing it to the stack or pointer-indirection.

Consider a system where we avoid the overhead of Result entirely for internal driver paths:

#[repr(u8)]
pub enum ErrorCode {
    Success = 0,
    Timeout = 1,
    BusFault = 2,
}

// Using a 'packed' return type that fits in one register
pub fn read_sensor() -> u8 {
    if !hardware_ready() {
        return ErrorCode::Timeout as u8;
    }
    // Perform operation...
    ErrorCode::Success as u8
}

By manually propagating a u8 status code, we avoid the layout overhead of Result (which must account for the larger of the two variants and a discriminant byte). Furthermore, we can use #[inline(always)] on our propagator functions to ensure the compiler strips the abstraction layer entirely, effectively turning our error handling into a direct branch on a status register.

Maintaining Safety

While lightweight, this approach requires discipline. To keep the system safe, use a custom Result-like wrapper that is repr(transparent) around your integer type. This allows you to use #[must_use] attributes to ensure that error codes are never silently ignored by the caller, preserving Rust's safety guarantees without the baggage of complex trait objects or heavy standard library dependencies.

Key Takeaway: Replace heavy Result abstractions with repr(u8) discriminants passed in registers to minimize stack pressure and binary size in resource-starved systems.