পাঠ ২০.১

Unsafe Rust

Unsafe Rust

Rust-এর ভেতরে আরেকটা language লুকিয়ে আছে — unsafe Rust। এটা compile time-এ memory safety guarantee দেয় না। কেন রাখা হয়েছে? এক, static analysis সবসময় conservative — কিছু safe code-ও reject করে। দুই, low-level systems programming-এ direct hardware-এর সাথে কাজ করতে গেলে safe Rust-এর সীমা পেরোতে হয়।

unsafe-এর পাঁচ superpower

unsafe block পাঁচটা অতিরিক্ত সুবিধা দেয়, যা safe Rust-এ নেই:

  1. Raw pointer dereference।
  2. Unsafe function বা method call।
  3. Mutable static variable read/write।
  4. Unsafe trait implement।
  5. union-এর field access।

খেয়াল রাখো: unsafe borrow checker বা type checker বন্ধ করে না। শুধু এই পাঁচটা feature-এ access দেয়। তাই unsafe block-এর ভেতরেও অনেক safety থাকে।

Raw pointer

Raw pointer দু'রকম — *const T (immutable) এবং *mut T (mutable)। Reference-এর তুলনায়:

  • Borrowing rule ignore করতে পারে — same location-এ একসাথে immutable ও mutable।
  • Valid memory point করার guarantee নেই।
  • Null হতে পারে।
  • Automatic cleanup নেই।

Raw pointer তৈরি safe code-এই করা যায়:

fn main() {
    let mut num = 5;

    let r1 = &raw const num;
    let r2 = &raw mut num;
}

Arbitrary memory address থেকেও pointer (বিপজ্জনক):

fn main() {
    let address = 0x012345usize;
    let r = address as *const i32;
}

কিন্তু dereference — শুধু unsafe-এ:

fn main() {
    let mut num = 5;

    let r1 = &raw const num;
    let r2 = &raw mut num;

    unsafe {
        println!("r1 is: {}", *r1);
        println!("r2 is: {}", *r2);
    }
}

Pointer তৈরিতে কোনো ক্ষতি নেই; ক্ষতি তখন — যখন invalid জায়গায় access করো। Raw pointer দিয়ে সহজেই data race বানানো যায় — সাবধান।

Unsafe function

fn main() {
    unsafe fn dangerous() {}

    unsafe {
        dangerous();
    }
}

unsafe fn declaration বলে — এই function-এর কিছু precondition আছে যেগুলো caller-কে নিশ্চিত করতে হবে। Call করতে গেলে unsafe block লাগবে।

Safe abstraction over unsafe

Standard library প্রায়ই unsafe code-কে safe API-তে wrap করে। যেমন split_at_mut — slice-কে দুটো mutable অংশে ভাগ করে। Pure safe Rust-এ এটা লেখা যায় না:

fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = values.len();

    assert!(mid <= len);

    (&mut values[..mid], &mut values[mid..])
}

fn main() {
    let mut vector = vec![1, 2, 3, 4, 5, 6];
    let (left, right) = split_at_mut(&mut vector, 3);
}
compile errortext
error[E0499]: cannot borrow `*values` as mutable more than once at a time

Borrow checker বুঝতে পারে না যে দু'টা slice non-overlapping। Solution — raw pointer দিয়ে unsafe-এ implement করা, কিন্তু পাবলিক API safe রাখা:

use std::slice;

fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = values.len();
    let ptr = values.as_mut_ptr();

    assert!(mid <= len);

    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

fn main() {
    let mut vector = vec![1, 2, 3, 4, 5, 6];
    let (left, right) = split_at_mut(&mut vector, 3);
}

assert! মাঝখানের bound check করছে, raw pointer দিয়ে non-overlapping দু'টা slice তৈরি — তাই function-টা সব input-এ safe। Public signature unsafe না।

বিপরীতে — unsafe-এর misuse:

fn main() {
    use std::slice;

    let address = 0x01234usize;
    let r = address as *mut i32;

    let values: &[i32] = unsafe { slice::from_raw_parts_mut(r, 10000) };
}

এই memory-র ownership আমাদের না — crash বা undefined behavior।

extern — অন্য language-এর function call

C-র মতো অন্য language-এর function call-এর জন্য extern block। FFI (Foreign Function Interface)। এই function-গুলো call unsafe — অন্য language Rust-এর rule মানে না।

unsafe extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

"C" = ABI; বলে দেয় function call convention C-র মতো।

