每日一 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
在并发开发中,Send / Sync 是最常遇到的 trait 之一。由于它们是 auto trait,所以大多数结构基本上都自动实现了这两个接口,除了 Rc / RefCell 等。
而且,如果你的数据结构中包含裸指针(没有实现 Send / Sync),所以导致你定义的结构也没有实现这两个 trait。如果能够保证自定义数据结构是线程安全的,即可以在线程间安全地转移所有权或共享,那则可以手动为它们实现 Send / Sync。例如,Bytes:
如果不能百分百确定自定义数据结构在多线程环境下的线程安全性,则尽量不要为其实现 Send / Sync trait,否则可能会出现莫名其妙的崩溃或其他难以稳定浮现的 Bug。
其实任何 trait 在声明时只要标注了 unsafe 就是一个 unsafe trait。unsafe 在这里的功能是提醒实现者需要小心谨慎地确保安全性,它是一个对实现者的约束。
02.2-调用已有的 unsafe 函数
unsafe 函数是在声明时标注了 unsafe 的函数。调用者调用 unsafe 函数的代码需要包裹在 unsafe 代码快中,例如:
unsafe fn 是对调用者的一种约束,它告诉调用者使用这段代码可能会导致内存不安全,请小心谨慎地使用。
许多第三方库提供的 API 都有两种实现,安全的版本和 unsafe 版本。如果不是特别明确,请优先使用安全版本,不要因为性能而冒险使用 unsafe 版本。
02.3-对裸指针解引用
生成裸指针的过程并非是 unsafe 的,因为它并没有对内存进行不安全的操作。但裸指针的解引用,即尝试读取裸指针指向的内存,是 unsafe 的,是存在潜在风险的,因此需要使用 unsafe block 包裹起来。告诉编译器也告诉其他阅读者,这里是有潜在的内存安全风险的。
例如(以下代码来自本节课件):
裸指针并不像引用那样,有可变引用、不可变引用不能共存的约束。如上所示,可变指针和不可变指针是可以共存的。
02.4-使用 FFI
像其他语言一样,Rust 也具备访问其他语言代码的能力。由于其他语言并不满足 Rust 对内存安全的要求,所以在使用其他语言时,需要使用 unsafe block。例如,使用 libc 中的 malloc / free(代码来自于本节课间):
03-不推荐或尽量避免使用 unsafe Rust 的地方
除了在上面几种场景中,我们可能在其他的场景中也能看到 unsafe Rust 的身影。这些场景中虽然可以使用 unsafe Rust,但是作者却并不推荐这样使用:
访问或者修改可变静态变量;如果真的需要访问或修改可变静态变量,尝试是否有其他的办法替代;如果是在找不出替代方法,就要思考设计上是否合理了。
在宏中使用 unsafe;宏的使用者或调用者很难意识到宏中是否存在 unsafe 代码,而且如果到处使用宏会使 unsafe 代码散落在程序各处,容易出现问题,且难以发现或定位。
使用 unsafe 代码提升性能;如果不是在编写非常基础的库,而且这个库在系统的关键路径上,尽量不要使用 unsafe 来提升性能。
本节课程链接:《30|Unsafe Rust:如何用C++的方式打开Rust?》
版权声明: 本文为 InfoQ 作者【Samson】的原创文章。
原文链接:【http://xie.infoq.cn/article/12b2fbf615af403867e6743ba】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论