之前我都没注意到,不同的场景下 unsafe 关键字的语义可能有很大不同(可能随着我的理解深入,能把这些语义统一起来吧)。本文主要介绍在三个不同场景下 unsafe 的语义。
unsafe block
unsafe block 里的 unsafe 是强制性的,是用于"提权"的。rust 代码中有些操作不用 unsafe block 提权是不能通过编译的,比如:
- deref raw ptr
- 调 FFI
- inline asm
- 调 unsafe fn
所以这里的 unsafe 的语义是"提权",是编译器强制要求开发者添加的。
unsafe fn
这里的 unsafe 是规范性的,用于提醒函数的调用者,需要满足特定的条件才能调用此函数。
unsafe fn 里的 unsafe 并不能提权,也就是函数实现里如果想进行 unsafe 的操作,还需要再使用 unsafe block(可能旧版本的 rust 并不需要,这一点有待验证)。反过来,函数内使用 unsafe block,不代表函数一定是 unsafe fn 的。所以说这里的 unsafe 是规范性的,是开发者为了保证代码质量自觉标明的。
unsafe trait
这里的 unsafe 也是规范性的,用于提醒 trait 的实现者,在编写 impl trait 时要注意满足特定的语义。以典型的 unsafe trait Sync 为例,开发者都相信,实现了 Sync 的类型,其引用可以在线程间传递。如果有人在实现 Sync 时,没有保证目标类型的引用可以在线程间传递,就可能让人们在使用这个类型时发生 UB。
比较微妙的一点是,其实所有 trait 都要求实现者在实现时合理地实现每个接口,那 unsafe trait 有什么特殊之处呢?关键在于,unsafe trait 实现的语义要被 unsafe code 依赖,并且会影响 unsafe code 是否会发生 UB。
一般的 trait 所承载的语义,并不会影响使用这个 trait 的代码。比如,如果一个 trait 只被 safe code 调用,无论其内部实现为何,其绑定的语义为何,safe code 都不会发生 UB,因为 safe code 能做的事情有限,他调用一个胡乱实现的 trait,可能会出 bug,但不会出 UB。即使是 trait 的实现内部会发生 UB,也不会传递到 safe code 中,所以不算 safe code 发生 UB。
但如果一个 trait 会被 unsafe code 调用,并且 unsafe code 会基于这个 trait 的语义做一些事,那就可能因为 trait 的实现没有满足语义要求,而导致 unsafe code 中发生 UB。对于这种 trait,就应该规范地将其标为 unsafe trait。