কিছু FFI function-এর memory safety concern নেই — সেগুলো safe keyword দিয়ে mark করা যায়:

unsafe extern "C" {
    safe fn abs(input: i32) -> i32;
}

fn main() {
    println!("Absolute value of -3 according to C: {}", abs(-3));
}

সাবধান — safe mark করলে তুমি promise করছ এটা সত্যিই safe। দায়িত্ব তোমার।

Reverse direction — Rust function অন্য language থেকে call:

#[unsafe(no_mangle)]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}

no_mangle compiler-কে name mangle করতে নিষেধ করে — তাই other language-এ symbol-টা স্বাভাবিক নামে available থাকে। নাম collision-এর ঝুঁকি, তাই unsafe।

Mutable static variable

Static variable — global variable; fixed memory address। Immutable static safe:

static HELLO_WORLD: &str = "Hello, world!";

fn main() {
    println!("value is: {HELLO_WORLD}");
}

নাম SCREAMING_SNAKE_CASE। Static শুধু 'static reference রাখতে পারে।

কিন্তু static mut — multiple thread access করলে data race হতে পারে, তাই unsafe:

static mut COUNTER: u32 = 0;

/// SAFETY: Calling this from more than a single thread at a time is undefined
/// behavior, so you *must* guarantee you only call it from a single thread at
/// a time.
unsafe fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    unsafe {
        // SAFETY: This is only called from a single thread in `main`.
        add_to_count(3);
        println!("COUNTER: {}", *(&raw const COUNTER));
    }
}

Convention: SAFETY: comment দিয়ে invariant লেখা — কী condition মানলে safe।

Practical advice — যেখানে সম্ভব, static mut এড়াও। Chapter 16-এর atomic type, Mutex, ইত্যাদি দিয়ে কাজ চালাও।

Unsafe trait implement

কোনো trait-এ এমন invariant থাকতে পারে যা compiler verify করতে পারে না — সেটা unsafe trait:

unsafe trait Foo {
    // methods go here
}

unsafe impl Foo for i32 {
    // method implementations go here
}

fn main() {}

Marker trait SendSync এর উদাহরণ। Auto-derive করে না এমন type-এ নিজে দাবি করতে চাইলে:

unsafe impl Send for MyType { }
unsafe impl Sync for MyType { }

তুমি promise করছ — এই type thread-এ পাঠানো বা share করা সত্যিই safe।

union-এর field

union struct-এর মতো, কিন্তু একসাথে শুধু একটা field-ই valid থাকে — সব field একই memory share করে। কোন field বর্তমানে set, সেটা compiler জানে না। তাই access unsafe। মূল ব্যবহার — C union-এর সাথে interop।

union MyUnion {
    field1: u32,
    field2: i32,
}

fn main() {
    let mut u = MyUnion { field1: 42 };

    unsafe {
        u.field1 = 10;
        println!("{}", u.field2); // ভুল field — undefined interpretation
    }
}

Miri দিয়ে unsafe code check

Miri — Rust-এর official tool, dynamic-ভাবে undefined behavior detect করে। Borrow checker static; Miri runtime-এ চালিয়ে violation খুঁজে বের করে।

rustup +nightly component add miri
cargo +nightly miri run
cargo +nightly miri test

Limitation — যেই code actually চলে, শুধু সেটাই check হয়। Miri কিছু না ধরা মানে code সঠিক — এটা guarantee না। ভালো test-এর সাথে combine করো।

Best practice

  • unsafe block যত ছোট রাখা যায়, ততই ভালো — audit সহজ।
  • SAFETY: comment-এ invariant লেখো।
  • সম্ভব হলে unsafe-কে safe abstraction-এ wrap করো।
  • Miri চালাও; testing-ও।
  • গভীরে যেতে — The Rustonomicon

এই পাঠ থেকে যা শিখলে

  • unsafe পাঁচটা feature unlock করে — raw pointer deref, unsafe fn, static mut, unsafe trait, union field।
  • Borrow checker চালু থাকে — unsafe safety পুরোপুরি বন্ধ করে না।
  • Raw pointer (*const T, *mut T) তৈরি safe, deref unsafe।
  • extern "C" দিয়ে FFI; অন্য language থেকে Rust call-এর জন্য no_mangle
  • static mut এড়াও — atomic বা Mutex ভালো।
  • Unsafe code-কে safe API-তে wrap করাই idiomatic; Miri দিয়ে dynamic check।