Unsafe Blocks
Functional Programming
Tutorial
The Problem
unsafe blocks are Rust's explicit opt-out from the borrow checker for a specific scope. The fundamental principle: minimize the unsafe footprint. Only the code that genuinely requires unsafe operations should be inside unsafe { }. Safe code (error handling, logging, computation) belongs outside. This discipline makes auditing easier — reviewers can focus on the narrow unsafe region rather than an entire function. It is a core practice in systems programming with Rust.
🎯 Learning Outcomes
unsafe { } scopes the suspension of safety guarantees to a minimal regionstatic mut requires unsafe access and why AtomicU64 is usually betterunsafe block with a // SAFETY: commentCode Example
#![allow(clippy::all)]
//! 700 — Unsafe Blocks
//! Keep unsafe footprint minimal: only what truly needs it.
static mut GLOBAL_COUNTER: u64 = 0;
/// Increment the global counter — smallest possible unsafe block.
fn increment() {
unsafe {
// SAFETY: Single-threaded; no concurrent access to GLOBAL_COUNTER.
// In multi-threaded code, use AtomicU64 instead.
GLOBAL_COUNTER += 1;
}
// ← Safe code (logging, side-effects) lives OUTSIDE the unsafe block.
}
fn get() -> u64 {
unsafe {
// SAFETY: Same single-threaded guarantee.
GLOBAL_COUNTER
}
}
fn reset() {
unsafe {
// SAFETY: Same single-threaded guarantee.
GLOBAL_COUNTER = 0;
}
// Safe operations after the minimal unsafe block
println!("Counter reset to 0.");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_counter_lifecycle() {
reset();
assert_eq!(get(), 0);
increment();
increment();
assert_eq!(get(), 2);
reset();
assert_eq!(get(), 0);
}
#[test]
fn test_safe_code_outside_unsafe() {
// Demonstrate safe code compiles and works without unsafe
let v = vec![1u32, 2, 3];
assert_eq!(v.iter().sum::<u32>(), 6);
}
}Key Differences
unsafe { }; OCaml's safety violations (Obj.magic) are equally visible but less common.unsafe blocks makes security audits tractable — tools like cargo geiger count unsafe lines; OCaml has no equivalent metric.static mut**: Rust's static mut is inherently unsafe; OCaml's global ref is always safe (GC-managed, no data races in single-threaded mode).OCaml Approach
OCaml has no unsafe blocks — all code is uniformly safe (with the exception of Obj.magic and direct C FFI):
let global_counter = ref 0
let increment () = incr global_counter
let get () = !global_counter
(* For thread safety: use Mutex.t or Atomic.t *)
Full Source
#![allow(clippy::all)]
//! 700 — Unsafe Blocks
//! Keep unsafe footprint minimal: only what truly needs it.
static mut GLOBAL_COUNTER: u64 = 0;
/// Increment the global counter — smallest possible unsafe block.
fn increment() {
unsafe {
// SAFETY: Single-threaded; no concurrent access to GLOBAL_COUNTER.
// In multi-threaded code, use AtomicU64 instead.
GLOBAL_COUNTER += 1;
}
// ← Safe code (logging, side-effects) lives OUTSIDE the unsafe block.
}
fn get() -> u64 {
unsafe {
// SAFETY: Same single-threaded guarantee.
GLOBAL_COUNTER
}
}
fn reset() {
unsafe {
// SAFETY: Same single-threaded guarantee.
GLOBAL_COUNTER = 0;
}
// Safe operations after the minimal unsafe block
println!("Counter reset to 0.");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_counter_lifecycle() {
reset();
assert_eq!(get(), 0);
increment();
increment();
assert_eq!(get(), 2);
reset();
assert_eq!(get(), 0);
}
#[test]
fn test_safe_code_outside_unsafe() {
// Demonstrate safe code compiles and works without unsafe
let v = vec![1u32, 2, 3];
assert_eq!(v.iter().sum::<u32>(), 6);
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_counter_lifecycle() {
reset();
assert_eq!(get(), 0);
increment();
increment();
assert_eq!(get(), 2);
reset();
assert_eq!(get(), 0);
}
#[test]
fn test_safe_code_outside_unsafe() {
// Demonstrate safe code compiles and works without unsafe
let v = vec![1u32, 2, 3];
assert_eq!(v.iter().sum::<u32>(), 6);
}
}
Exercises
std::sync::atomic::AtomicU64 — compare the code size and verify thread safety.