Unsafe Rust và Giao tiếp C/C++ với FFI
Làm chủ Unsafe Rust: 5 siêu năng lực của unsafe, giải tham chiếu raw pointers, gọi hàm C thông qua Foreign Function Interface (FFI) và đóng gói API an toàn.
Bài thứ hai trong Phần 3: Advanced Systems & Production Rust. Mặc dù Rust nổi tiếng nhờ hệ thống kiểm tra an toàn bộ nhớ tĩnh khắt khe, trong lập trình hệ thống thực tế — như viết hệ điều hành, driver phần cứng, nhúng thư viện C/C++ kế thừa hoặc tối ưu hóa hiệu năng cực hạn — máy tính cần thực hiện những thao tác mà trình biên dịch không thể tự chứng minh tính an toàn.
Rust giải quyết vấn đề này bằng khối unsafe.
graph TD
A["Unsafe Rust"] --> B["5 Siêu Năng Lực Unsafe"]
B --> B1["Giải tham chiếu Raw Pointers (*const T, *mut T)"]
B --> B2["Gọi hàm unsafe hoặc hàm FFI ngoại lai"]
B --> B3["Triển khai Unsafe Trait"]
B --> B4["Truy cập / Sửa đổi biến Mutable Static"]
B --> B5["Truy cập các trường của Union"]
A --> C["Giao tiếp FFI (C ABI)"]
A --> D["Mô hình: Safe Abstraction bọc Unsafe Code"]
Giải thích đơn giản (ELI5): Tấm kính chắn của người thợ hàn#
- Safe Rust: Giống như công tắc đèn trong phòng khách. Bạn nhấn nút bật/tắt an toàn 100%, không lo bị điện giật vì toàn bộ dây điện đã được bọc kín và kiểm tra kỹ lưỡng.
- Unsafe Rust: Giống như việc mở tủ điện kỹ thuật và tháo găng tay bảo hộ. Trình biên dịch sẽ nói: “Tôi không thể kiểm tra dây nào có điện ở đây, tôi trao toàn quyền kiểm soát và trách nhiệm cho bạn!”
unsafekhông tắt borrow checker và cũng không biến Rust thành C; nó chỉ mở khóa đúng 5 thao tác đặc biệt mà bạn cần phải tự đảm bảo tính đúng đắn.
1. Con trỏ thô: Raw Pointers (*const T và *mut T)#
Khác với tham chiếu (&T và &mut T), raw pointers:
- Được phép bỏ qua các quy tắc mượn (borrowing rules), có thể có nhiều con trỏ đọc và ghi vào cùng một địa chỉ.
- Không đảm bảo trỏ tới vùng nhớ hợp lệ (có thể bị
nullhoặc trỏ vào ô nhớ rác). - Không tự động dọn dẹp bộ nhớ (không tự động Drop).
[!NOTE] Việc tạo raw pointer là hoàn toàn an toàn. Chỉ khi bạn giải tham chiếu (
*pointer) để đọc/ghi thì mới bắt buộc phải đặt trong khốiunsafe.
fn main() {
let mut num = 42;
// Tạo raw pointers từ tham chiếu (Safe)
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
// Giải tham chiếu raw pointers (Unsafe)
unsafe {
println!("r1 trỏ tới giá trị: {}", *r1);
*r2 = 100;
println!("r2 sau khi thay đổi: {}", *r2);
}
}rust2. Gọi hàm Unsafe và đóng gói Safe Abstraction#
Triết lý chuẩn mực trong Rust là: Dùng unsafe ở tầng lõi bên trong, nhưng bọc ngoài bằng một Safe API công khai để người dùng không cần dùng từ khóa unsafe.
Ví dụ: Hàm split_at_mut trích xuất 2 lát cắt rời nhau từ cùng 1 vector:
use std::slice;
fn split_at_mut<T>(values: &mut [T], mid: usize) -> (&mut [T], &mut [T]) {
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);
println!("Left: {left:?}, Right: {right:?}");
}rust3. Giao tiếp với C qua Foreign Function Interface (FFI)#
Rust có thể gọi trực tiếp các hàm từ thư viện C mà không tốn chi phí trung gian (zero-overhead C ABI):
Gọi hàm thư viện chuẩn C abs và snprintf#
use std::ffi::c_int;
// Khai báo giao diện hàm ngoại lai (C ABI)
extern "C" {
fn abs(input: c_int) -> c_int;
}
fn main() {
unsafe {
let result = abs(-42);
println!("Giá trị tuyệt đối từ thư viện C: {result}");
}
}rustXuất hàm Rust cho C/C++ gọi lại#
Bạn có thể viết thư viện bằng Rust và biên dịch thành shared library (.so, .dylib, .dll) cho C, Python hoặc Node.js gọi:
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
a + b
}rust#[no_mangle]: Tắt tính năng đổi tên hàm của compiler để trình liên kết (linker) C có thể tìm thấy đúng tên hàmrust_add.extern "C": Sử dụng quy ước gọi hàm chuẩn của C (C calling convention).
Quy tắc vàng khi làm việc với Unsafe#
- Thu hẹp phạm vi
unsafe: Giữ khốiunsafecàng nhỏ càng tốt. - Luôn kiểm tra bất biến (Invariants): Dùng
assert!kiểm tra biên trước khi dùng raw pointers. - Sử dụng công cụ kiểm tra: Sử dụng
Miri(cargo miri test) để phát hiện hành vi không xác định (Undefined Behavior) trong khối unsafe lúc chạy thử nghiệm.
Tóm tắt#
unsafemở khóa 5 thao tác cấp thấp khi bạn cần tương tác trực tiếp với phần cứng, bộ nhớ hoặc FFI.- Raw pointers (
*const T,*mut T) cho phép thao tác con trỏ tự do nhưng cần lập trình viên tự bảo vệ an toàn. - FFI cho phép Rust tương tác hai chiều mượt mà với thế giới mã nguồn C/C++.
- Luôn bao bọc unsafe code bên trong các safe abstractions an toàn, tiện lợi.