unsafe Rust
问题
unsafe 在 Rust 中意味着什么?什么时候需要使用 unsafe?
答案
unsafe 的 5 种超能力
在 unsafe 块/函数中,你可以做以下 5 件事:
- 解引用裸指针
- 调用 unsafe 函数
- 访问/修改可变静态变量
- 实现 unsafe trait
- 访问 union 的字段
// 1. 裸指针
let x = 42;
let ptr = &x as *const i32;
unsafe {
println!("{}", *ptr); // 解引用裸指针
}
// 2. 调用 unsafe 函数
unsafe fn dangerous() { /* ... */ }
unsafe { dangerous(); }
// 3. 可变静态变量
static mut COUNTER: u32 = 0;
unsafe { COUNTER += 1; }
// 4. unsafe trait
unsafe trait MyTrait { }
unsafe impl MyTrait for MyType { }
// 5. Union
union FloatBits {
f: f32,
bits: u32,
}
let u = FloatBits { f: 1.0 };
unsafe { println!("bits: {:#x}", u.bits); }
unsafe 不意味着"不安全"
unsafe 意味着"编译器无法验证这段代码的安全性,由程序员负责"。好的 unsafe 代码应该:
- 最小化 unsafe 范围
- 封装在安全抽象后面
- 详细文档说明安全不变量
// 标准库的 Vec::get_unchecked 就是安全封装的例子
pub fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
Some(unsafe { self.get_unchecked(index) }) // unsafe 被安全 API 封装
} else {
None
}
}
常见的 unsafe 使用场景
FFI(外部函数接口):
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
let result = unsafe { abs(-3) };
println!("{}", result); // 3
}
性能关键路径:
// 跳过边界检查(确认 index 合法时)
let slice = &[1, 2, 3, 4, 5];
let value = unsafe { *slice.get_unchecked(2) }; // 比 slice[2] 快(无边界检查)
未定义行为(UB)
以下是 Rust 中的 UB,即使在 unsafe 中也绝对不能做:
- 解引用空指针或悬垂指针
- 创建未对齐的引用
- 违反别名规则(同时存在可变和不可变引用)
- 产生无效的原始值(如
bool不是 0 或 1) - 数据竞争
常见面试问题
Q1: unsafe 和 C/C++ 的区别?
答案:
在 C/C++ 中,所有代码都是"unsafe"的——编译器不保证内存安全。Rust 的 unsafe 是有限的、可审计的——你可以搜索所有 unsafe 块来审查安全性。安全代码中的 bug 不会导致未定义行为。
Q2: 什么是"安全抽象"?
答案:
安全抽象是指用安全的 API 封装 unsafe 代码,使得外部用户无论怎么调用都不会触发未定义行为。标准库大量使用这种模式:Vec、String、HashMap 内部都有 unsafe,但对外 API 完全安全。
Q3: *const T 和 *mut T 与引用有什么区别?
答案:
| 特性 | &T / &mut T | *const T / *mut T |
|---|---|---|
| 空值 | 不可能 | 可能 |
| 对齐 | 保证 | 不保证 |
| 悬垂 | 不可能 | 可能 |
| 别名规则 | 编译器检查 | 不检查 |
| 解引用 | 安全 | 需要 unsafe |
裸指针主要用于 FFI 和实现底层数据结构。