Unsafe Functions
An unsafe function (unsafe fn) is a function or method whose correctness depends on preconditions that the Rust type system cannot verify at compile time. Marking a function unsafe fn forces every caller to wrap the call in an unsafe { ... } block and verify that the function's documented safety contract holds for the arguments passed.
When to Mark a Function unsafe fn
A function should be marked unsafe fn if and only if there exists some combination of type-checked arguments or program states for which calling the function causes undefined behavior (memory corruption, dangling references, data races, or invalid bit patterns).
- Should be
unsafe fn:slice::from_raw_parts(ptr, len)(passing a null pointer or out-of-boundslencauses UB),String::from_utf8_unchecked(bytes)(passing non-UTF-8 bytes violatesstr's validity invariant). - Should NOT be
unsafe fn: A function that deletes a file, panics on invalid input, leaks memory, or causes a logic bug without violating memory safety.
Rust 2024 Edition Rule: unsafe_op_in_unsafe_fn
In Rust 2021 and earlier editions, the entire body of an unsafe fn implicitly acted as one large unsafe {} block. Starting in the Rust 2024 edition, the unsafe_op_in_unsafe_fn lint is enabled by default: an unsafe fn signature declares a contract to callers, but performing an unsafe operation inside the body still requires an explicit unsafe { ... } block.
This separation prevents large unsafe fn bodies from accidentally invoking unintended unsafe operations without review:
// ❌ does not compile
// In Rust 2024 edition, dereferencing `ptr` inside `unsafe fn` without an
// inner `unsafe { ... }` block is rejected:
unsafe fn read_offset(ptr: *const u32, offset: usize) -> u32 {
*ptr.add(offset)
}
fn main() {}
error[E0133]: call to unsafe function `std::ptr::const_ptr::<impl *const T>::add` is unsafe and requires unsafe block
--> src/main.rs:5:6
|
5 | *ptr.add(offset)
| ^^^^^^^^^^^^^^^ call to unsafe function
|
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html>
note: an unsafe function restricts its caller, but its body is safe by default
--> src/main.rs:4:1
|
4 | unsafe fn read_offset(ptr: *const u32, offset: usize) -> u32 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Wrapping only the specific pointer arithmetic and dereference inside an inner unsafe { ... } block makes the exact unsafe operation explicit:
/// Returns the element at `offset` from `ptr`.
///
/// # Safety
/// - `ptr` must be non-null, properly aligned, and valid for reads.
/// - `ptr.add(offset)` must point to an initialized `u32` within the same allocation.
unsafe fn read_offset(ptr: *const u32, offset: usize) -> u32 {
// SAFETY: Caller guarantees `ptr.add(offset)` is in-bounds, aligned, and initialized.
unsafe { *ptr.add(offset) }
}
fn main() {
let buffer: [u32; 4] = [100, 200, 300, 400];
// SAFETY: `buffer.as_ptr()` is valid for 4 elements and offset `2` is in bounds.
let third = unsafe { read_offset(buffer.as_ptr(), 2) };
println!("third element: {}", third);
}
third element: 300
Safe Wrapper vs. _unchecked Companion Naming Convention
In the standard library and idiomatic Rust crates, when both a checked safe function and an unchecked unsafe fn exist for the same operation, the unsafe variant is suffixed with _unchecked:
| Safe Function (Checked) | Unsafe Function (Unchecked Precondition) |
|---|---|
slice.get(index) -> Option<&T> |
unsafe fn get_unchecked(index) -> &T |
String::from_utf8(vec) -> Result<String, ...> |
unsafe fn from_utf8_unchecked(vec) -> String |
NonZeroU32::new(n) -> Option<NonZeroU32> |
unsafe fn new_unchecked(n) -> NonZeroU32 |
char::from_u32(i) -> Option<char> |
unsafe fn from_u32_unchecked(i) -> char |
u32::checked_add(a, b) -> Option<u32> |
unsafe fn unchecked_add(a, b) -> u32 |