Unsafe
cargo run --bin 59_unsafeunsafe unlocks five things the compiler cannot check for you: dereferencing a raw pointer, calling an unsafe fn, reading or writing a static mut, implementing an unsafe trait, and accessing union fields. It does not turn off the borrow checker for ordinary references. Keep unsafe blocks small, and write a SAFETY comment saying why each one is sound.
*const T and *mut T are raw pointers. They may be null, dangling, or unaligned, and they carry no lifetime. Creating them is safe; reading or writing through them is not. add and offset move in units of T, not bytes.
MaybeUninit<T> is memory that may not hold a valid T yet. Write to it first, then call the unsafe assume_init. The usual goal of unsafe code is to build a safe API on top, like the split_at_mut and Vec-like examples below.
Raw pointers from references
fn demo_raw_pointers() {
println!("-- raw pointers --");
let n = 5;
let p: *const i32 = &n; // safe: just making a pointer
let v = unsafe {
// SAFETY: p came from a live &i32, so it is aligned, non-null, and valid.
*p
};
println!("read {v} through {p:p}");
let mut m = 10;
let pm = &raw mut m;
unsafe {
// SAFETY: pm points at m, which is alive and not otherwise borrowed right now.
*pm += 1;
}
println!("m {m}");
let null: *const i32 = ptr::null();
println!("null? {} as_ref {:?}", null.is_null(), unsafe { null.as_ref() });
}Pointer arithmetic over an array
fn demo_pointer_math() {
println!("-- pointer arithmetic --");
let mut a = [1, 2, 3, 4];
let p = a.as_mut_ptr();
unsafe {
// SAFETY: every offset used is in 0..4, inside the array.
*p.add(1) = 20;
let third = p.add(2);
*third *= 10;
println!("last via offset {}", *p.add(a.len() - 1));
}
println!("{a:?}");
let base = a.as_ptr();
let last = &a[3] as *const i32;
println!("distance in elements {}", unsafe { last.offset_from(base) });
}A safe API built on unsafe code
/// Same contract as the standard slice method: panics if mid > len.
fn split_at_mut(s: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = s.len();
let p = s.as_mut_ptr();
assert!(mid <= len);
unsafe {
// SAFETY: the two ranges 0..mid and mid..len do not overlap and are both in bounds,
// so handing out two &mut slices cannot alias.
(
std::slice::from_raw_parts_mut(p, mid),
std::slice::from_raw_parts_mut(p.add(mid), len - mid),
)
}
}
fn demo_safe_abstraction() {
println!("-- safe abstraction --");
let mut v = [1, 2, 3, 4, 5];
let (a, b) = split_at_mut(&mut v, 2);
a[0] = 100;
b[0] = 300;
println!("{v:?}");
// The borrow checker alone rejects the obvious version:
// let a = &mut v[..2]; let b = &mut v[2..]; a[0] = 1; b[0] = 2;
// error[E0499]: cannot borrow `v` as mutable more than once at a time
}unsafe functions and their contracts
/// # Safety
///
/// `i` must be less than `s.len()`.
unsafe fn get_unchecked(s: &[u8], i: usize) -> u8 {
// SAFETY: the caller promises i is in bounds.
unsafe { *s.as_ptr().add(i) }
}
fn demo_unsafe_fn() {
println!("-- unsafe fn --");
let data = b"rust";
let c = unsafe {
// SAFETY: 2 < 4.
get_unchecked(data, 2)
};
println!("byte {}", c as char);
// get_unchecked(data, 2);
// error[E0133]: call to unsafe function `get_unchecked` is unsafe and requires unsafe block
let s = unsafe {
// SAFETY: the bytes are ASCII, hence valid UTF-8.
std::str::from_utf8_unchecked(data)
};
println!("from_utf8_unchecked {s}");
}static mut (prefer atomics or Mutex instead)
static mut COUNTER: u32 = 0;
fn bump() -> u32 {
unsafe {
// SAFETY: this program only touches COUNTER from the main thread.
COUNTER += 1;
COUNTER
}
}
fn demo_static_mut() {
println!("-- static mut --");
bump();
bump();
println!("counter {}", bump());
// Taking a reference to a static mut (&COUNTER) is denied in edition 2024, because it is
// too easy to create aliasing &mut. Use &raw const COUNTER if you need a pointer.
let p = &raw const COUNTER;
println!("via raw pointer {}", unsafe { *p });
}MaybeUninit
fn demo_maybe_uninit() {
println!("-- MaybeUninit --");
let mut slot: MaybeUninit<i32> = MaybeUninit::uninit();
slot.write(7);
let n = unsafe {
// SAFETY: we wrote a valid i32 on the line above.
slot.assume_init()
};
println!("{n}");
// Fill an array of non-Copy values one element at a time.
let mut arr: [MaybeUninit<String>; 3] = [const { MaybeUninit::uninit() }; 3];
for (i, cell) in arr.iter_mut().enumerate() {
cell.write(format!("item {i}"));
}
let arr: [String; 3] = arr.map(|c| unsafe {
// SAFETY: every element was written in the loop above.
c.assume_init()
});
println!("{arr:?}");
// In safe code, std::array::from_fn does the same:
println!("{:?}", std::array::from_fn::<String, 3, _>(|i| format!("item {i}")));
}A tiny fixed-capacity stack with raw memory
struct ArrayStack<T, const N: usize> {
items: [MaybeUninit<T>; N],
len: usize,
}
impl<T, const N: usize> ArrayStack<T, N> {
fn new() -> Self {
Self { items: [const { MaybeUninit::uninit() }; N], len: 0 }
}
fn push(&mut self, t: T) -> Result<(), T> {
if self.len == N {
return Err(t);
}
self.items[self.len].write(t);
self.len += 1;
Ok(())
}
fn pop(&mut self) -> Option<T> {
if self.len == 0 {
return None;
}
self.len -= 1;
// SAFETY: slots below the old len were initialized, and we just lowered len so this
// slot will not be read or dropped again.
Some(unsafe { self.items[self.len].assume_init_read() })
}
}
impl<T, const N: usize> Drop for ArrayStack<T, N> {
fn drop(&mut self) {
while self.pop().is_some() {}
}
}
fn demo_array_stack() {
println!("-- ArrayStack --");
let mut s: ArrayStack<String, 2> = ArrayStack::new();
s.push("a".into()).unwrap();
s.push("b".into()).unwrap();
println!("push when full: {:?}", s.push("c".into()));
println!("pop {:?} {:?} {:?}", s.pop(), s.pop(), s.pop());
}$ cargo run --bin 59_unsafe -- raw pointers -- read 5 through 0x7ffec053b43c m 11 null? true as_ref None -- pointer arithmetic -- last via offset 4 [1, 20, 30, 4] distance in elements 3 -- safe abstraction -- [100, 2, 300, 4, 5] -- unsafe fn -- byte s from_utf8_unchecked rust -- static mut -- counter 3 via raw pointer 3 -- MaybeUninit -- 7 ["item 0", "item 1", "item 2"] ["item 0", "item 1", "item 2"] -- ArrayStack -- push when full: Err("c") pop Some("b") Some("a") None
Full program
// 59. unsafe, raw pointers, and MaybeUninit
//
// unsafe unlocks five things the compiler cannot check for you: dereferencing a raw
// pointer, calling an unsafe fn, reading or writing a static mut, implementing an
// unsafe trait, and accessing union fields. It does not turn off the borrow checker for
// ordinary references. Keep unsafe blocks small, and write a SAFETY comment saying why
// each one is sound.
//
// *const T and *mut T are raw pointers. They may be null, dangling, or unaligned, and
// they carry no lifetime. Creating them is safe; reading or writing through them is not.
// add and offset move in units of T, not bytes.
//
// MaybeUninit<T> is memory that may not hold a valid T yet. Write to it first, then
// call the unsafe assume_init. The usual goal of unsafe code is to build a safe API on
// top, like the split_at_mut and Vec-like examples below.
//
// Run: cargo run --bin 59_unsafe
use std::mem::MaybeUninit;
use std::ptr;
// Example 1: raw pointers from references.
fn demo_raw_pointers() {
println!("-- raw pointers --");
let n = 5;
let p: *const i32 = &n; // safe: just making a pointer
let v = unsafe {
// SAFETY: p came from a live &i32, so it is aligned, non-null, and valid.
*p
};
println!("read {v} through {p:p}");
let mut m = 10;
let pm = &raw mut m;
unsafe {
// SAFETY: pm points at m, which is alive and not otherwise borrowed right now.
*pm += 1;
}
println!("m {m}");
let null: *const i32 = ptr::null();
println!("null? {} as_ref {:?}", null.is_null(), unsafe { null.as_ref() });
}
// Example 2: pointer arithmetic over an array.
fn demo_pointer_math() {
println!("-- pointer arithmetic --");
let mut a = [1, 2, 3, 4];
let p = a.as_mut_ptr();
unsafe {
// SAFETY: every offset used is in 0..4, inside the array.
*p.add(1) = 20;
let third = p.add(2);
*third *= 10;
println!("last via offset {}", *p.add(a.len() - 1));
}
println!("{a:?}");
let base = a.as_ptr();
let last = &a[3] as *const i32;
println!("distance in elements {}", unsafe { last.offset_from(base) });
}
/// Same contract as the standard slice method: panics if mid > len.
fn split_at_mut(s: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = s.len();
let p = s.as_mut_ptr();
assert!(mid <= len);
unsafe {
// SAFETY: the two ranges 0..mid and mid..len do not overlap and are both in bounds,
// so handing out two &mut slices cannot alias.
(
std::slice::from_raw_parts_mut(p, mid),
std::slice::from_raw_parts_mut(p.add(mid), len - mid),
)
}
}
// Example 3: a safe API built on unsafe code.
fn demo_safe_abstraction() {
println!("-- safe abstraction --");
let mut v = [1, 2, 3, 4, 5];
let (a, b) = split_at_mut(&mut v, 2);
a[0] = 100;
b[0] = 300;
println!("{v:?}");
// The borrow checker alone rejects the obvious version:
// let a = &mut v[..2]; let b = &mut v[2..]; a[0] = 1; b[0] = 2;
// error[E0499]: cannot borrow `v` as mutable more than once at a time
}
/// # Safety
///
/// `i` must be less than `s.len()`.
unsafe fn get_unchecked(s: &[u8], i: usize) -> u8 {
// SAFETY: the caller promises i is in bounds.
unsafe { *s.as_ptr().add(i) }
}
// Example 4: unsafe functions and their contracts.
fn demo_unsafe_fn() {
println!("-- unsafe fn --");
let data = b"rust";
let c = unsafe {
// SAFETY: 2 < 4.
get_unchecked(data, 2)
};
println!("byte {}", c as char);
// get_unchecked(data, 2);
// error[E0133]: call to unsafe function `get_unchecked` is unsafe and requires unsafe block
let s = unsafe {
// SAFETY: the bytes are ASCII, hence valid UTF-8.
std::str::from_utf8_unchecked(data)
};
println!("from_utf8_unchecked {s}");
}
static mut COUNTER: u32 = 0;
fn bump() -> u32 {
unsafe {
// SAFETY: this program only touches COUNTER from the main thread.
COUNTER += 1;
COUNTER
}
}
// Example 5: static mut (prefer atomics or Mutex instead).
fn demo_static_mut() {
println!("-- static mut --");
bump();
bump();
println!("counter {}", bump());
// Taking a reference to a static mut (&COUNTER) is denied in edition 2024, because it is
// too easy to create aliasing &mut. Use &raw const COUNTER if you need a pointer.
let p = &raw const COUNTER;
println!("via raw pointer {}", unsafe { *p });
}
// Example 6: MaybeUninit.
fn demo_maybe_uninit() {
println!("-- MaybeUninit --");
let mut slot: MaybeUninit<i32> = MaybeUninit::uninit();
slot.write(7);
let n = unsafe {
// SAFETY: we wrote a valid i32 on the line above.
slot.assume_init()
};
println!("{n}");
// Fill an array of non-Copy values one element at a time.
let mut arr: [MaybeUninit<String>; 3] = [const { MaybeUninit::uninit() }; 3];
for (i, cell) in arr.iter_mut().enumerate() {
cell.write(format!("item {i}"));
}
let arr: [String; 3] = arr.map(|c| unsafe {
// SAFETY: every element was written in the loop above.
c.assume_init()
});
println!("{arr:?}");
// In safe code, std::array::from_fn does the same:
println!("{:?}", std::array::from_fn::<String, 3, _>(|i| format!("item {i}")));
}
// Example 7: a tiny fixed-capacity stack with raw memory.
struct ArrayStack<T, const N: usize> {
items: [MaybeUninit<T>; N],
len: usize,
}
impl<T, const N: usize> ArrayStack<T, N> {
fn new() -> Self {
Self { items: [const { MaybeUninit::uninit() }; N], len: 0 }
}
fn push(&mut self, t: T) -> Result<(), T> {
if self.len == N {
return Err(t);
}
self.items[self.len].write(t);
self.len += 1;
Ok(())
}
fn pop(&mut self) -> Option<T> {
if self.len == 0 {
return None;
}
self.len -= 1;
// SAFETY: slots below the old len were initialized, and we just lowered len so this
// slot will not be read or dropped again.
Some(unsafe { self.items[self.len].assume_init_read() })
}
}
impl<T, const N: usize> Drop for ArrayStack<T, N> {
fn drop(&mut self) {
while self.pop().is_some() {}
}
}
fn demo_array_stack() {
println!("-- ArrayStack --");
let mut s: ArrayStack<String, 2> = ArrayStack::new();
s.push("a".into()).unwrap();
s.push("b".into()).unwrap();
println!("push when full: {:?}", s.push("c".into()));
println!("pop {:?} {:?} {:?}", s.pop(), s.pop(), s.pop());
}
fn main() {
demo_raw_pointers();
demo_pointer_math();
demo_safe_abstraction();
demo_unsafe_fn();
demo_static_mut();
demo_maybe_uninit();
demo_array_stack();
}