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

Defining Undefined Behavior

You will distinguish between language-level invariants and hardware-level expectations to avoid illegal CPU states.

In this lesson, you will learn how to distinguish between violating Rust’s high-level language contracts and triggering illegal states at the hardware level.

When we write unsafe Rust, we often talk about "Undefined Behavior" (UB) as a single concept. However, UB actually exists on two distinct planes: Language-Level Invariants and Hardware-Level Expectations.

Language-Level Invariants

These are the rules the Rust compiler (and the borrow checker) assumes are always true to optimize your code. If you break these, you violate the "contract" between you and the compiler. Examples include:

  • Dereferencing a null or dangling pointer.
  • Creating a reference to uninitialized memory.
  • Violating aliasing rules (e.g., having two mutable references to the same location).

When these are violated, the compiler may generate code that assumes the impossible cannot happen, leading to logical inconsistencies that aren't technically "crashes"—they are silent corruptions.

Hardware-Level Expectations

These are the physical constraints of your CPU. Even if you satisfy the Rust compiler's rules, you can still trigger UB by ignoring how the hardware functions. For example:

  • Executing non-executable memory: Attempting to jump to a data buffer as if it were machine code.
  • Misaligned Access: Some CPU architectures trigger a hardware exception (or perform dangerously slow/unexpected operations) if you attempt to load a 64-bit integer from an address that isn't a multiple of 8.
  • Atomic Violation: Performing a non-atomic load on memory that the hardware expects to be accessed via specific cache-coherency protocols.

Consider this snippet:

let ptr = 0x1234 as *const u64; // Arbitrary memory address
unsafe {
    let val = *ptr; // Potential Language UB (dangling) AND Hardware UB (alignment/access)
}

In the example above, the compiler assumes ptr is a valid, aligned reference to a u64. If 0x1234 is not aligned to an 8-byte boundary, the hardware might throw a fault. If the address is restricted by the OS, you trigger a segmentation fault. Both are UB, but one is a failure of the language's safety model, and the other is a failure to meet the hardware's electrical or architectural requirements.

Key Takeaway: Undefined Behavior occurs when you either break the compiler’s logical assumptions (language-level) or attempt an operation the CPU cannot physically execute (hardware-level).