写点什么

每日一 R「21」Unsafe Rust

作者:Samson
  • 2022 年 8 月 31 日
    上海
  • 本文字数:1810 字

    阅读完需:约 6 分钟

每日一R「21」Unsafe Rust

到目前为止,我们学习的所有内容都是在 Rust 严格的安全性要求之下的。那 Rust 中有没有一些特殊的场景,需要我们破坏或者说暂时不遵循其严格的安全性要求?当然,那就是 unsafe Rust,我们今天学习的主题。

01-unsafe Rust

使用 unsafe rust 的三种场景:

  • 计算机硬件本身是 unsafe 的,例如操作 IO 访问外设,或者使用汇编指令进行特殊操作,所以需要使用 unsafe Rust

  • 访问其他语言,例如 C/C++,因为这些语言本身并不满足 Rust 的安全性要求,因此需要 unsafe Rust

  • 追求极致的性能,例如跳过边界检查、使用未初始化的内存等,需要使用 unsafe Rust。不推荐为了追求性能而放弃安全性,除非可以证明性能优化能够解决系统性能瓶颈。

02-可以使用 unsafe Rust 的场景

如何使用 unsafe Rust ?


  • 实现 unsafe trait,主要是 Send / Sync 这两个 trait

  • 调用已有的 unsafe 接口

  • 对裸指针解引用

  • 使用 FFI (Foreign Function Interface)

02.1-实现 unsafe trait

// 可以安全地在线程间转移所有权pub unsafe auto trait Send {}// 可以安全地在线程间共享pub unsafe auto trait Sync {}
复制代码


在并发开发中,Send / Sync 是最常遇到的 trait 之一。由于它们是 auto trait,所以大多数结构基本上都自动实现了这两个接口,除了 Rc / RefCell 等。


而且,如果你的数据结构中包含裸指针(没有实现 Send / Sync),所以导致你定义的结构也没有实现这两个 trait。如果能够保证自定义数据结构是线程安全的,即可以在线程间安全地转移所有权或共享,那则可以手动为它们实现 Send / Sync。例如,Bytes


pub struct Bytes {    ptr: *const u8,    ...}
// Vtable must enforce this behaviorunsafe impl Send for Bytes {}unsafe impl Sync for Bytes {}
复制代码


如果不能百分百确定自定义数据结构在多线程环境下的线程安全性,则尽量不要为其实现 Send / Sync trait,否则可能会出现莫名其妙的崩溃或其他难以稳定浮现的 Bug。


其实任何 trait 在声明时只要标注了 unsafe 就是一个 unsafe trait。unsafe 在这里的功能是提醒实现者需要小心谨慎地确保安全性,它是一个对实现者的约束。

02.2-调用已有的 unsafe 函数

unsafe 函数是在声明时标注了 unsafe 的函数。调用者调用 unsafe 函数的代码需要包裹在 unsafe 代码快中,例如:


unsafe { call_some_unsafe_fn(); }
复制代码


unsafe fn 是对调用者的一种约束,它告诉调用者使用这段代码可能会导致内存不安全,请小心谨慎地使用。


许多第三方库提供的 API 都有两种实现,安全的版本和 unsafe 版本。如果不是特别明确,请优先使用安全版本,不要因为性能而冒险使用 unsafe 版本。

02.3-对裸指针解引用

生成裸指针的过程并非是 unsafe 的,因为它并没有对内存进行不安全的操作。但裸指针的解引用,即尝试读取裸指针指向的内存,是 unsafe 的,是存在潜在风险的,因此需要使用 unsafe block 包裹起来。告诉编译器也告诉其他阅读者,这里是有潜在的内存安全风险的。


例如(以下代码来自本节课件):


let mut age = 18;let r1 = &age as *const i32;let r2 = &age as *mut i32;unsafe { println!("{}, {}", *r1, *r2); }
复制代码


裸指针并不像引用那样,有可变引用、不可变引用不能共存的约束。如上所示,可变指针和不可变指针是可以共存的。

02.4-使用 FFI

像其他语言一样,Rust 也具备访问其他语言代码的能力。由于其他语言并不满足 Rust 对内存安全的要求,所以在使用其他语言时,需要使用 unsafe block。例如,使用 libc 中的 malloc / free(代码来自于本节课间):


let data = unsafe {    let p = libc::malloc(8);    let arr: &mut [u8; 8] = transmute(p);    arr};unsafe {libc::free(transmute(data))};
复制代码

03-不推荐或尽量避免使用 unsafe Rust 的地方

除了在上面几种场景中,我们可能在其他的场景中也能看到 unsafe Rust 的身影。这些场景中虽然可以使用 unsafe Rust,但是作者却并不推荐这样使用:


  • 访问或者修改可变静态变量;如果真的需要访问或修改可变静态变量,尝试是否有其他的办法替代;如果是在找不出替代方法,就要思考设计上是否合理了。

  • 在宏中使用 unsafe;宏的使用者或调用者很难意识到宏中是否存在 unsafe 代码,而且如果到处使用宏会使 unsafe 代码散落在程序各处,容易出现问题,且难以发现或定位。

  • 使用 unsafe 代码提升性能;如果不是在编写非常基础的库,而且这个库在系统的关键路径上,尽量不要使用 unsafe 来提升性能。


本节课程链接:《30|Unsafe Rust:如何用C++的方式打开Rust?

发布于: 2022 年 08 月 31 日阅读数: 26
用户头像

Samson

关注

还未添加个人签名 2019.07.22 加入

InfoQ签约作者 | 阿里云社区签约作者

评论

发布
暂无评论
每日一R「21」Unsafe Rust_学习笔记_Samson_InfoQ写作社区