Unsafe Rust: A Complete Guide to Using It Safely and Effectively

Aarambh Dev Hub
4 min readFeb 25, 2025

--

Rust is a powerful systems programming language known for its memory safety guarantees and strict compiler checks that prevent common issues like segmentation faults, buffer overflows, and data races. However, there are situations where you need to bypass these restrictions for performance optimizations, low-level programming, or interacting with external systems. This is where unsafe Rust comes into play.

In this comprehensive guide, we’ll explore:

  • What unsafe Rust is and why it exists
  • When to use unsafe Rust in your programs
  • The 5 key features unlocked by unsafe
  • Best practices to write safe unsafe Rust
  • Common mistakes and how to avoid undefined behavior

By the end of this guide, you’ll have a solid understanding of how to use unsafe Rust effectively while keeping your code reliable and efficient.

🚀 What is Unsafe Rust?

Rust’s borrow checker ensures that references, pointers, and memory access are always safe. However, in some cases, the compiler cannot verify memory safety, and you need to take manual control. The unsafe keyword allows you to:

  • Dereference raw pointers (*const T and *mut T)
  • Call unsafe functions (including C FFI)
  • Access or modify mutable static variables
  • Implement unsafe traits
  • Work with union fields

💡 Important: Using unsafe does not disable Rust’s safety checks entirely. Instead, it allows operations that Rust normally forbids, and you become responsible for ensuring memory safety.

🛠️ When Should You Use Unsafe Rust?

unsafe should only be used when absolutely necessary. Here are some common use cases where unsafe Rust is essential:

1️⃣ Interacting with C Code (Foreign Function Interface — FFI)

When working with C libraries, Rust cannot guarantee memory safety because it doesn’t control the foreign code. unsafe allows you to call external functions manually.

extern "C" {
fn puts(s: *const i8);
}

fn main() {
let msg = b"Hello from Rust!\0".as_ptr() as *const i8;
unsafe {
puts(msg);
}
}

2️⃣ Raw Pointers for Performance Optimizations

In high-performance applications like game engines or operating systems, you may want to use raw pointers to bypass Rust’s borrow checker overhead.

fn sum_array(arr: &[i32]) -> i32 {
let mut sum = 0;
let ptr = arr.as_ptr(); // Convert to raw pointer
for i in 0..arr.len() {
unsafe {
sum += *ptr.add(i); // Dereference with manual indexing
}
}
sum
}

3️⃣ Low-Level Memory Management (Custom Allocators)

If you’re building memory allocators, lock-free data structures, or operating system kernels, you will need unsafe.

struct MyBox<T> {
ptr: *mut T,
}

impl<T> MyBox<T> {
fn new(value: T) -> Self {
let ptr = Box::into_raw(Box::new(value));
MyBox { ptr }
}

fn get(&self) -> &T {
unsafe { &*self.ptr }
}
}

4️⃣ Working with Static Mutability

Rust doesn’t allow mutable global variables, but unsafe can be used to modify static mut variables.

static mut COUNTER: i32 = 0;  

fn increment_counter() {
unsafe {
COUNTER += 1;
println!("Counter: {}", COUNTER);
}
}

5️⃣ Implementing Unsafe Traits

Some traits cannot be guaranteed as safe (e.g., Send, Sync), so unsafe is required.

unsafe trait MyUnsafeTrait {
fn dangerous_function(&self);
}

✅ Best Practices for Using Unsafe Rust Safely

Since unsafe Rust can introduce undefined behavior, you should follow these best practices:

🔹 1. Minimize the Unsafe Scope

Keep your unsafe blocks as small as possible and wrap them inside safe Rust functions.

fn get_first_element(slice: &[i32]) -> Option<i32> {  
if slice.is_empty() {
None
} else {
Some(unsafe { *slice.as_ptr() }) // Only this line is unsafe
}
}

🔹 2. Use Safe Wrappers Around Unsafe Code

Encapsulate unsafe logic inside a safe API to prevent misuse.

struct SafePtr<T> {
ptr: *mut T,
}

impl<T> SafePtr<T> {
fn new(value: T) -> Self {
let boxed = Box::new(value);
Self { ptr: Box::into_raw(boxed) }
}

fn get(&self) -> &T {
unsafe { &*self.ptr }
}
}

🔹 3. Avoid Dereferencing Null or Dangling Pointers

Always check for null before dereferencing a raw pointer.

fn safe_dereference(ptr: *const i32) -> Option<i32> {
if ptr.is_null() {
None
} else {
Some(unsafe { *ptr })
}
}

🔹 4. Follow Rust’s Unsafe Code Guidelines (UCG)

Refer to the Rust Unsafe Code Guidelines to avoid memory corruption, data races, and undefined behavior.

🔹 5. Use Unsafe in Libraries Only When Necessary

Avoid exposing unsafe functions in public APIs unless absolutely required.

❌ Common Mistakes in Unsafe Rust (Avoid These!)

🚫 Dereferencing an Invalid Pointer

let x: *const i32 = std::ptr::null();
unsafe {
println!("{}", *x); // 🚨 Undefined behavior!
}

🚫 Modifying Data from Multiple Threads Without Synchronization

static mut DATA: i32 = 0;

fn race_condition() {
unsafe {
DATA += 1; // 🚨 Possible data race!
}
}

🚫 Using unsafe for No Good Reason

unsafe {
let x = 42;
println!("{}", x); // ❌ This is already safe Rust!
}

🔥 When to Use Unsafe Rust?

✅ Use unsafe when:

  • Interacting with C libraries (FFI)
  • High-performance optimizations (game engines, databases)
  • Low-level memory management (custom allocators, OS kernels)
  • Implementing unsafe traits

🚫 Avoid unsafe when:

  • It’s just a shortcut to bypass Rust’s safety rules
  • The same thing can be achieved with safe Rust

💡 Conclusion

Rust’s unsafe mode is a powerful tool, but it must be used with caution. Always favor safe Rust first, and only use unsafe when absolutely necessary.

✅ Key Takeaways:

  • unsafe allows manual memory management and FFI calls.
  • Always minimize unsafe blocks and wrap them in safe abstractions.
  • Follow Rust’s Unsafe Code Guidelines to prevent undefined behavior.

🔗 What’s your experience with unsafe Rust? Let’s discuss in the comments! 🚀

By following these guidelines and best practices, you can harness the power of unsafe Rust while maintaining the safety and reliability that Rust is known for. Whether you’re working on high-performance systems, low-level programming, or FFI, this guide will help you use unsafe Rust safely and effectively. Happy coding! 🦀

--

--

Aarambh Dev Hub
Aarambh Dev Hub

Written by Aarambh Dev Hub

Rust developer sharing coding tutorials, backend tips, and insights. Follow for deep dives into Rust, programming challenges, and modern development practices.

No responses yet