Unsafe Rust

Back

Loading concept...

🔓 Unsafe Rust: Opening the Secret Door

Imagine Rust as a super-safe playground with padded floors, guardrails, and a watchful teacher. But sometimes, you need to go into the “staff only” room to fix something special. That’s Unsafe Rust!


🎭 The Story: The Safe Kingdom & The Secret Workshop

Once upon a time, there was a kingdom called Rustland. In Rustland, everyone was super safe. The King (the Rust Compiler) made sure nobody could hurt themselves.

But sometimes, the kingdom needed to:

  • Fix things the King couldn’t see
  • Talk to other kingdoms (like C-land)
  • Do special tricks that needed extra care

For these moments, there was a Secret Workshop with a special key: unsafe.


🚪 Unsafe Blocks: The Secret Door

An unsafe block is like saying: “Hey King, I’m going into the workshop. I’ll be careful, I promise!”

fn main() {
    // Safe playground
    let x = 5;

    // Opening the secret door
    unsafe {
        // Here, YOU are responsible
        // The King trusts you!
    }
}

What Can You Do Inside?

Inside the secret workshop, you can do 5 special things:

  1. ✨ Dereference raw pointers
  2. 📞 Call unsafe functions
  3. 🌍 Access mutable static variables
  4. 🔧 Implement unsafe traits
  5. 🏗️ Access union fields

💡 Remember: The code is “unsafe” but not “bad”. You’re just telling Rust: “I’ve checked this myself!”


🎯 Raw Pointers: Arrows Without Safety Nets

In safe Rust, we have references (& and &mut). They’re like arrows with rubber tips - safe!

Raw pointers are like real arrows - powerful but need care!

fn main() {
    let number = 42;

    // Safe reference (rubber arrow)
    let safe_ref = &number;

    // Raw pointer (real arrow)
    let raw_ptr = &number as *const i32;

    println!("Safe: {}", safe_ref);
    // Can't use raw_ptr yet - not in unsafe!
}

Two Types of Raw Pointers

Type Symbol Meaning
Immutable *const T Look but don’t touch
Mutable *mut T Can read AND write
fn main() {
    let mut value = 10;

    // Read-only arrow
    let read_ptr: *const i32 = &value;

    // Read-write arrow
    let write_ptr: *mut i32 = &mut value;
}

Why Use Raw Pointers?

  • 🤝 Talking to C code
  • 🏎️ Super-fast operations
  • 🔧 Building special data structures

🎪 Dereferencing Raw Pointers

Dereference means: “Give me what’s at the end of this arrow!”

With safe references, Rust checks everything. With raw pointers, YOU check!

fn main() {
    let treasure = 100;
    let map: *const i32 = &treasure;

    unsafe {
        // Following the map to find treasure!
        let found = *map;
        println!("Found: {}!", found);
    }
}

⚠️ The Danger Zone

fn main() {
    let bad_ptr: *const i32 = 0x12345 as *const i32;

    unsafe {
        // ☠️ DANGER! This points to nowhere!
        // let crash = *bad_ptr; // DON'T DO THIS
    }
}

Safe Pattern: Check First!

fn main() {
    let data = 42;
    let ptr: *const i32 = &data;

    unsafe {
        if !ptr.is_null() {
            println!("Safe to read: {}", *ptr);
        }
    }
}

🛠️ Unsafe Functions: Special Tools

Some tools are so powerful they need the unsafe label.

Creating an Unsafe Function

unsafe fn laser_beam(target: *mut i32) {
    // Powerful but dangerous!
    *target = 999;
}

fn main() {
    let mut robot = 0;
    let ptr = &mut robot as *mut i32;

    unsafe {
        laser_beam(ptr);
    }

    println!("Robot power: {}", robot);
}

Why Mark Functions Unsafe?

It’s a promise to callers: “Check things before calling me!”

graph TD A["Your Code"] --> B{Call unsafe fn?} B -->|Yes| C["Must use unsafe block"] B -->|No| D["Normal call"] C --> E["You checked everything"] E --> F["Function runs safely"]

🏰 Safe Abstractions: The Magic Wrapper

The best pattern in Rust: Unsafe inside, safe outside!

It’s like a power plant: dangerous machinery inside, but you just flip a switch outside.

Example: A Safe Wrapper

pub struct SafeBox {
    data: *mut i32,
}

