Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Recipe

Wrap an unsafe operation in a safe API

Use when: safe Rust refuses something you can argue is correct, such as two &mut into one slice, and no standard-library function already does it

Goal: a safe function whose checks make its unsafe core valid for every input, so callers never write unsafe

Taught in Safe abstractions over unsafe code

  1. 1

    Search the docs for the operation (split_at_mut, get_disjoint_mut, swap). If one exists, use it and stop.

    Example

    Moving chips between two seats: seats.get_disjoint_mut([from, to]) already returns two &mut.

    Why: std's unsafe cores are reviewed by many people; yours is reviewed by you.

  2. 2

    Name the unsafe operations you need and list each one's documented conditions.

    Example

    fn pick_two_mut<T>(items: &mut [T], i: usize, j: usize) -> (&mut T, &mut T): add needs in-bounds offsets; two &mut need i != j.

    Why: every condition you list is one your checks must cover.

  3. 3

    Use assert!, an early return None, or a Result, so that no argument can reach the block with a condition false.

    Example

    assert!(i != j); assert!(i < items.len() && j < items.len());

    Why: soundness means "for every input", and only a check covers every input.

  4. 4

    Put only the unchecked operations inside, and argue each condition from the checks above.

    Example

    // SAFETY: both < len, and i != j, so ... above unsafe { (&mut *p.add(i), &mut *p.add(j)) }.

    Why: the comment is the proof; the reviewer checks it against the lines above.

  5. 5

    Write tests for the edge cases (equal indices, last item, empty slice) and run them with cargo +nightly miri test (section 7).

    Why: Miri catches a wrong argument that ordinary tests pass by luck.

Common mistakes

  • Checking one condition and forgetting another, such as bounds but not i != j.
  • Marking the wrapper unsafe fn "to be careful": that hands the conditions to every caller instead of checking them once.
  • Letting safe code change a field the unsafe block relies on (see "The unsound neighbour").