Unsafe Rust
Rust 程序设计语言 - Rust 程序设计语言 简体中文版 (kaisery.github.io)
Unsafe Rust
unsafe rust不会强制保证内存安全,但是可以提供更强大的功能。通过使用unsafe标识,可以方便确认程序中可能有问题的代码块。
编译器有时无法判断程序正确性,所以会严格按语法规范编译失败,这时可要告诉编译器我们自己来保证程序的正确性。
使用unsafe关键字开始一个代码块,其中的代码可以是unsafe的,在其中可以进行以下操作:
- 解引用一个原始指针
- 调用一个unsafe的函数或方法
- 访问或修改不可变静态变量
- 实现unsafe的trait
- 访问union S的字段
基本用法
原始指针raw pointer
原始指针分为可变*mut T
和不可变两种 *const T
,其中的*
号是数据类型名称的一部分,不是解引用操作。它与引用或智能指针的差异:
- 可变和不可变原始指针可以指向同一个内存地址,不需要考虑借用规则
- 不保证指向的内存地址是有效,可以访问的
- 指针的值可以是null
- 没有自动释放机制
原始指针主要用在提高程序性能,与其他语言交互或者操作硬件时。
使用as关键字把一个引用转换为对应的原始指针类型. rust编译器不会检查指针指向地址的有效性,两个变量同时指向同一个地址可能出现数据竞争的多线程问题。
1 | fn main() { |
unsafe函数
使用unsafe关键字开头修饰的函数或方法只能在unsafe代码块中被调用
1 | unsafe fn dangerous() {} |
使用safe抽象来包装unsafe代码
如果一个函数中的部分代码是unsafe的,不一定要求这个函数式unsafe的。实现一个功能时需要使用unsafe的代码,例如下面的例子中从指定的索引位置分割一个数组。如果直接使用(&mut values[..mid], &mut values[mid..])
来返回分割的两个部分数据,编译器会认为我们同时创建了values的两个可变引用,导致出错。这时可以使用unsafe的原始指针来分割这个values的可变引用。
1 | use std::slice; |
使用其他程序语言接口
rust使用extern关键字来创建和使用外部函数接口Foreign Function Interface(EFI).
调用外部接口时,需要在extern后面定义外部接口使用的应用二进制接口application binary interface(ABI).ABI定义了在汇编层次如何调用一个函数接口。例子中"C"
就说明了使用C语言的ABI。
使用extern的函数都是unsafe的,因为rust无法保证外部接口的安全性。
1 | extern "C" { |
提供外部语言使用的rust接口
同理可以让外部语言使用rust实现的接口。#[no_mangle]
用来让编译器不要对函数名进行混淆,避免外部调用时在库中找不到函数,同样也需要用extern关键字后加ABI类型指明调用的接口类型。
1 |
|
访问或修改可变静态变量
rust中的全局变量称为静态变量。静态变量和常量类似,也使用 SCREAMING_SNAKE_CASE
命名习惯。使用static关键字修饰,rust编译器可以明确静态变量的声明周期。
静态变量和常量的差异:
- 静态变量在内存中有固定的地址,静态变量也可以定义为可变的
- 常量在使用的地方都有一份复制
访问不可变静态变量是safe的,但是读或写可变静态变量都是不安全的,都需要在unsafe代码块中,因为可能存在多线程的数据竞争问题。
1 | static mut COUNTER: u32 = 0; |
Unsafe Trait
一个Trait中的方法如果有编译器无法验证的不变体,这个Trait就是不安全的,实现这个trait时也需要声明unsafe。
1 | unsafe trait Foo { |
联合体中的字段
rust的中联合体和C中的类似,一个时刻只能使用union中定义的一个字段,主要也是为了和C语言交互使用的,但是rust编译器无法确定当前union中的成员具体的数据是什么样的,所以访问union中的字段也是不安全的。