Course contentsShow
← Rust
Lesson 1489 of 1,91012. Unsafe Rust and Low-Level AccessFree lesson

Defining Panic Behavior in Embedded

You will implement custom panic routines that output debug information via serial consoles.

In this lesson, you will learn how to override the default Rust panic behavior to provide actionable diagnostic feedback over a serial console in a no_std embedded environment.

The Problem with Default Panics

In standard Rust, a panic! prints an error message to stderr and unwinds the stack. In an embedded system without an operating system, there is no stderr, and unwinding is often disabled or impossible. By default, the compiler provides a "panic handler" that simply enters an infinite loop, leaving you blind to why your firmware crashed.

Implementing a Custom Panic Handler

To regain visibility, we define our own panic routine using the #[panic_handler] attribute. This function must accept a PanicInfo struct—which contains the file, line number, and error message—and it must never return.

Because we need to send data over a peripheral (like a UART serial port), we must interact with the hardware directly. Here is a simplified implementation:

use core::panic::PanicInfo;

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    // Access your hardware abstraction layer (HAL) 
    // to initialize the UART peripheral
    let mut serial = uart::init_serial_port();

    // Use the debug formatter to write the error to the console
    core::fmt::write(&mut serial, format_args!("Panic occurred: {}\n", info))
        .ok();

    // Prevent the processor from executing unpredictable code
    loop {
        core::hint::spin_loop();
    }
}

Why This Matters for Embedded

When a system crashes, the PanicInfo object acts as your "black box." By routing this information through your serial console, you can catch critical faults—like index-out-of-bounds errors or failed unwrap() calls—during integration testing.

However, be aware: your panic handler must be extremely robust. If your panic handler itself triggers a panic (for example, by trying to allocate memory that isn't available), the system will likely enter a recursive lockup. Keep your handler minimal, ideally using only registers and basic I/O primitives provided by your hardware abstraction layer.

Key Takeaway: By defining a #[panic_handler], you transform silent hardware freezes into informative debug reports, allowing you to intercept and log critical failures via serial output before the system halts.