Unsafe Rust is a subset of the Rust programming language that allows developers to opt out of some of Rust's compile-time memory safety guarantees. While Rust is famous for its strict ownership system, borrow checker, and the promise of memory safety without a garbage collector, there are specific scenarios where these guarantees need to be relaxed or cannot be verified by the compiler alone.
Why Unsafe Rust Exists:
1. Interfacing with Foreign Code (FFI): When interacting with C libraries, operating system APIs, or other languages that don't adhere to Rust's memory safety rules, `unsafe` is often necessary to correctly pass data and manage memory across the language boundary.
2. Performance-Critical Code: For certain highly optimized algorithms or data structures, manually managing memory can offer performance gains by avoiding some overhead or allowing more precise control than the borrow checker permits. This is rare, as the Rust compiler is very good at optimizing safe code.
3. Implementing Data Structures: Complex data structures like doubly-linked lists, custom allocators, or certain types of trees inherently involve manual memory management, pointer arithmetic, or self-referential structures that are difficult or impossible to express safely with Rust's default rules.
4. Compiler Limitations: Sometimes, the compiler isn't smart enough to prove the safety of a piece of code, even if it is logically sound. In such cases, `unsafe` allows the developer to assert to the compiler that the code is indeed safe.
What `unsafe` Does (and Doesn't Do):
An `unsafe` block or function doesn't turn off *all* of Rust's safety checks. It only allows you to do five specific things that the compiler cannot guarantee are memory-safe:
1. Dereference a raw pointer: Raw pointers (`*const T` and `*mut T`) are analogous to pointers in C/C++. Dereferencing them is `unsafe` because the compiler cannot guarantee they are valid, non-null, or properly aligned.
2. Call an `unsafe` function or method: Functions marked with `unsafe fn` have preconditions that the caller must uphold to ensure memory safety. The compiler cannot verify these preconditions.
3. Access or modify a mutable static variable: Mutable static variables can be accessed by multiple threads simultaneously, leading to data races without proper synchronization. Accessing them is `unsafe` to force the developer to consider synchronization.
4. Implement `unsafe` traits: Traits can be marked `unsafe`, meaning that implementations of these traits must uphold certain invariants that the compiler cannot check. For example, `Send` and `Sync` are `unsafe` implicitly because their correct implementation is crucial for thread safety.
5. Access fields of `union`s: `union`s are similar to C `union`s, allowing multiple types to occupy the same memory location. Reading from a `union` field that was not the last field written to is undefined behavior without `unsafe` to signal the programmer is responsible for correctness.
The Contract of `unsafe`:
When you use `unsafe` Rust, you are making a contract with the compiler: you promise that the code inside the `unsafe` block will uphold all of Rust's memory safety invariants, even though the compiler cannot verify it. This includes:
* No null or dangling pointer dereferences.
* No out-of-bounds memory access.
* No data races (when multiple threads access the same memory without synchronization and at least one is a write).
* No aliasing of mutable references (e.g., `&mut T` and `&mut T` to the same data, or `&T` and `&mut T` to the same data).
Safe Abstraction:
The most common and recommended way to use `unsafe` is to encapsulate it within a *safe abstraction*. This means writing an `unsafe` implementation of a function or data structure, but exposing a *safe* API (a function or method that doesn't use `unsafe` in its signature) to the rest of your code. The responsibility then falls on the implementer of the `unsafe` code to ensure that the safe API correctly maintains all invariants, so users of the API don't have to worry about `unsafe` Rust.
`unsafe` Rust is a powerful tool, but it should be used judiciously, with careful consideration and thorough testing, as it shifts the burden of memory safety from the compiler to the programmer.
Example Code
```rust
use std::slice;
fn main() {
// --- 1. Dereferencing a raw pointer ---
let mut value = 10;
let r1 = &value as *const i32; // Create an immutable raw pointer
let r2 = &mut value as *mut i32; // Create a mutable raw pointer
unsafe {
// Dereferencing raw pointers requires an unsafe block
println!("r1 points to: {}", *r1);
*r2 = 20; // Modify value through the mutable raw pointer
println!("r2 points to: {}", *r2);
}
println!("Original value after modification: {}", value);
// --- 2. Calling an unsafe function ---
// Example: Implementing a function similar to `split_at_mut` using raw pointers
// This function splits a mutable slice into two mutable slices at a given index.
// The internal logic uses `unsafe` functions like `as_mut_ptr` and `from_raw_parts_mut`.
fn custom_split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr(); // Get a mutable raw pointer to the slice's data
assert!(mid <= len, "mid index out of bounds");
// The following block is unsafe because `from_raw_parts_mut` trusts
// the caller to provide a valid pointer and length. We manually ensure
// correctness here by checking `mid <= len` and calculating lengths.
unsafe {
// Create the first slice from the start pointer and 'mid' length.
let first_half = slice::from_raw_parts_mut(ptr, mid);
// Create the second slice from the pointer advanced by 'mid' and remaining length.
let second_half = slice::from_raw_parts_mut(ptr.add(mid), len - mid);
(first_half, second_half)
}
}
let mut numbers = vec![1, 2, 3, 4, 5, 6, 7, 8];
println!("Original vector: {:?}", numbers);
let (left, right) = custom_split_at_mut(&mut numbers, 4);
println!("Left slice: {:?}", left); // [1, 2, 3, 4]
println!("Right slice: {:?}", right); // [5, 6, 7, 8]
left[0] = 100;
right[0] = 500;
println!("Left slice after modification: {:?}", left);
println!("Right slice after modification: {:?}", right);
println!("Vector after split and modification: {:?}", numbers);
// --- 3. Accessing a mutable static variable ---
// Mutable static variables are globally accessible and can lead to data races.
// Accessing or modifying them must be done within an unsafe block.
static mut COUNTER: i32 = 0;
fn add_to_counter(inc: i32) {
unsafe {
// Direct access to a mutable static variable is unsafe
COUNTER += inc;
}
}
add_to_counter(5);
add_to_counter(3);
unsafe {
// Reading also requires unsafe due to potential data races
println!("COUNTER value: {}", COUNTER);
}
}
```








Unsafe Rust