可靠性
函数应验证其参数 (C-VALIDATE)
Rust API 通常不遵循鲁棒性原则:“对外发送信息时要保守;对接收到的信息要宽容”。
相反,Rust 代码应尽可能地强制输入的有效性。
可以通过以下机制实现强制验证(按优先顺序列出)。
静态验证
选择一种可以排除无效输入的参数类型。
例如,优先选择
#![allow(unused)] fn main() { fn foo(a: Ascii) { /* ... */ } }
而不是
#![allow(unused)] fn main() { fn foo(a: u8) { /* ... */ } }
其中 Ascii
是 u8
的一个包装类型,它保证最高位为零;有关创建类型安全包装器的更多细节,请参见 newtype 模式 (C-NEWTYPE)。
静态验证通常几乎不带来运行时成本:它将成本推到边界(例如,当 u8
首次转换为 Ascii
时)。它还可以在编译期间捕获错误,而不是通过运行时失败来发现错误。
另一方面,有些属性很难或不可能用类型来表达。
动态验证
在处理输入时(或在必要时提前)验证输入。动态检查通常比静态检查更容易实现,但也有几个缺点:
- 运行时开销(除非检查可以作为处理输入的一部分完成)。
- 错误的检测延迟。
- 引入失败情况,无论是通过
panic!
还是Result
/Option
类型,这些都必须由客户端代码处理。
使用 debug_assert!
的动态验证
与动态验证相同,但可以轻松地在生产构建中关闭昂贵的检查。
动态验证的选择退出
与动态验证相同,但增加了可以选择退出检查的同类函数。
惯例是使用类似 _unchecked
的后缀标记这些选择退出检查的函数,或者将它们放在 raw
子模块中。
在以下情况下,可以慎重使用未经检查的函数:(1) 性能要求避免检查,并且 (2) 客户端可以确信输入是有效的。
析构函数不应失败 (C-DTOR-FAIL)
析构函数在发生 panic 时执行,在这种情况下,如果析构函数失败会导致程序中止。
与其让析构函数失败,不如提供一个单独的方法来检查是否正常结束,例如 close
方法,该方法返回一个 Result
以表示问题。如果未调用该 close
方法,则 Drop
实现应进行清理,并忽略或记录/跟踪它产生的任何错误。
可能阻塞的析构函数应提供替代方案 (C-DTOR-BLOCK)
同样,析构函数不应调用阻塞操作,这会使调试更加困难。再次考虑提供一个单独的方法来准备无错误的、非阻塞的清理工作。