701: Unsafe Functions
Difficulty: 4 Level: Expert Declare `unsafe fn` to push safety contracts to the caller, then wrap them in safe APIs.The Problem This Solves
Some operations have preconditions the compiler cannot check โ "this index is in bounds", "these memory regions don't overlap", "this pointer has been initialised". You could document them in a comment and hope callers comply, but Rust provides a better mechanism: `unsafe fn`. Marking a function `unsafe` makes the contract visible in the type system โ callers must write an `unsafe` block to invoke it, forcing them to acknowledge the preconditions exist. The safe-wrapper idiom builds on top of this. You write a private `unsafe fn` that is fast and unchecked, then a public safe function that validates the preconditions first. Callers interact only with the safe API; the `unsafe fn` is an implementation detail. This is exactly how `slice::get_unchecked` and `ptr::copy_nonoverlapping` work in the standard library. unsafe is a tool, not a crutch โ use only when safe Rust genuinely can't express the pattern.The Intuition
Think of an `unsafe fn` as a professional power tool: it does the job faster than the consumer version, but it ships without the guard rail. The `// # Safety` doc section is the user manual โ it lists every condition that must be true before you press the trigger. A safe wrapper is the guard rail: it checks those conditions before handing control to the dangerous function. The compiler does not verify `// # Safety` comments. It only verifies that the caller acknowledged the contract by writing `unsafe { }`. The human โ you, the reviewer, the auditor โ reads the comment and decides whether the acknowledgement is justified.How It Works in Rust
/// Copy `n` bytes from `src` to `dst`.
///
/// # Safety
/// - `src` must be valid for `n` bytes of reads.
/// - `dst` must be valid for `n` bytes of writes.
/// - The two regions must not overlap.
unsafe fn raw_copy(src: *const u8, dst: *mut u8, n: usize) {
for i in 0..n {
// SAFETY: Caller guarantees validity, non-overlap, and correct size.
*dst.add(i) = *src.add(i);
}
}
/// Safe wrapper: validates slice lengths, then delegates to raw_copy.
pub fn safe_copy(src: &[u8], dst: &mut [u8]) -> Result<(), String> {
if src.len() != dst.len() {
return Err(format!("length mismatch: {} vs {}", src.len(), dst.len()));
}
unsafe {
// SAFETY: Both slices are valid for their full length.
// Rust's borrow checker guarantees &[u8] and &mut [u8] cannot alias.
raw_copy(src.as_ptr(), dst.as_mut_ptr(), src.len());
}
Ok(())
}
The structure is always: (1) document preconditions in `# Safety`, (2) implement the fast path, (3) write a safe wrapper that enforces those preconditions before calling through.
What This Unlocks
- Standard library primitives โ `slice::get_unchecked`, `ptr::copy_nonoverlapping`, `str::from_utf8_unchecked` all follow this exact pattern.
- Custom collection internals โ a sorted map can expose a safe insert and an `unsafe get_unchecked` for callers who can prove the index is valid.
- Performance-critical inner loops โ skip redundant bounds checks after a single safe validation at the loop entry point.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| Precondition declaration | `[@warning "-8"]` or doc comment | `unsafe fn` + `# Safety` doc section |
| Caller acknowledgement | None required | Must write `unsafe { }` to call |
| Unchecked array access | `Array.unsafe_get` | `slice::get_unchecked` (unsafe fn) |
| Safe wrapper pattern | Checked wrapper calls unchecked | `pub fn` validates, then calls `unsafe fn` |
| Compiler enforcement | No | Yes โ calling `unsafe fn` without `unsafe` is a compile error |
//! 701 โ Unsafe Functions
//! unsafe fn declarations and the safe-wrapper idiom.
/// Copy `n` bytes from `src` to `dst` without bounds checking.
///
/// # Safety
/// - `src` must be valid for `n` bytes of reads.
/// - `dst` must be valid for `n` bytes of writes.
/// - The two regions must not overlap.
/// - Both pointers must be aligned for `u8` (alignment 1 โ always satisfied).
unsafe fn raw_copy(src: *const u8, dst: *mut u8, n: usize) {
for i in 0..n {
// SAFETY: Caller guarantees src and dst are valid for n bytes,
// non-overlapping, and aligned.
*dst.add(i) = *src.add(i);
}
}
/// Safe wrapper: validates slice lengths, then calls `raw_copy`.
pub fn safe_copy(src: &[u8], dst: &mut [u8]) -> Result<(), String> {
if src.len() != dst.len() {
return Err(format!(
"length mismatch: src={} dst={}",
src.len(), dst.len()
));
}
unsafe {
// SAFETY: Both slices are valid for their full length.
// Rust's borrow rules guarantee &[u8] and &mut [u8] cannot alias.
raw_copy(src.as_ptr(), dst.as_mut_ptr(), src.len());
}
Ok(())
}
/// Get the element at index without bounds check.
///
/// # Safety
/// `idx` must be less than `slice.len()`.
unsafe fn get_unchecked<T: Copy>(slice: &[T], idx: usize) -> T {
// SAFETY: Caller guarantees idx < slice.len().
*slice.as_ptr().add(idx)
}
/// Safe wrapper: bounds-checks before calling get_unchecked.
pub fn safe_get<T: Copy>(slice: &[T], idx: usize) -> Option<T> {
if idx < slice.len() {
Some(unsafe {
// SAFETY: idx < slice.len() confirmed above.
get_unchecked(slice, idx)
})
} else {
None
}
}
fn main() {
let src = b"Hello, Rust!";
let mut dst = vec![0u8; src.len()];
safe_copy(src, &mut dst).expect("copy failed");
println!("Copied: {}", std::str::from_utf8(&dst).unwrap());
match safe_copy(src, &mut vec![0u8; 3]) {
Ok(_) => println!("unexpected ok"),
Err(e) => println!("Expected error: {e}"),
}
let nums = [10, 20, 30, 40];
println!("safe_get(2) = {:?}", safe_get(&nums, 2));
println!("safe_get(9) = {:?}", safe_get(&nums, 9));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_copy() {
let src = b"abcde";
let mut dst = vec![0u8; 5];
assert!(safe_copy(src, &mut dst).is_ok());
assert_eq!(&dst, b"abcde");
}
#[test]
fn test_safe_copy_mismatch() {
assert!(safe_copy(b"abc", &mut vec![0u8; 5]).is_err());
}
#[test]
fn test_safe_get() {
let v = [1i32, 2, 3];
assert_eq!(safe_get(&v, 0), Some(1));
assert_eq!(safe_get(&v, 2), Some(3));
assert_eq!(safe_get(&v, 3), None);
}
}
(* OCaml: no unsafe functions โ safety is guaranteed by the type system.
We model "low-level" access with validation wrappers. *)
(** Array.unsafe_get skips bounds check โ the OCaml unsafe equivalent. *)
let unchecked_get (arr : 'a array) (i : int) : 'a = Array.unsafe_get arr i
(** Safe wrapper validates before delegating to unchecked_get. *)
let safe_get (arr : 'a array) (i : int) : 'a option =
if i >= 0 && i < Array.length arr then Some (unchecked_get arr i)
else None
let () =
let data = [| 100; 200; 300 |] in
(match safe_get data 1 with
| Some v -> Printf.printf "safe_get(1) = %d\n" v
| None -> print_endline "out of bounds");
(match safe_get data 5 with
| Some _ -> print_endline "Should not happen"
| None -> print_endline "safe_get(5) = out of bounds (caught safely)")