Unsafe Rust
unsafe 稍稍放开了编译器赋予代码的枷锁,但不安全代码不等于危险代码,而是将安全责任转移给开发者,开发者需要自己负责unsafe代码的安全性。
仅在以下情况考虑使用:
- 与硬件/操作系统交互
- 性能关键路径
- 集成 C 代码库
- 实现底层数据结构
基础使用方法:用 unsafe 块包裹
unsafe {
// 不安全操作
}Unsafe 解锁五大超能力
- 解引用裸指针
- 调用不安全的函数或方法
- 访问或修改可变静态变量
- 实现 unsafe trait
- 访问
union的字段
unsafe 只是关闭了上述五个编译器安全检查的功能,它并不会关闭借用检查器或禁用任何其他 Rust 安全检查,所以你仍然能在不安全块中获得某种程度的安全。
而且若程序出错,则可以快速定位在使用了 unsafe 的代码块。也正是如此,最好确保每一个 unsafe 代码块都足够小,以便程序在出错时可快速修复。
最好的unsafe使用方式是:把不安全代码打包进安全的函数之中,以便安全代码的调用(否则调用方也必须是unsafe代码)。
解引用裸指针
还记得那所谓的 "悬垂引用" 吗,编译器不允许变量有多个可变引用 或者 同时有可变和不可变引用 或 多个可变引用。
不安全 Rust 有两个被称为裸指针的新类型(作用类似于引用)。
裸指针类型:
*const T(不可变)和*mut T(可变),*不是解引用符合,而是裸指针类型名的一部分。与普通引用和智能指针的区别:
可同时存在多个可变/不可变指针(忽略借用规则)
可能指向无效内存
允许为空
没有自动清理功能(需要手动drop)
let mut num = 5;
// 安全代码中创建裸指针
let r1 = &raw const num; // 不可变裸指针
let r2 = &raw mut num; // 可变裸指针
// 必须在 unsafe 块中解引用
unsafe {
println!("r1: {}", *r1); // 输出 5
println!("r2: {}", *r2); // 输出 5
}调用不安全的函数或方法
// unsafe函数使用注释
// 由函数作者撰写的一些注意事项
unsafe fn dangerous() { /* 底层操作 */ } // 函数被声明unsafe,只能在unsafe块中被使用
unsafe {
dangerous(); // 需确保满足函数契约(调用条件,一般要看注释)
}必须在一个单独的 unsafe 块中调用 dangerous 函数。
通过 unsafe 块,我们向 Rust 承诺我们已经阅读过函数的文档,理解如何正确使用它,并核实我们履行了该函数的契约(要求)。
安全抽象实践
——将不安全代码封装为安全接口。 示例:实现安全的 split_at_mut
use std::slice;
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = values.len();
let ptr = values.as_mut_ptr(); // 获取裸指针
assert!(mid <= len);
unsafe {
(
slice::from_raw_parts_mut(ptr, mid), // 创建前半段切片
slice::from_raw_parts_mut(ptr.add(mid), len - mid) // 创建后半段切片
)
}
}
// 安全调用:let (a, b) = v.split_at_mut(3);FFI 调用(调用外部库)
使用关键字extern 使用 外部函数接口(Foreign Function Interface,FFI)。外部函数接口是一个编程语言用以定义函数的方式,其允许不同编程语调用这些函数(同语言编译出来的库也是如此)。
因为其他语言不会强制执行 Rust 的规则,所以 extern 块中声明的函数在 Rust 代码中通常是不安全的,extern 块本身也必须标注 unsafe。
——与其他语言交互,示例为 Rust 调用 C 标准库中的 abs 函数
unsafe extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}代码中的
"C"定义了外部函数所使用的 应用二进制接口(application binary interface,ABI)是"C"ABI。ABI 定义了如何在汇编语言层面调用此函数。
"C"ABI 是最常见的,并遵循 C 编程语言的 ABI。Rust 支持的所有 ABI 的信息参见 the Rust Reference。
unsafe extern 默认是 unsafe 的,但可以使用 safe 关键字让一些 FFI 函数可以安全地调用。
例如,C 标准库中的 abs 函数可以使用任何 i32 参数调用,完全不必考虑内存安全:
unsafe extern "C" {
safe fn abs(input: i32) -> i32;
}
fn main() {
// 在 safe 代码块中直接调用
println!("Absolute value of -3 according to C: {}", abs(-3));
}注意: 不是所有 unsafe 块中都能使用 safe 关键字,它只允许在unsafe extern 中使用。
暴露函数(导出函数)
如果说 FFI 是调用其他语言的函数,那暴露函数就是给其他程序(包括跨语言)调用。
不同于创建整个 extern 块来使用 FFI,导出函数需要:
- 在
fn关键字之前增加extern关键字并指定 ABI - 增加
#[no_mangle]注解来告诉 Rust 编译器不要 mangle 此函数的名称。
Mangling 指编译时会将函数更改为其他名称。
禁用 Rust 编译器的 name mangling 是不安全的,需要开发者自行确保所有导出的函数之间没有命名冲突。
#[unsafe(no_mangle)] //在这里对no_mangle属性声明unsafe,并非将下面的代码变成unsafe
//表示“我知道这个属性危险,但我知道我在干什么。”
pub extern "C" fn call_from_c() {
println!("Just called a Rust function from C!");
}访问或修改可变静态变量
静态变量即为全局变量,一般是不可变的。
static HELLO_WORLD: &str = "Hello, world!";
fn main() {
println!("name is: {HELLO_WORLD}");
}可变的静态变量是unsafe的:
static mut COUNTER: u32 = 0;
/// SAFETY: 同时在多个线程调用这个方法是未定义的行为,所以你*必须*保证同一时间只
/// 有一个线程在调用它。
unsafe fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
unsafe {
// SAFETY: 它只在 `main` 这一个线程被调用。
add_to_count(3);
println!("COUNTER: {}", *(&raw const COUNTER));
}
}- 使用
mut关键字来指定可变性。任何读写COUNTER的代码都必须位于unsafe块中。
实现 unsafe trait
当 trait 中的方法存在不变式(invariant)时该 trait 就是不安全的。
unsafe trait Foo { // 这里要unsafe关键字
// 方法声明在这里
}
unsafe impl Foo for i32 { // 这里也要unsafe关键字
// 方法实现在这里
}访问 union 的字段
union 和 struct 类似,但是在一个实例中同时只能使用一个已声明的字段。
这类型的作用是与 C 代码中的联合体进行交互。
#[repr(C)]
union MyUnion {
i: i32,
f: f32,
}
fn main() {
let u = MyUnion { i: 42 };
unsafe {
// 访问联合体字段需要 unsafe
println!("Integer: {}", u.i);
// 危险:可能错误解释内存
// println!("Float: {}", u.f); // 未定义行为!
}
}最佳实践
文档注释
每当编写一个不安全函数,惯常做法是编写一个以 SAFETY 开头的注释并解释调用者如何使用这个函数。
同理,当进行不安全操作时,编写一个以 SAFETY 解释如何根据安全性规则进行维护。
static mut COUNTER: u32 = 0;
/// SAFETY: 同时在多个线程调用这个方法是未定义的行为,所以你*必须*保证同一时间只
/// 有一个线程在调用它。
unsafe fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
unsafe {
// SAFETY: 它只在 `main` 这一个线程被调用。
add_to_count(3);
println!("COUNTER: {}", *(&raw const COUNTER));
}
}unsafe {
// SAFETY: 指针有效性由以下条件保证:
// 1. 指针来自有效引用
// 2. 索引在边界内
*ptr.add(index) = value;
}缩小 unsafe 范围
fn safe_wrapper() {
// 良好实践:将 unsafe 限制在最小范围
let result = unsafe {
// 仅包含必要的不安全操作
};
// 安全处理结果
}用 miri 检查不安全代码
rustup +nightly component add miri
cargo +nightly miri test