SuperAPI AI 工具集

rust-unsafe

编程与开发

查看项目

更新于 10-15 13:46

技能介绍

Rust unsafe

用途

本技能引导代理编写、审查和推理 unsafe Rust:哪些操作需要 unsafe、如何编写安全抽象、审计模式、常见陷阱,以及何时应当使用 unsafe。

触发场景

“何时需要在 Rust 中使用 unsafe?”

“如何为 unsafe 代码编写安全抽象?”

“如何审计一个 unsafe 块?”

“Rust 中原始指针的规则是什么?”

“transmute 做什么,何时是安全的?”

“如何正确实现 UnsafeCell?”

工作流

  1. 五类 unsafe 超级能力

unsafe 恰好授予安全 Rust 中不具备的五种能力:

解引用原始指针(*const T、*mut T)

调用 unsafe 函数(包括 extern "C" 函数)

访问或修改可变静态变量

实现 unsafe trait(Send、Sync)

访问 union 的字段

Rust 中其他一切——包括内存分配、借用、闭包——即使在 unsafe 块内也遵循安全规则。

  1. 原始指针

创建原始指针(安全——尚未解引用) 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)

内存在引用生命周期内必须有效

  1. 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 {}

  1. 在 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()); } } }

  1. 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 陷阱:

大小错误(编译错误,但泛型类型需注意)

构造无效的枚举值

构造生命周期错误的引用

  1. 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 }
}

}

  1. unsafe 审计清单

审查 unsafe 块时:

是否有 // Safety: 注释解释不变量?

原始指针是否非空?

原始指针是否对目标类型正确对齐?

内存是否已初始化?

引用的生命周期是否有效?

是否遵守别名规则(没有同时存在的 & 和 &mut)?

对于 extern "C":C 不变量是否已记录并验证?

对于 Send/Sync 实现:线程安全是否确实得到保证?

unsafe 块是否尽可能小?

是否有在 Miri 下运行 unsafe 代码的测试?

  1. 何时使用 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 中的别名与内存序