Step 1 of 4 · Reading · ~2 min
Learn
Macros and Unsafe
unsafe lets you do things the compiler cannot verify. It shows up in:
- calling C libraries (FFI)
- implementing data structures (linked lists, raw pointers)
- hardware and OS interfaces
- performance-critical paths where you have checked the invariant by hand
The 5 unsafe superpowers:
- Dereference a raw pointer (
*ptr) - Call an
unsafefunction - Access or modify a mutable
static - Implement an
unsafetrait - Access fields of a
union
That list is the whole of it. unsafe is a permission, not an escape
hatch: inside the block, ownership, borrowing and the type system are enforced
exactly as they are outside it. What changes is that five operations whose
soundness the compiler cannot prove become legal - and proving them becomes
your job.
Raw pointers:
let x = 5;
let r1: *const i32 = &x; // safe: this is just an address
unsafe {
println!("{}", *r1); // unsafe: following it is the risky part
}
Note where the boundary falls. Building a raw pointer reads and writes nothing,
so it needs no unsafe even if the pointer is null or dangling. Dereferencing
is the moment the compiler can no longer prove the pointee is live, initialised
and correctly aligned, so that is the operation that is gated.
FFI to C:
extern "C" {
fn abs(x: i32) -> i32; // declared from libc
}
fn main() {
let result = unsafe { abs(-5) };
println!("{}", result); // prints: 5
}
The compiler has no idea what abs does; the unsafe is you promising the
declaration matches the real symbol.
Calling unsafe Rust functions:
unsafe fn dangerous() {
println!("trust me");
}
fn main() {
unsafe { dangerous(); }
}
Block discipline:
- Wrap as small a region as possible - a large
unsafeblock hides which line is load-bearing. - Write a
// SAFETY:comment saying which invariant makes it sound. - Provide a safe wrapper API; callers should not need
unsafeof their own.
// SAFETY: `buf` was just allocated with `len` bytes, so `ptr` is valid for the write.
unsafe { ptr::write_bytes(ptr, 0, len); }
std::mem::transmute reinterprets one type's bits as another and is the
sharpest tool here - it can invent invalid values of any type. Prefer
to_ne_bytes, from_bits or a union when one exists.
Avoid unsafe when you can. Sound safe wrappers already exist for most of
what people reach for it: slice::from_raw_parts has a checked cousin,
Cell::as_ptr hands you a pointer without one. Reach for raw unsafe only
when there is genuinely no safe API.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…