unsafe-Rust

用于跟硬件沟通的编程逻辑无法通过编译器的检查,为了实现相应的程序功能,unsafe代码块会解除检查限制,允许我们编写不安全的代码实现自己的需求。需要注意的是unsafe模式可以绕过编译阶段的检查,但是执行的时候如果逻辑有错误仍然会panic

  • 解引用裸指针,就如上例所示
  • 调用一个 unsafe 或外部的函数
  • 访问或修改一个可变的静态变量
  • 实现一个 unsafe 特征
  • 访问 union 中的字段

解引用裸指针

裸指针(raw pointer,又称原生指针) 在功能上跟引用类似,同时它也需要显式地注明可变性。裸指针长这样: *const T*mut T

裸指针:

  • 可以绕过 Rust 的借用规则,可以同时拥有一个数据的可变、不可变指针,甚至还能拥有多个可变的指针
  • 并不能保证指向合法的内存
  • 可以是 null
  • 没有实现任何自动的回收 (drop)

裸指针跟 C 指针是非常像的,使用它需要以牺牲安全性为前提,但我们获得了更好的性能,也可以跟其它语言或硬件打交道。

基于引用创建裸指针

1
2
3
4
5
6
7
8
9
fn main() {
let mut num = 5;

let r1 = &num as *const i32;//创建裸指针本身是安全行为,所以不需要放到unsafe块中

unsafe {
println!("r1 is: {}", *r1);
}
}

基于内存地址创建裸指针

1
2
3
4
5
fn main() {
let address = 0x012345usize;//不保证该地址是否被使用,非常不安全的行为
let r = address as *const i32;

}

基于智能指针创建裸指针

1
2
3
4
5
let a: Box<i32> = Box::new(10);
// 需要先解引用a
let b: *const i32 = &*a;
// 使用 into_raw 来创建
let c: *const i32 = Box::into_raw(a);

调用unsafe函数或方法

unsafe 函数从外表上来看跟普通函数并无区别,唯一的区别就是它需要使用 unsafe fn 来进行定义。这种定义方式是为了告诉调用者:当调用此函数时,你需要注意它的相关需求,因为 Rust 无法担保调用者在使用该函数时能满足它所需的一切需求

image-20250428150958313

1
2
3
4
5
6
unsafe fn dangerous() {}
fn main() {
unsafe {
dangerous();
}
}

用安全抽象包裹 unsafe 代码

一个函数包含了 unsafe 代码不代表我们需要将整个函数都定义为 unsafe fn

我们编写一个函数,需要将一个数组分成两个切片,且每一个切片都要求是可变的。对同一个数组做两个可变借用,违反Rust借用规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();

assert!(mid <= len);

(&mut slice[..mid], &mut slice[mid..])
}

fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];

let r = &mut v[..];

let (a, b) = split_at_mut(r, 3);

assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5, 6]);
}

我们知道这两个引用肯定不会冲突,但是rust编译器无法判断是否安全就禁止编译,这种情况下可以借助unsafe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use std::slice;

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr();

assert!(mid <= len);

unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}

fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];

let r = &mut v[..];

let (a, b) = split_at_mut(r, 3);

assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5, 6]);
}

**split_at_mut 使用了 unsafe,但我们无需将其声明为 unsafe fn**,这种情况下就是使用安全的抽象包裹 unsafe 代码,这里的 unsafe 使用是非常安全的,因为我们从合法数据中创建了的合法指针

FFI

FFI(Foreign Function Interface)可以用来与其它语言进行交互

unsafe 的另一个重要目的就是对 FFI 提供支持

通过 FFI , 我们的 Rust 代码可以跟其它语言的外部代码进行交互

1
2
3
4
5
6
7
8
9
extern "C" {
fn abs(input: i32) -> i32;
}

fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}

访问或修改一个可变的静态变量

实现unsafe特性

通过 unsafe impl 的使用,我们告诉编译器:相应的正确性由我们自己来保证

1
2
3
4
5
6
7
8
9
unsafe trait Foo {
// 方法列表
}

unsafe impl Foo for i32 {
// 实现相应的方法
}

fn main() {}

访问 union 中的字段

union ,它主要用于跟 C 代码进行交互,所有字段共享存储空间有数据覆盖的风险

1
2
3
4
5
#[repr(C)]
union MyUnion {
f1: u32,
f2: f32,
}