我们应该使用 u32
/i32
还是它的低版本 (u8
/i8
, u16
/i16
) 在处理范围有限的数字时,例如范围从 1-30 的“月中的天数”或范围从 0 到 100 的“主题分数”?或者为什么我们不应该这样做?
较低版本(即内存效率)是否有任何优化或优势?
最佳答案
总结
正确性应优先于性能和正确性(对于 1-100 之类的范围),所有解决方案( u8
、 u32
……)都同样糟糕。最好的解决方案是创建一个新类型以受益于强类型。
我的其余回答试图证明这一说法并讨论创建新类型的不同方法。
更多解释
让我们看一下“主题得分”示例:唯一的合法值是 0–100。我认为在正确性方面,使用 u8
和 u32
同样糟糕:在这两种情况下,您的变量可以保存在您的语义上下文中不合法的值;那很糟!
争论 u8
更好,因为非法值更少,就像争论与熊搏斗比步行穿过纽约更好,因为你只有一种死亡的可能性(被熊攻击失血)而不是到纽约死亡的多种可能性(车祸、持刀袭击、溺水……)。
所以我们想要的是一种保证只持有合法值的类型。我们想创建一个新的类型来做这件事。但是,有多种方法可以继续;每个都有不同的优点和缺点。
(A) 公开内在值(value)
struct ScoreOfSubject(pub u8);
优点:至少API更容易理解,因为参数已经用类型解释了。什么更容易理解:
-
add_record("peter", 75, 47)
或 -
add_record("peter", StudentId(75), ScoreOfSubject(47))
?
我会说后者 ;-)
缺点:我们实际上没有做任何范围检查,非法值仍然会出现;不好!</p>
(B) 将内部值设为私有(private)并提供范围检查构造函数
struct ScoreOfSubject(pub u8);
impl ScoreOfSubject {
pub fn new(value: u8) -> Self {
assert!(value <= 100);
ScoreOfSubject(value)
}
pub fn get(&self) -> u8 { self.0 }
}
优点:我们用很少的代码执行合法值,是的:)
缺点:使用类型可能很烦人。几乎每个操作都需要程序员打包和解包值。
(C) 添加一堆实现(除了(B))
(代码为 impl Add<_>
、 impl Display
等)
优点:程序员可以直接使用该类型并对其执行所有有用的操作——带有范围检查!这是非常理想的。
请看一下 Matthieu M. 的评论:
[...] generally multiplying scores together, or dividing them, does not produce a score! Strong typing not only enforces valid values, it also enforces valid operations, so that you don't actually divide two scores together to get another score.
我觉得这是我之前没有说清楚的一个很重要的点。强类型防止程序员对值执行非法操作(没有任何意义的操作)。一个很好的例子是 crate cgmath
,它区分点和方向向量,因为它们都支持对它们的不同操作。你可以找到额外的解释 here 。
缺点:代码很多:(
幸运的是,Rust 宏/编译器插件系统可以减少这个缺点。有像 newtype_derive
或 bounded_integer
这样的 crate 可以为您生成这种代码(免责声明:我从未与他们合作过)。
但现在你说:“你不是认真的吗?我应该花时间写新类型吗?”。
不一定,但如果您正在处理生产代码(== 至少有些重要),那么我的回答是:是的,您应该。
关于rust - `u32`/`i32` 即使在有限范围的情况下也被建议吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39939386/