可依赖
函数会验证其参数
Functions validate their arguments (C-VALIDATE)
Rust APIs 不遵循 稳健性法则: 对己方保守,对他方宽容。
正相反, Rust 代码应该在任何可以 强制 校验其输入的时候进行强制校验。
可以通过以下机制来实现强制验证。出现的顺序代表性能高低。
静态验证
把输入限制在类型上,排除掉不符合的输入。 比如,请这样写:
fn foo(a: Ascii) { /* ... */ }
而不是这样写:
fn foo(a: u8) { /* ... */ }
Ascii
是对 u8
类型的包装器 (wrapper) ,从而保证最高位是零。
参考 newtype 模式,来详细了解生成类型安全的包装器。
静态验证 (static enforcement) 通常具有很少的运行时代价:
它的代价在于边界,例如在 u8
首先转换为 Ascii
类型的时候。
它也能在早期发现 bugs ,在编译期发现,而不是在运行失败的时候才发现。
可是,有些性质很难或者不可能用类型来描述。
动态验证
动态验证 (dynamic enforcement) 即 在处理的时候(如果有必要的话处理之前) 校验输入。 动态检查通常比静态检查更容易实现,但是有以下几个缺点:
- 运行时开销(除非动态检查是处理输入的一部分)
- bugs 检测不及时
- 带来失败的情况,用户的代码需要通过
panic!
,或者Result
/Option
类型处理。
-
使用
debug_assert!
。 在构建生产版本的时候可以关掉高花销的检查。 -
增加不进行检查的同类函数。 通常用
_unchecked
后缀来标记这个函数不进行某些检查, 或者把函数放在raw
的子模块下面。
在下面两个场景下使用 unchecked 函数是明智的: 为了更高的性能;使用者确信输入的数据是有效的。
析构函数不该运行失败
Destructors never fail (C-DTOR-FAIL)
析构函数 (destructor) 在某项任务失败的时候也会执行, 如果析构函数运行失败的话程序就会中断且退出。
所以为了不让析构函数失败,应该单独提供用于检查的方法。
比如 close
方法就返回 Result
来表明遇到了问题。
阻塞的析构函数应有替代的办法
Destructors that may block have alternatives (C-DTOR-BLOCK)
同样,析构函数不应该调用造成阻塞的操作, 阻塞会让调试更困难。
重申一遍,考虑为不会失败且非阻塞的析构提供单独的方法。