Rust unsafe
用途
本技能引导代理编写、审查和推理 unsafe Rust:哪些操作需要 unsafe、如何编写安全抽象、审计模式、常见陷阱,以及何时应当使用 unsafe。
触发场景
“何时需要在 Rust 中使用 unsafe?”
“如何为 unsafe 代码编写安全抽象?”
“如何审计一个 unsafe 块?”
“Rust 中原始指针的规则是什么?”
“transmute 做什么,何时是安全的?”
“如何正确实现 UnsafeCell?”
工作流
- 五类 unsafe 超级能力
unsafe 恰好授予安全 Rust 中不具备的五种能力:
解引用原始指针(*const T、*mut T)
调用 unsafe 函数(包括 extern "C" 函数)
访问或修改可变静态变量
实现 unsafe trait(Send、Sync)
访问 union 的字段
Rust 中其他一切——包括内存分配、借用、闭包——即使在 unsafe 块内也遵循安全规则。
- 原始指针
创建原始指针(安全——尚未解引用) let x = 42u32; let ptr: *const u32 = &x; let mut_ptr: *mut u32 = &mut some_val as *mut u32;
空指针 let null: *const u32 = std::ptr::null(); let null_mut: *mut u32 = std::ptr::null_mut();
解引用(unsafe) let val = unsafe { *ptr };
空值检查 if !ptr.is_null() { let val = unsafe { *ptr }; }
偏移(计算安全,解引用 unsafe) let arr = [1u32, 2, 3, 4, 5]; let p = arr.as_ptr(); let third = unsafe { *p.add(2) }; // arr[2] let also_third = unsafe { *p.offset(2) };
从原始部件构造切片 let slice: &[u32] = unsafe { std::slice::from_raw_parts(p, arr.len()) };
健全解引用原始指针的规则:
指针必须非空
指针必须对 T 对齐
内存必须已针对 T 初始化
不得违反别名规则(同一位置只能有一个 &mut)
内存在引用生命周期内必须有效
- unsafe 函数与 trait
声明 unsafe 函数(调用者必须维护不变量)
/// # Safety
/// ptr 必须非空且按 T 对齐,并指向已初始化的数据。
/// 调用者必须确保同一位置不存在其他可变引用。
unsafe fn read_ptr<T>(ptr: *const T) -> T {
ptr.read() // ptr::read 是 unsafe
}
调用 unsafe 函数 let val = unsafe { read_ptr(some_ptr) };
unsafe trait——实现者必须维护安全不变量 unsafe trait MyUnsafeTrait { fn operation(&self); }
实现 unsafe trait 是 unsafe 的 unsafe impl MyUnsafeTrait for MyType { fn operation(&self) { /* 必须维护该 trait 的不变量 */ } }
Send 与 Sync // Send:类型可被移动到另一个线程 // Sync:类型可在线程间共享(&T 是 Send) unsafe impl Send for MyType {} unsafe impl Sync for MyType {}
- 在 unsafe 之上构建安全抽象
黄金法则:unsafe 块应当小、隔离,并包裹在维护不变量的安全 API 中
pub struct MyVec<T> { ptr: *mut T, len: usize, cap: usize, }
impl<T> MyVec<T> { pub fn new() -> Self { MyVec { ptr: std::ptr::NonNull::dangling().as_ptr(), len: 0, cap: 0 } }
// 安全的公开 API
pub fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
// Safety: index < len 保证 ptr+index 在界内且已初始化
Some(unsafe { &*self.ptr.add(index) })
} else {
None
}
}
// # Safety 注释记录不变量
pub fn push(&mut self, val: T) {
if self.len == self.cap {
self.grow();
}
// Safety: grow() 后 len < cap,因此 ptr+len 在界内
unsafe { self.ptr.add(self.len).write(val) };
self.len += 1;
}
}
// 实现 Drop 以清理 impl<T> Drop for MyVec<T> { fn drop(&mut self) { // Safety: ptr 按此布局分配,且所有元素均已初始化 unsafe { std::ptr::drop_in_place(std::slice::from_raw_parts_mut(self.ptr, self.len)); std::alloc::dealloc(self.ptr as *mut u8, std::alloc::Layout::array::<T>(self.cap).unwrap()); } } }
- transmute
// transmute:将一种类型的位重新解释为另一种类型 // 两种类型必须大小相同
// 安全用法: let x: u32 = 0x3f800000; let f: f32 = unsafe { std::mem::transmute(x) }; // 位 → 浮点
// 转换切片指针(若类型大小/对齐相同则健全) let bytes: &[u8] = &[0x00, 0x00, 0x80, 0x3f]; let floats: &[f32] = unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const f32, 1) };
// 有安全替代方案时优先使用: let f = f32::from_bits(x); // 代替用 transmute 转换浮点位 let n = u32::from_ne_bytes(bytes); // 代替用 transmute 转换字节数组
常见 transmute 陷阱:
大小错误(编译错误,但泛型类型需注意)
构造无效的枚举值
构造生命周期错误的引用
- UnsafeCell——内部可变性
use std::cell::UnsafeCell;
// UnsafeCell 是唯一能通过共享引用进行修改的方式 struct MyCell<T> { value: UnsafeCell<T>, }
impl<T: Copy> MyCell<T> { fn new(val: T) -> Self { MyCell { value: UnsafeCell::new(val) } }
fn get(&self) -> T {
// Safety: 单线程,无并发修改
unsafe { *self.value.get() }
}
fn set(&self, val: T) {
// Safety: 单线程,无未释放的引用
unsafe { *self.value.get() = val }
}
}
- unsafe 审计清单
审查 unsafe 块时:
是否有 // Safety: 注释解释不变量?
原始指针是否非空?
原始指针是否对目标类型正确对齐?
内存是否已初始化?
引用的生命周期是否有效?
是否遵守别名规则(没有同时存在的 & 和 &mut)?
对于 extern "C":C 不变量是否已记录并验证?
对于 Send/Sync 实现:线程安全是否确实得到保证?
unsafe 块是否尽可能小?
是否有在 Miri 下运行 unsafe 代码的测试?
- 何时使用 unsafe
在使用 unsafe 之前,检查: ├── std 是否有安全 API?(Vec、Box、Arc——通常有) ├── 是否有 crate 能处理?(memmap2、nix、windows-sys) ├── 能否重构以避免它? └── 性能收益是否经过测量且显著?
正当用途: ├── 与 C 库的 FFI(extern "C") ├── 操作系统级 API(syscalls、mmap、ioctl) ├── 性能关键的数据结构(自定义分配器、SoA) ├── 硬件访问(嵌入式、驱动) └── 实现安全抽象(标准库本身)
关于 unsafe 模式与审计示例,参见 references/unsafe-patterns.md。
相关技能
使用 skills/rust/rust-sanitizers-miri——Miri 是测试 unsafe 代码的必备工具
使用 skills/rust/rust-ffi 处理 FFI 场景中的 unsafe 模式
使用 skills/rust/rust-debugging 调试 unsafe 代码中的 panic
使用 skills/low-level-programming/memory-model 了解 unsafe 中的别名与内存序