Unsafe Rust: เมื่อคุณต้องควบคุมหน่วยความจำด้วยตัวเอง

Sharing is caring!

Rust เป็นภาษาที่มุ่งเน้นความปลอดภัยของหน่วยความจำ (memory safety) เป็นหลัก โดยใช้ระบบ ownership และ borrow checker เพื่อตรวจสอบความถูกต้องของโค้ดตั้งแต่ compile time

แต่มีบางสถานการณ์ที่คุณจำเป็นต้อง “ก้าวข้ามกฎความปลอดภัย” เพื่อทำงานระดับล่าง เช่น การเข้าถึง pointer โดยตรง การจัดการหน่วยความจำแบบ manual หรือ FFI (Foreign Function Interface)

Rust มีทางออกให้คุณผ่าน unsafe ซึ่งช่วยให้คุณควบคุมหน่วยความจำได้ด้วยตนเอง แต่ก็ต้องใช้ด้วยความระมัดระวัง


อะไรคือ unsafe Rust?

unsafe Rust ไม่ใช่โหมดใหม่ของภาษา แต่เป็นพื้นที่ที่ compiler อนุญาตให้คุณทำสิ่งที่ปกติแล้วผิดกฎ เช่น:

  • Dereference raw pointer
  • เรียก unsafe function
  • เข้าถึง mutable static
  • Implement unsafe trait
  • Access union field
let x: i32 = 42;
let r: *const i32 = &x;

unsafe {
    println!("Value: {}", *r); // Dereference raw pointer
}

ภายนอก unsafe block ยังคงถูกตรวจสอบตามปกติ Compiler จะไม่รับผิดชอบถ้าเกิดปัญหาภายใน unsafe block

ทำไมต้องใช้ unsafe?

  • ทำงานกับ pointer/raw memory โดยตรง
  • Performance optimization แบบ low-level
  • Call C หรือภาษาอื่นผ่าน FFI
  • เขียน low-level abstraction เช่น allocator, concurrency primitive

Raw Pointer

Raw pointer ใน Rust คือ *const T และ *mut T

let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

unsafe {
    println!("{}", *r1);
    *r2 = 10;
}

ต่างจาก reference ทั่วไป compiler จะไม่ตรวจสอบ lifetime หรือ borrow rule ให้

การเขียน FFI ด้วย unsafe

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

fn main() {
    unsafe {
        println!("abs(-3) = {}", abs(-3));
    }
}

เราสามารถใช้ extern block เพื่อเรียก C function ได้โดย unsafe

การจัดการหน่วยความจำแบบ Manual

use std::alloc::{alloc, dealloc, Layout};

fn main() {
    let layout = Layout::new::<u32>();
    unsafe {
        let ptr = alloc(layout) as *mut u32;
        *ptr = 99;
        println!("value = {}", *ptr);
        dealloc(ptr as *mut u8, layout);
    }
}

การใช้ allocator โดยตรงต้องระบุ layout และ deallocate เอง

Implement Unsafe Trait

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

unsafe impl MyUnsafeTrait for i32 {
    fn dangerous(&self) {
        println!("Calling unsafe on {}", self);
    }
}

Static Mutable Variable

static mut COUNTER: u32 = 0;

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

เนื่องจาก static mut ไม่ปลอดภัยต่อการ race condition จึงต้องใช้ใน unsafe block

หลักการใช้ unsafe อย่างปลอดภัย

  • จำกัด unsafe block ให้แคบที่สุด
  • ห่อ unsafe ไว้ใน abstraction ที่ปลอดภัย (safe wrapper)
  • อ่านเอกสารจาก unsafe-code-guidelines
  • ใช้เครื่องมือช่วยวิเคราะห์ เช่น miri, valgrind

Safe Abstraction Example

fn get_first_element(arr: &[i32]) -> Option<i32> {
    unsafe {
        if arr.len() > 0 {
            Some(*arr.as_ptr())
        } else {
            None
        }
    }
}

เราห่อ unsafe ในฟังก์ชันปลอดภัยโดยควบคุมเงื่อนไขก่อน dereference

ข้อดีของ unsafe

  • เข้าถึง hardware หรือ memory ได้โดยตรง
  • ทำ optimization ลึก ๆ ได้
  • สร้าง library ที่ reusable ได้ เช่น Vec, Mutex ก็ใช้ unsafe

ข้อเสีย

  • เกิด segmentation fault, memory leak, data race ได้
  • ยากต่อการ debug และ test
  • ขัดกับหลัก safety ที่ Rust พยายามรักษา

สรุป

Unsafe Rust ไม่ได้แปลว่าไม่ปลอดภัยเสมอไป — มันคือพื้นที่ที่คุณต้องรับผิดชอบความปลอดภัยด้วยตนเอง

มันมีพลังมหาศาลในการควบคุม low-level และ performance-critical แต่ก็ต้องใช้ด้วยความเข้าใจลึกซึ้ง

สุดท้าย หากคุณสามารถออกแบบ abstraction ที่ใช้ unsafe ภายใน แต่ปลอดภัยภายนอก — คุณได้ใช้ Rust อย่างแท้จริง

Leave a Reply

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *