Rust is famous for its memory safety guarantees enforced at compile time. However, hidden inside safe Rust is a second language: Unsafe Rust. Unsafe Rust exists because the compiler's static analysis is inherently conservative — when the compiler cannot be sure code is correct, it will reject it, even if the code is actually safe.
Rust nổi tiếng với các đảm bảo an toàn bộ nhớ được thực thi tại thời điểm biên dịch. Tuy nhiên, bên trong Rust an toàn có ẩn giấu một ngôn ngữ thứ hai: Unsafe Rust. Unsafe Rust tồn tại vì phân tích tĩnh của trình biên dịch vốn bảo thủ — khi trình biên dịch không thể chắc chắn rằng mã là đúng, nó sẽ từ chối, ngay cả khi mã thực sự an toàn.
The unsafe keyword does not turn off the borrow checker or disable Rust's other safety checks. It only gives you access to five features that the compiler does not check for memory safety. You still get safety inside an unsafe block.
Từ khóa unsafe không tắt bộ kiểm tra mượn hay vô hiệu hóa các kiểm tra an toàn khác của Rust. Nó chỉ cấp cho bạn quyền truy cập vào năm tính năng mà trình biên dịch không kiểm tra an toàn bộ nhớ. Bạn vẫn được hưởng sự an toàn bên trong một khối unsafe.
Sử dụng unsafe là một hợp đồng giữa bạn và trình biên dịch: bạn đang nói 'Tôi biết mình đang làm gì và tôi đảm bảo các bất biến an toàn được duy trì.' Nếu bạn vi phạm hợp đồng này, bạn sẽ gặp hành vi không xác định.
In unsafe Rust, you can perform five actions that you cannot do in safe Rust. These are called the unsafe superpowers.
Trong unsafe Rust, bạn có thể thực hiện năm hành động mà bạn không thể làm trong Rust an toàn. Đây được gọi là các siêu năng lực unsafe.
- Dereference a raw pointer.
Giải tham chiếu con trỏ thô (raw pointer).
- Call an unsafe function or method.
Gọi hàm hoặc phương thức unsafe.
- Access or modify a mutable static variable.
Truy cập hoặc sửa đổi biến tĩnh có thể thay đổi (mutable static variable).
- Implement an unsafe trait.
Triển khai trait unsafe.
- Access fields of a union.
Truy cập các trường của union.
It is important to understand that unsafe does not mean the code is necessarily dangerous. It means the responsibility for maintaining memory safety guarantees falls on the programmer instead of the compiler.
Điều quan trọng cần hiểu là unsafe không có nghĩa là mã nhất thiết nguy hiểm. Nó có nghĩa là trách nhiệm duy trì các đảm bảo an toàn bộ nhớ thuộc về lập trình viên thay vì trình biên dịch.
Unsafe Rust has two raw pointer types: *const T (immutable pointer) and *mut T (mutable pointer). The asterisk is part of the type name, not the dereference operator. Raw pointers differ from references and smart pointers in several ways.
Unsafe Rust có hai kiểu con trỏ thô: *const T (con trỏ bất biến) và *mut T (con trỏ có thể thay đổi). Dấu sao là một phần của tên kiểu, không phải toán tử giải tham chiếu. Con trỏ thô khác với tham chiếu và con trỏ thông minh ở nhiều điểm.
- Raw pointers are allowed to ignore the borrowing rules: you can have both immutable and mutable pointers or multiple mutable pointers to the same location.
Con trỏ thô được phép bỏ qua quy tắc mượn: bạn có thể có cả con trỏ bất biến và có thể thay đổi hoặc nhiều con trỏ có thể thay đổi đến cùng một vị trí.
- Raw pointers are not guaranteed to point to valid memory.
Con trỏ thô không được đảm bảo trỏ đến bộ nhớ hợp lệ.
- Raw pointers are allowed to be null.
Con trỏ thô được phép là null.
- Raw pointers do not implement any automatic cleanup.
Con trỏ thô không triển khai bất kỳ tự động dọn dẹp nào.
You can create raw pointers in safe code, but you cannot dereference them outside an unsafe block.
Bạn có thể tạo con trỏ thô trong mã an toàn, nhưng bạn không thể giải tham chiếu chúng bên ngoài khối unsafe.
1fn main() {2 let mut num = 5;34 // Creating raw pointers in safe code is fine5 let r1 = &num as *const i32;6 let r2 = &mut num as *mut i32;78 // Dereferencing requires an unsafe block9 unsafe {10 println!("r1 is: {}", *r1);11 println!("r2 is: {}", *r2);12 }1314 // Creating a pointer to an arbitrary memory address15 let address = 0x012345usize;16 let _r = address as *const i32;17 // Dereferencing this would likely cause a segfault!18}Raw pointers are useful when interfacing with C code (FFI), when building safe abstractions the borrow checker cannot understand, or when you need maximum performance and can manually guarantee correctness.
Con trỏ thô hữu ích khi giao tiếp với mã C (FFI), khi xây dựng các trừu tượng an toàn mà bộ kiểm tra mượn không thể hiểu, hoặc khi bạn cần hiệu suất tối đa và có thể đảm bảo tính đúng đắn theo cách thủ công.
Unsafe functions are functions that have preconditions the compiler cannot verify. You declare an unsafe function by adding the unsafe keyword before fn. The entire body of an unsafe function is treated as an unsafe block.
Hàm unsafe là hàm có các điều kiện tiên quyết mà trình biên dịch không thể xác minh. Bạn khai báo hàm unsafe bằng cách thêm từ khóa unsafe trước fn. Toàn bộ thân hàm unsafe được coi là một khối unsafe.
1// An unsafe function — caller must ensure the pointer is valid2unsafe fn dangerous(ptr: *const i32) -> i32 {3 *ptr4}56fn main() {7 let value = 42;8 let ptr = &value as *const i32;910 // Must call unsafe functions inside an unsafe block11 let result = unsafe { dangerous(ptr) };12 println!("Result: {}", result);13}A best practice is to keep unsafe blocks as small as possible. This makes it easier to audit and narrow down the source of any memory bugs.
Một thực hành tốt là giữ các khối unsafe càng nhỏ càng tốt. Điều này giúp dễ dàng kiểm tra và thu hẹp nguồn gốc của bất kỳ lỗi bộ nhớ nào.
1fn main() {2 let mut data = vec![1, 2, 3, 4, 5];3 let ptr = data.as_mut_ptr();45 // Bad: large unsafe block6 // unsafe {7 // let val = *ptr;8 // let processed = val + 10;9 // println!("{}", processed);10 // // ... many more lines ...11 // }1213 // Good: minimal unsafe block14 let val = unsafe { *ptr };15 let processed = val + 10;16 println!("{}", processed);17}Khi bạn gặp lỗi bộ nhớ, bạn chỉ cần kiểm tra các khối unsafe. Giữ chúng nhỏ giúp giảm đáng kể thời gian gỡ lỗi.
Rust can call functions written in other languages through FFI. This always requires unsafe because Rust cannot guarantee the safety of foreign code. You use extern blocks to declare foreign functions.
Rust có thể gọi các hàm viết bằng ngôn ngữ khác thông qua FFI. Điều này luôn yêu cầu unsafe vì Rust không thể đảm bảo tính an toàn của mã ngoại. Bạn sử dụng khối extern để khai báo các hàm ngoại.
1// Declare external functions from the C standard library2extern "C" {3 fn abs(input: i32) -> i32;4 fn strlen(s: *const u8) -> usize;5}67fn main() {8 let x = -5;910 // Calling foreign functions is always unsafe11 let result = unsafe { abs(x) };12 println!("Absolute value of {} is {}", x, result);1314 let s = b"hello\0"; // null-terminated byte string15 let len = unsafe { strlen(s.as_ptr()) };16 println!("Length: {}", len);17}The "C" string after extern specifies the ABI (Application Binary Interface) the foreign function uses. The "C" ABI is the most common and follows the C language calling convention.
Chuỗi "C" sau extern xác định ABI (Application Binary Interface) mà hàm ngoại sử dụng. ABI "C" là phổ biến nhất và tuân theo quy ước gọi hàm của ngôn ngữ C.
You can also allow other languages to call Rust functions. Use extern "C" on the function definition and the #[no_mangle] attribute to prevent the Rust compiler from mangling the function name.
Bạn cũng có thể cho phép các ngôn ngữ khác gọi hàm Rust. Sử dụng extern "C" trên định nghĩa hàm và thuộc tính #[no_mangle] để ngăn trình biên dịch Rust làm rối tên hàm.
1// This function can be called from C or other languages2#[no_mangle]3pub extern "C" fn call_from_c(x: i32) -> i32 {4 x * 25}67// In C, you would declare it as:8// int32_t call_from_c(int32_t x);The most common pattern in Rust is wrapping unsafe code in a safe API. The standard library does this extensively. The split_at_mut function is a classic example.
Mẫu phổ biến nhất trong Rust là bọc mã unsafe trong một API an toàn. Thư viện chuẩn làm điều này rất nhiều. Hàm split_at_mut là một ví dụ kinh điển.
Let us try to implement split_at_mut ourselves. This function takes a mutable slice and splits it into two at a given index. The borrow checker will not let us borrow two different parts of the same slice because it only sees that we are borrowing from the same slice twice.
Hãy thử tự triển khai split_at_mut. Hàm này nhận một slice có thể thay đổi và tách nó thành hai tại một chỉ mục cho trước. Bộ kiểm tra mượn không cho phép chúng ta mượn hai phần khác nhau của cùng một slice vì nó chỉ thấy rằng chúng ta đang mượn từ cùng một slice hai lần.
1use std::slice;23fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {4 let len = values.len();5 let ptr = values.as_mut_ptr();67 assert!(mid <= len);89 // This would NOT compile with safe Rust:10 // (&mut values[..mid], &mut values[mid..])1112 // Safe abstraction using unsafeNotice that split_at_mut has a safe signature — it is not marked unsafe. The unsafe code inside is protected by the assert! check, ensuring the index is within bounds. This is what a safe abstraction means: users do not need to know that unsafe is used internally.
Lưu ý rằng hàm split_at_mut có chữ ký an toàn — nó không được đánh dấu là unsafe. Mã unsafe bên trong được bảo vệ bởi kiểm tra assert!, đảm bảo chỉ mục nằm trong phạm vi. Đây chính là ý nghĩa của trừu tượng an toàn: người dùng không cần biết rằng bên trong có sử dụng unsafe.
Khi tạo trừu tượng an toàn, bạn phải đảm bảo rằng tất cả các đầu vào hợp lệ mà người dùng có thể cung cấp sẽ không gây ra hành vi không xác định. Sử dụng assert!, kiểm tra giới hạn, và các bất biến kiểu dữ liệu để bảo vệ mã unsafe bên trong.
Rust supports global variables with the static keyword. Accessing and modifying mutable static variables is unsafe because multiple threads could access them simultaneously, leading to data races.
Rust hỗ trợ biến toàn cục với từ khóa static. Truy cập và sửa đổi biến tĩnh có thể thay đổi là unsafe vì nhiều luồng có thể truy cập cùng lúc, dẫn đến chạy đua dữ liệu.
1static mut COUNTER: u32 = 0;23fn add_to_count(inc: u32) {4 // Modifying a mutable static requires unsafe5 unsafe {6 COUNTER += inc;7 }8}910fn main() {11 add_to_count(3);12 add_to_count(7);1314 // Reading a mutable static also requires unsafe15 unsafe {16 println!("COUNTER: {}", COUNTER);17 }18}Trong hầu hết các trường hợp, bạn nên ưu tiên sử dụng các kiểu an toàn cho đồng thời như Mutex hoặc AtomicU32 thay vì biến tĩnh có thể thay đổi. Biến tĩnh mut rất dễ gây ra chạy đua dữ liệu.
A trait is unsafe when at least one of its methods has an invariant the compiler cannot verify. You declare an unsafe trait and use unsafe impl to implement it.
Một trait là unsafe khi ít nhất một trong các phương thức của nó có bất biến mà trình biên dịch không thể xác minh. Bạn khai báo trait unsafe và sử dụng unsafe impl để triển khai nó.
1// A trait with safety invariants the compiler cannot check2unsafe trait Zeroable {3 // Types implementing this must be valid when all bits are zero4}56// We promise that i32 is valid when zero-initialized7unsafe impl Zeroable for i32 {}8unsafe impl Zeroable for u64 {}910fn zero_init<T: Zeroable>() -> T {11 unsafe { std::mem::zeroed() }12}1314fn main() {15 let x: i32 = zero_init();16 let y: u64 = zero_init();17 println!("x = {}, y = {}", x, y);18}A union is similar to a struct but all fields share the same memory. Accessing union fields is unsafe because Rust cannot guarantee the type currently stored is the correct one.
Union tương tự struct nhưng tất cả các trường chia sẻ cùng một vùng bộ nhớ. Truy cập trường union là unsafe vì Rust không thể đảm bảo kiểu dữ liệu hiện đang được lưu trữ là chính xác.
1union IntOrFloat {2 i: i32,3 f: f32,4}56fn main() {7 let u = IntOrFloat { i: 42 };89 // Accessing a union field requires unsafe10 let value = unsafe { u.i };11 println!("As integer: {}", value);1213 // Interpreting the same bits as a float14 let u2 = IntOrFloat { f: 3.14 };15 let bits = unsafe { u2.i };16 println!("Float bits as integer: {}", bits);17}Unsafe is a powerful tool but should be used carefully. Here are guidelines for deciding when to use unsafe.
Unsafe là công cụ mạnh mẽ nhưng cần được sử dụng cẩn thận. Dưới đây là các hướng dẫn để quyết định khi nào sử dụng unsafe.
- Use unsafe when you need to interface with C code or the operating system (FFI). This is the most legitimate reason.
Sử dụng unsafe khi bạn cần giao tiếp với mã C hoặc hệ điều hành (FFI). Đây là lý do hợp lệ nhất.
- Use unsafe when the borrow checker is too conservative and you can prove correctness. The split_at_mut example above illustrates this.
Sử dụng unsafe khi bộ kiểm tra mượn quá bảo thủ và bạn có thể chứng minh tính đúng đắn. Ví dụ split_at_mut ở trên minh họa điều này.
- Use unsafe for performance optimizations when truly needed and measured. For example: skipping bounds checks with get_unchecked.
Sử dụng unsafe cho tối ưu hóa hiệu suất khi thực sự cần thiết và đã đo lường được. Ví dụ: bỏ qua kiểm tra giới hạn với get_unchecked.
- Avoid unsafe when a reasonable safe solution exists. Safe code is easier to maintain and less buggy.
Tránh unsafe khi có giải pháp an toàn hợp lý. Mã an toàn dễ bảo trì và ít lỗi hơn.
- Always wrap unsafe in safe abstractions. Do not let unsafe code spread across the codebase.
Luôn bọc unsafe trong trừu tượng an toàn. Không để mã unsafe lan ra khắp codebase.
Hành vi không xác định (UB) trong Rust bao gồm: giải tham chiếu con trỏ null hoặc treo, đọc bộ nhớ chưa khởi tạo, vi phạm quy tắc bí danh con trỏ, tạo tham chiếu null, và chạy đua dữ liệu. Nếu mã unsafe của bạn gây ra bất kỳ điều nào trong số này, chương trình của bạn có hành vi không xác định.
Key Takeaways
Điểm Chính
- unsafe blocks unlock 5 superpowers the borrow checker cannot verifyKhối unsafe mở khóa 5 siêu năng lực mà borrow checker không thể xác minh
- Raw pointers (*const T, *mut T) can be null or danglingCon trỏ thô (*const T, *mut T) có thể null hoặc treo
- FFI uses extern "C" to call functions across language boundariesFFI dùng extern "C" để gọi hàm qua ranh giới ngôn ngữ
- Minimize unsafe scope and wrap it in safe public APIsGiảm thiểu phạm vi unsafe và bọc nó trong API công khai an toàn
Practice
Test your understanding of this chapter
How many unsafe superpowers does Rust provide?
Rust cung cấp bao nhiêu siêu năng lực unsafe?
Creating a raw pointer requires an unsafe block.
Tạo con trỏ thô yêu cầu khối unsafe.
What does the #[no_mangle] attribute do when used with extern functions?
Thuộc tính #[no_mangle] làm gì khi sử dụng với hàm extern?
The unsafe keyword disables all of Rust's safety checks including the borrow checker.
Từ khóa unsafe vô hiệu hóa tất cả kiểm tra an toàn của Rust bao gồm cả bộ kiểm tra mượn.
Complete the code to dereference a raw pointer
Hoàn thành mã để giải tham chiếu con trỏ thô
let val = { *ptr };