即使在有限的范围数字情况下,是否建议使用`u32` /`i32`?

Abd*_*lah 6 rust

我们应该使用u32/ i32或更低的变体(u8/ i8,u16/ i16)来处理有限范围的数字,如"月中的天数",范围从1-30或"主题的得分",范围从0到100?或者为什么我们不应该?

较低的变体(即内存效率)是否有任何优化或好处?

Luk*_*odt 7

摘要

正确性应在性能和正确性明智的(像1-100范围)优先,所有的解决方案(u8,u32,...)也同样糟糕.最好的解决方案是创建一种新类型,以便从类型中获益.

我的其余部分试图证明这种说法是正确的,并讨论了创建新类型的不同方法.

更多解释

让我们来看看"主题得分"的例子:唯一的合法值是0-100.我认为正确性,使用u8u32同样糟糕:在这两种情况下,您的变量都可以保存在语义上下文中合法的值; 那很糟!

并且认为u8更好,因为有更少的非法价值观,就像争辩说摔跤熊比走过纽约更好,因为你只有一种可能性死亡(熊受攻击失血)而不是许多可能性在纽约的死亡(车祸,刀袭,溺水......)

所以我们想要的是一种保证只保留合法价值的类型.我们想要创建一个完全符合这一要求的新类型.但是,有多种方法可以继续; 每个都有不同的优点和缺点.


(A)公开内在价值

struct ScoreOfSubject(pub u8);
Run Code Online (Sandbox Code Playgroud)

优点:至少API更容易理解,因为参数已经由类型解释.更容易理解的是:

  • add_record("peter", 75, 47) 要么
  • add_record("peter", StudentId(75), ScoreOfSubject(47))

我会说后者;-)

缺点:我们实际上没有进行任何范围检查,仍然可能发生非法值; 坏!.


(B)将内部值设为私有并提供范围检查构造函数

struct ScoreOfSubject(pub u8);

impl ScoreOfSubject {
    pub fn new(value: u8) -> Self {
        assert!(value <= 100);
        ScoreOfSubject(value)
    }
    pub fn get(&self) -> u8 { self.0 }
}
Run Code Online (Sandbox Code Playgroud)

优势:我们用很少的代码强制执行合法的价值观,是的:)

缺点:使用该类型可能很烦人.几乎每个操作都需要程序员打包和解包值.


(C)添加一堆实现(除(B)之外)

(代码会impl Add<_>,impl Display等等)

优点:程序员可以直接使用该类型并对其执行所有有用的操作 - 通过范围检查!这是非常理想的.

请看看Matthieu M.的评论:

[...]通常将分数相乘或除以它们不会产生分数!强类型不仅强制执行有效值,还强制执行有效操作,因此您实际上不会将两个分数分开以获得另一个分数.

我认为这是一个非常重要的一点,我以前没有说清楚.强类型可以防止程序员对值执行非法操作(没有任何意义的操作).一个很好的例子是cgmath用于区分点向量和方向向量的箱子,因为它们都支持不同的操作.您可以在此处找到其他说明.

缺点:很多代码:(

幸运的是,Rust宏/编译器插件系统可以减少这个缺点.有像newtype_derive或者bounded_integer那样的箱子为你做这种代码生成(免责声明:我从未与他们合作过).


但现在你说:"你不能认真吗?我应该花时间写新类型吗?"

不一定,但如果你正在研究生产代码(==至少有些重要),那么我的答案是:是的,你应该.

  • +1用于引入新类型。至于样板,我想指出,将分数相乘或相除通常不会产生分数!强类型输入不仅会强制执行有效值,还会强制执行有效的*运算*,因此您实际上不必将两个分数相除即可得到另一个分数。 (2认同)