Course contentsShow
← Rust
Lesson 64 of 1,9101. Rust Fundamentals and ToolingFree lesson

Unsafe Rust and Its Boundaries

Wrap unsafe operations in safe abstractions to maintain a coherent safety contract.

In this lesson, you will learn how to build a "safe shell" around unsafe code to ensure your program remains memory-safe.

The Problem with unsafe

When we use the unsafe keyword, we are telling the compiler, "I promise I have verified that this operation is memory-safe, even though you cannot prove it." Because unsafe code can trigger undefined behavior (like memory corruption), we never want to expose it directly to the rest of our application. If a user of your code accidentally passes a bad input to an unsafe block, your entire program could crash.

The Safe Abstraction Pattern

To keep our code robust, we wrap unsafe operations inside a safe interface. We create a "boundary" where the unsafe code is hidden inside a function or a method. We then place the necessary safety checks inside that function before we ever reach the unsafe block.

Consider this example where we want to read a memory address:

fn read_memory(ptr: *const i32) -> i32 {
    // We create a boundary: the user doesn't see the 'unsafe' block.
    // We check for null pointers first to maintain safety.
    if ptr.is_null() {
        panic!("Cannot read from a null pointer!");
    }

    unsafe {
        *ptr // The unsafe operation is now protected by our safety check
    }
}

Establishing a Safety Contract

The core idea is the Safety Contract. By wrapping the code:

  1. Encapsulation: The caller of read_memory doesn't need to use the unsafe keyword. They can call it like any other function.
  2. Pre-conditions: We enforce rules (like checking is_null()) inside our function body.
  3. Guarantees: Because we have validated the input, the logic inside the unsafe block is now guaranteed to be safe by our own code's logic.

By acting as a "gatekeeper," you ensure that the rest of your program remains high-level, readable, and—most importantly—prevented from entering an undefined state. You manage the risk inside the function so that the rest of your codebase doesn't have to.

Key Takeaway: Always wrap your unsafe code in a safe, public function that performs the necessary validation to guarantee that the safety contract is never violated by the caller.