Skip to content

unsafe

unsafe 之所以存在,是因为静态分析本质上是保守的。
当编译器尝试确定一段代码是否支持某个保证时,拒绝一些有效的程序比接受无效程序要好一些。
这必然意味着有时代码可能是合法的,但是 Rust 不这么认为!
在这种情况下,可以使用不安全代码告诉编译器,“相信我,我知道我在干什么”。
这么做的缺点就是你只能靠自己了:如果不安全代码出错了,比如解引用空指针,可能会导致不安全的内存使用

另外一个需要不安全 Rust 的原因在于底层计算机硬件固有的不安全性。
如果 Rust 不允许进行不安全的操作,那么某些底层任务可能根本就完成不了了。
Rust 作为一门系统语言需要能够进行底层编程,它应当允许你直接与操作系统打交道甚至是编写你自己的操作系统,这正是 Rust 语言的目标之一。

不安全的超能力

你可以在代码块前使用关键字 unsafe 来切换到不安全模式,并在被标记后的代码块中使用不安全代码。
不安全 Rust 允许你执行 4 种安全 Rust 中不被允许的操作,而它们也就是所谓的不安全超能力。这些能力包括:

  • 解引用裸指针
  • 调用不安全的函数或方法
  • 访问或修改可变的静态变量
  • 实现不安全 trait

需要注意的是,unsafe 关键字并不会关闭借用检查器或禁用任何其他 Rust 安全检查:如果你在不安全代码中使用引用,那么该引用依然会被检查。
unsafe 关键字仅仅让你可以访问这 4 种不会被编译器进行内存安全检查的特性。
因此,即便是身处于不安全的代码块中,你也仍然可以获得一定程度的安全性。

另外,unsafe 并不意味着块中的代码一定就是危险的或一定会导致内存安全问题,它仅仅是将责任转移到了程序员的肩上,你需要手动确定 unsafe 块中的代码会以合法的方式访问内存。

人无完人,错误总是会在不经意间发生。
但通过将这 4 种不安全操作约束在拥有 unsafe 标记的代码块中,我们可以在出现内存相关的错误时快速地将问题定位到 unsafe 代码块中。
你应当尽可能地避免使用 unsafe 代码块,这会使你在最终排查内存错误时感激自己

为了尽可能地隔离不安全代码,你可以将不安全代码封装在一个安全的抽象中并提供一套安全的 API。
实际上,某些标准库功能同样使用了审查后的不安全代码,并以此为基础提供了安全的抽象接口。
这种技术可以有效地防止 unsafe 代码泄漏到任何调用它的地方,因此使用安全抽象总会是安全的。

解引用裸指针

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
fn to_u8<T>(data: &Vec<T>) -> Vec<u8> {
    let mut result = Vec::new();
    for item in data.iter() {
        let ptr = unsafe { std::mem::transmute::<&T, *const u8>(&item) };
        let slice = unsafe { 
            std::slice::from_raw_parts(ptr, std::mem::size_of::<T>()) 
        };
        result.extend_from_slice(slice);
    }
    result
}
pub fn value_to_u8<T>(value: T) -> Vec<u8> {
    let ptr = unsafe { std::mem::transmute::<&T, *const u8>(&value) };
    unsafe {
        std::slice::from_raw_parts(ptr, std::mem::size_of::<T>()).to_vec() 
    }
}
1
2
3
unsafe {
    arr.set_len(1024);
}
1
2
3
4
5
6
7
8
9
fn encode(x: usize) -> [u8;8] {
    unsafe { mem::transmute(x as u64) }
}

pub fn decode(buf: &[u8]) -> usize {
    let buf: [u8;8] = buf.try_into().expect("expect length=8");
    let res: u64 = unsafe { mem::transmute(buf) };
    res as usize
}
 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#[repr(C, packed(1))]
#[derive(Debug, Clone)]
pub struct Person {
    // name: String,
    age: i32,
    sex: char,
    height: f32,
}

pub fn data_to_u8<T>(data: &Vec<T>) -> Vec<u8> {
    let mut result = Vec::new();
    for item in data.iter() {
        let ptr = unsafe { std::mem::transmute::<&T, *const u8>(&item) };
        let slice = unsafe { 
            std::slice::from_raw_parts(ptr, std::mem::size_of::<T>()) 
        };
        result.extend_from_slice(slice);
    }
    result
}

pub fn u8_to_data<T: Sized>(data: &[u8]) -> &[T] {
    unsafe { 
        let res: &[T] = std::mem::transmute(data);
        &res[..data.len() / std::mem::size_of::<T>()]
    }
}

fn main() {
    let dline = Person {
        // name: String::from("2"),
        age: 18,
        sex: 'm',
        height: 1.8,
    };
    println!("dline = {:?}", dline);
    let data_u8 = data_to_u8(&vec![dline]);
    println!("data u8 = {:?}", data_u8);
    println!("data_u8 len = {}", data_u8.len());

    let new_val = u8_to_data::<Person>(&data_u8);
    println!("new_val = {:?}", new_val);
}