其他
编写或审查 Rust unsafe 代码时:用五个…
在编写、审查或调试 unsafe Rust、裸指针、transmute、UnsafeCell、Send/Sync 不安全实现及 FFI 时,用于安全封装和审计底层操作。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 48/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 55/100 ×0.15
适用场景
编写或审查 Rust unsafe 代码通用
解决思路
- 先记住 unsafe 只赋予五种能力:1) 解引用裸指针(*const T、*mut T);2) 调用 unsafe 函数(包括 extern "C" 函数);3) 访问或修改可变静态变量;4) 实现 unsafe trait(Send、Sync);5) 访问 union 的字段。Rust 中其他操作(内存分配、借用、闭包等)即使在 unsafe 块内也仍遵循安全规则。
- 裸指针操作:创建裸指针是安全的(尚未解引用),如 let x = 42u32; let ptr: *const u32 = &x; let mut_ptr: *mut u32 = &mut some_val as *mut u32;;空指针用 std::ptr::null() 和 std::ptr::null_mut();解引用必须写 unsafe { *ptr };解引用前用 if !ptr.is_null() 检查;用 p.add(2) 或 p.offset(2) 计算偏移(计算安全,解引用 unsafe);用 std::slice::from_raw_parts(p, arr.len()) 从裸部件构造 slice。
- 确保裸指针解引用 sound 的规则:指针必须非空;必须按 T 对齐;内存必须已为 T 初始化;不得违反别名规则(同一位置只能有一个 &mut);内存在引用生命周期内必须有效。
- unsafe 函数与 trait:把 unsafe fn read_ptr<T>(ptr: *const T) -> T 声明为 unsafe,并在 # Safety 文档中写明调用者必须保证 ptr 非空、按 T 对齐、指向已初始化数据,且没有其他可变引用指向同一位置;调用时用 unsafe { read_ptr(some_ptr) }。
- 定义 unsafe trait MyUnsafeTrait 时要求实现者维护安全不变量;实现 unsafe trait 必须写 unsafe impl MyUnsafeTrait for MyType,并确保 operation 等方法维护这些不变量。
- Send/Sync:Send 表示类型可移动到其他线程,Sync 表示类型可在线程间共享(&T 是 Send);只有确实保证线程安全时才写 unsafe impl Send for MyType {} 或 unsafe impl Sync for MyType {}。
- 安全抽象黄金法则:unsafe 块应尽可能小、隔离,并包装在安全 API 中,由该 API 维护不变量。示例:pub struct MyVec<T> { ptr: *mut T, len: usize, cap: usize };new 返回 MyVec { ptr: std::ptr::NonNull::dangling().as_ptr(), len: 0, cap: 0 }。
- 为安全抽象编写安全公开 API:get 用 if index < self.len { Some(unsafe { &*self.ptr.add(index) }) } else { None },并用 // Safety: 注释说明 index < len 保证 ptr+index 在边界内且已初始化;push 在 if self.len == self.cap { self.grow(); } 后执行 unsafe { self.ptr.add(self.len).write(val) }; self.len += 1;,并用 // Safety: 注释说明 grow() 后 len < cap,因此 ptr+len 在边界内。
- 为安全抽象实现 Drop 清理:在 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());用 // Safety: 注释说明 ptr 是按该布局分配且所有元素已初始化。
- transmute:用 std::mem::transmute 把一个类型的位重新解释为另一个类型,两个类型必须有相同大小;安全用法示例:let x: u32 = 0x3f800000; let f: f32 = unsafe { std::mem::transmute(x) };;把 slice 指针转换为同大小/对齐类型:用 std::slice::from_raw_parts(bytes.as_ptr() as *const f32, 1)。
- 优先用安全替代 transmute:浮点位转换用 f32::from_bits(x) 或类似方法;字节数组转换用 u32::from_ne_bytes(bytes) 等。
- 避免 transmute 常见陷阱:类型大小错误(普通类型会编译错误,但泛型类型要检查);创建无效枚举值;创建生命周期错误的引用。
- UnsafeCell 用于内部可变性:它是唯一能通过共享引用修改数据的途径。定义 struct MyCell<T> { value: UnsafeCell<T> },new 用 UnsafeCell::new(val)。
- 实现 MyCell<T> 的 get 和 set:get 写 unsafe { *self.value.get() },并用 // Safety: 注释说明单线程且无并发修改;set 写 unsafe { *self.value.get() = val },并用 // Safety: 注释说明单线程且没有未结束的引用。
- 审查 unsafe 块时逐项执行审计清单:是否有 // Safety: 注释解释不变量;裸指针是否非空;裸指针是否按目标类型正确对齐;内存是否初始化;引用生命周期是否有效;是否遵守别名规则(没有同时存在的 & 和 &mut);对 extern "C",C 不变量是否文档化并验证;对 Send/Sync impl,线程安全是否确实保证;unsafe 块是否尽可能小;unsafe 代码是否有 Miri 测试。
- 决定是否使用 unsafe 前逐项检查:std 是否已有安全 API(Vec、Box、Arc 通常有);是否有 crate 处理(memmap2、nix、windows-sys);能否重构避免 unsafe;性能收益是否已测量且显著。
- unsafe 的合法用途包括:FFI 到 C 库(extern "C");OS 级 API(syscalls、mmap、ioctl);性能关键数据结构(自定义分配器、SoA);硬件访问(嵌入式、驱动);实现安全抽象(标准库本身)。
- 需要 unsafe 模式和审计示例时阅读 references/unsafe-patterns.md;用 Miri 测试 unsafe 代码时参考 skills/rust/rust-sanitizers-miri;FFI 场景参考 skills/rust/rust-ffi;调试 unsafe 代码中的 panic 参考 skills/rust/rust-debugging;别名和内存顺序参考 skills/low-level-programming/memory-model。
适用边界 · 注意事项
- 当 std 已有安全 API(如 Vec、Box、Arc)时,不要为了相同功能手写 unsafe。
- 当已有 crate 能处理(如 memmap2、nix、windows-sys)或可以通过重构避免 unsafe 时,不要直接使用 unsafe。
- 当性能收益没有经过测量且不显著时,不要为了微优化引入 unsafe。