ProgramGuru

Unsafe code and external boundaries

Unsafe code and external boundaries

Rust’s safe interfaces let beginners work without manually proving pointer validity. Some low-level operations and foreign interfaces need unsafe code. Unsafe marks a boundary where the programmer must uphold specific requirements the compiler cannot fully check; it is not a performance switch or permission to ignore every rule.

What you’ll learn

  • Identify the validity requirements for a raw-pointer read.
  • Explain why unsafe does not repair an invalid pointer.

Unsafe code and external boundaries in action

  1. Live score owns valid storage

    The raw pointer refers to the live integer score.

    Live score owns valid storage
  2. Raw pointer refers to it

    The unsafe dereference reads 80, and printing displays it.

    Raw pointer refers to it
  3. Justified read observes 80

    The block does not change the integer’s lifetime or prevent a dangling pointer in a different program.

    Justified read observes 80
The raw pointer refers to the live integer score. The unsafe dereference reads 80, and printing displays it. The block does not change the integer’s lifetime or prevent a dangling pointer in a different program.

Unsafe code and external boundaries explained

Dereferencing a raw pointer requires an unsafe block. The pointer below is created from a valid reference, remains within the owner’s lifetime and is only read while no conflicting mutable access occurs. Those facts justify this narrowly scoped operation. Ordinary reference-based code is preferable when it can express the same task.

Work through the Rust program

Save the complete source as main.rs in the Rust environment. No standard input is needed.

Rust 2024 · complete program

fn main() {
  let score = 80;
  let pointer: *const i32 = &score;
  // SAFETY: score is alive, initialized, aligned, and not being mutated.
  let observed = unsafe { *pointer };
  println!("Observed: {observed}");
}

Expected output

Observed: 80

Follow the execution

The raw pointer refers to the live integer score. The unsafe dereference reads 80, and printing displays it. The block does not change the integer’s lifetime or prevent a dangling pointer in a different program.

Try the complete program in the Rust compiler. For tests, Cargo commands and local files, use the specified local environment.

Try a small variation

Your task: Would adding unsafe make a pointer to already freed storage valid?

Rust 2024 · complete program

fn main() {
  let score = 80;
  let pointer: *const i32 = &score;
  // SAFETY: score is alive, initialized, aligned, and not being mutated.
  let observed = unsafe { *pointer };
  println!("Observed: {observed}");
}

Worked answer

No. Unsafe transfers responsibility for upholding the operation’s requirements to the programmer. Violating them can cause undefined behavior.

Mistakes and limits to recognize

Foreign function interfaces must agree on ABI, representation, lifetimes and ownership contracts. Rust 2024 has explicit requirements for unsafe external declarations and certain attributes. This lesson introduces the boundary; it is not a license to build a safe foreign API without checking its documentation.

Check your understanding

Would adding unsafe make a pointer to already freed storage valid?
Choose one answer. You can try again.

Answer and explanation

No.

No. Unsafe transfers responsibility for upholding the operation’s requirements to the programmer. Violating them can cause undefined behavior.

Take the idea into your next program

No. Unsafe transfers responsibility for upholding the operation’s requirements to the programmer. Violating them can cause undefined behavior. Foreign function interfaces must agree on ABI, representation, lifetimes and ownership contracts.

Further reading: the official Rust Book on unsafe code and external boundaries.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Threads, ownership and joining →