impl SafeBox {
    pub fn new(value: i32) -> Self {
        let ptr = Box::into_raw(Box::new(value));
        SafeBox { data: ptr }
    }

    // Safe to call! Unsafe is hidden inside
    pub fn get(&self) -> i32 {
        unsafe { *self.data }
    }

    pub fn set(&mut self, value: i32) {
        unsafe { *self.data = value }
    }
}

impl Drop for SafeBox {
    fn drop(&mut self) {
        unsafe {
            drop(Box::from_raw(self.data));
        }
    }
}

Using the Safe Wrapper

fn main() {
    let mut box1 = SafeBox::new(10);
    println!("Value: {}", box1.get()); // Safe!
    box1.set(20);                       // Safe!
    println!("New value: {}", box1.get());
}

No unsafe needed by users! 🎉


🌍 Extern Functions: Talking to Other Languages

Rust can call functions from C and other languages using extern!

Calling C Functions

// Declaring a C function
extern "C" {
    fn abs(input: i32) -> i32;
    fn sqrt(input: f64) -> f64;
}

fn main() {
    let number = -5;

    unsafe {
        let result = abs(number);
        println!("Absolute value: {}", result);

        let root = sqrt(16.0);
        println!("Square root: {}", root);
    }
}

Letting C Call Rust

#[no_mangle]
pub extern "C" fn greet() -> i32 {
    println!("Hello from Rust!");
    42
}
Part Meaning
#[no_mangle] Keep my name as-is
extern "C" Use C’s way of calling

🔗 FFI Basics: The Bridge Between Worlds

FFI = Foreign Function Interface

It’s like being a translator between Rust and C!

graph TD A["Rust Program"] -->|FFI| B["C Library"] B -->|FFI| A A --> C["Safe Rust Code"] B --> D["System Functions"]

Simple FFI Example

use std::os::raw::c_int;

extern "C" {
    fn rand() -> c_int;
}

fn get_random() -> i32 {
    unsafe { rand() }
}

fn main() {
    let num = get_random();
    println!("Random: {}", num);
}

Common C Types in Rust

C Type Rust Type
int c_int
char c_char
void* *mut c_void

🗃️ Static Mut Variables: The Shared Notebook

A static mut is like a notebook everyone can read and write.

The Problem

static mut COUNTER: i32 = 0;

fn add_one() {
    unsafe {
        COUNTER += 1;
    }
}

fn main() {
    add_one();
    add_one();

    unsafe {
        println!("Count: {}", COUNTER);
    }
}

⚠️ Why Is This Dangerous?

With multiple threads, two people might write at the same time!

graph TD A["Thread 1"] -->|Read: 5| C["COUNTER"] B["Thread 2"] -->|Read: 5| C A -->|Write: 6| C B -->|Write: 6| C C --> D["Lost update! Should be 7"]

Safer Alternative: Atomic Types

use std::sync::atomic::{AtomicI32, Ordering};

static SAFE_COUNTER: AtomicI32 = AtomicI32::new(0);

fn main() {
    SAFE_COUNTER.fetch_add(1, Ordering::SeqCst);
    SAFE_COUNTER.fetch_add(1, Ordering::SeqCst);

    println!("Safe count: {}",
        SAFE_COUNTER.load(Ordering::SeqCst));
}

🎓 The Golden Rules

✅ DO:

  1. Keep unsafe blocks small
  2. Document why it’s safe
  3. Create safe wrappers around unsafe code
  4. Test thoroughly

❌ DON’T:

  1. Use unsafe just to “make it compile”
  2. Forget to free memory
  3. Create data races
  4. Dereference unknown pointers

🌟 Summary: Your Unsafe Toolkit

Tool What It Does Safety Level
unsafe {} Opens the secret door 🔓 You’re in charge
*const T Read-only raw pointer ⚠️ Check before use
*mut T Read-write raw pointer ⚠️ Extra careful!
unsafe fn Dangerous function ⚠️ Caller checks
Safe wrapper Unsafe inside, safe outside ✅ Best practice!
extern "C" Call C functions ⚠️ Trust the library
static mut Shared mutable state ⚠️ Thread danger!

🚀 You Did It!

You now understand the secret workshop of Rust!

Remember: unsafe doesn’t mean “bad code”. It means:

“I, the programmer, have verified this is correct.”

Use this power wisely, wrap it safely, and your Rust code will be both powerful and reliable! 💪


Next: Practice with the interactive exercises to feel the power of unsafe Rust!

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.