1 简介
Rust 在编译时会强制执行的内存安全保证,但是 Rust 中还存不强制执行内存安全保证的不安全 Rust(unsafe Rust),它与常规 Rust 代码无异,但是会提供额外的超能力。
静态分析本质上是保守的,当编译器尝试确定一段代码是否支持某个保证时,拒绝一些合法的程序比接受错误的程序要好一些。这必然意味着有时代码可能是合法的,但如果 Rust 编译器没有足够的信息来确定,它将拒绝该代码。在这种情况下,可以使用不安全代码。
另一个 Rust 存在不安全一面的原因是:底层计算机硬件固有的不安全性。如果 Rust 不允许进行不安全操作,那么有些任务则根本完成不了。Rust 需要能够进行像直接与操作系统交互,甚至于编写操作系统这样的底层系统编程。
可以通过 unsafe
关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块。这里有五类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作:
- 解引用裸指针
- 调用不安全的函数或方法
- 访问或修改可变静态变量
- 实现不安全 trait
- 访问 union 的字段
unsafe
关键字只是提供了五个不会被编译器检查内存安全的功能,不会关闭借用检查器或禁用任何其他 Rust 安全检查。
2 解引用裸指针
不安全 Rust 有两个被称为裸指针(raw pointers)的类似于引用的新类型,分为不可变或可变的,分别写作 *const T
和 *mut T
,裸指针与引用和智能指针的区别在于:
- 允许忽略借用规则,可以同时拥有不可变和可变的指针,或多个指向相同位置的可变指针
- 不保证指向有效的内存
- 允许为空
- 不能实现任何自动清理功能
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
3 extern 调用外部代码
有 Rust 可能需要与其他语言编写的代码交互,为此 Rust 有一个关键字 extern
,有助于创建和使用外部函数接口(Foreign Function Interface, FFI)。外部函数接口是一个编程语言用以定义函数的方式,其允许不同(外部)编程语言调用这些函数。
extern 块中声明的函数在 Rust 代码中总是不安全的。因为其他语言不会强制执行 Rust 的规则且 Rust 无法检查它们,所以确保其安全是程序员的责任:
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}
在 extern "C"
块中,列出了希望能够调用的另一个语言中的外部函数的签名和名称。"C"
部分定义了外部函数所使用的应用二进制接口(application binary interface,ABI) —— ABI 定义了如何在汇编语言层面调用此函数。"C"
ABI 是最常见的,并遵循 C 编程语言的 ABI。
extern
的使用无需 unsafe
。
4 访问或修改可变静态变量
全局变量在 Rust 中被称为静态(static)变量。
static HELLO_WORLD: &str = "Hello, world!";
fn main() {
println!("name is: {}", HELLO_WORLD);
}
常量与不可变静态变量可能看起来很类似,区别是静态变量中的值有一个固定的内存地址,使用这个值总是会访问相同的地址,常量则允许在任何被用到的时候复制其数据;常量与静态变量的另一个区别在于静态变量可以是可变的,访问和修改可变静态变量都是不安全的:
static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_count(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}
5 不安全 trait
当 trait 中至少有一个方法中包含编译器无法验证的不变式(invariant)时 trait 是不安全的。可以在 trait 之前增加 unsafe
关键字将 trait 声明为 unsafe,同时 trait 的实现也必须标记为 unsafe
:
unsafe trait Foo {
// methods go here
}
unsafe impl Foo for i32 {
// method implementations go here
}
6 访问联合体字段
联合体 union
和 struct
类似,但是在一个实例中同时只能使用一个声明的字段。联合体主要用于和 C 代码中的联合体交互。访问联合体的字段是不安全的,因为 Rust 无法保证当前存储在联合体实例中数据的类型。
网友评论