可靠性

函数应验证其参数 (C-VALIDATE)

Rust API 通常遵循鲁棒性原则:“对外发送信息时要保守;对接收到的信息要宽容”。

相反,Rust 代码应尽可能地强制输入的有效性。

可以通过以下机制实现强制验证(按优先顺序列出)。

静态验证

选择一种可以排除无效输入的参数类型。

例如,优先选择

#![allow(unused)]
fn main() {
fn foo(a: Ascii) { /* ... */ }
}

而不是

#![allow(unused)]
fn main() {
fn foo(a: u8) { /* ... */ }
}

其中 Asciiu8 的一个包装类型,它保证最高位为零;有关创建类型安全包装器的更多细节,请参见 newtype 模式 (C-NEWTYPE)。

静态验证通常几乎不带来运行时成本:它将成本推到边界(例如,当 u8 首次转换为 Ascii 时)。它还可以在编译期间捕获错误,而不是通过运行时失败来发现错误。

另一方面,有些属性很难或不可能用类型来表达。

动态验证

在处理输入时(或在必要时提前)验证输入。动态检查通常比静态检查更容易实现,但也有几个缺点:

  1. 运行时开销(除非检查可以作为处理输入的一部分完成)。
  2. 错误的检测延迟。
  3. 引入失败情况,无论是通过 panic! 还是 Result/Option 类型,这些都必须由客户端代码处理。

使用 debug_assert! 的动态验证

与动态验证相同,但可以轻松地在生产构建中关闭昂贵的检查。

动态验证的选择退出

与动态验证相同,但增加了可以选择退出检查的同类函数。

惯例是使用类似 _unchecked 的后缀标记这些选择退出检查的函数,或者将它们放在 raw 子模块中。

在以下情况下,可以慎重使用未经检查的函数:(1) 性能要求避免检查,并且 (2) 客户端可以确信输入是有效的。

析构函数不应失败 (C-DTOR-FAIL)

析构函数在发生 panic 时执行,在这种情况下,如果析构函数失败会导致程序中止。

与其让析构函数失败,不如提供一个单独的方法来检查是否正常结束,例如 close 方法,该方法返回一个 Result 以表示问题。如果未调用该 close 方法,则 Drop 实现应进行清理,并忽略或记录/跟踪它产生的任何错误。

可能阻塞的析构函数应提供替代方案 (C-DTOR-BLOCK)

同样,析构函数不应调用阻塞操作,这会使调试更加困难。再次考虑提供一个单独的方法来准备无错误的、非阻塞的清理工作。