u32
/ i32
还是其低版本(u8
/i8
, u16
/i16
)?如果使用低版本,是否有任何优化或好处(例如更节省内存)?u32
/ i32
还是其低版本(u8
/i8
, u16
/i16
)?如果使用低版本,是否有任何优化或好处(例如更节省内存)?正确性应该优先于性能,并且针对像1-100这样的范围,在正确性方面,所有解决方案(u8
,u32
等)都同样不好。最好的解决方案是创建一个新类型以受益于强类型。
我的回答的其余部分试图证明这一主张并讨论创建新类型的不同方式。
让我们看一下“学科成绩”的例子:唯一合法的值为0-100。我认为,在正确性方面,使用u8
和u32
同样糟糕:在这两种情况下,您的变量可以包含语义上不合法的值;那是不好的!
而且,认为u8
更好,因为非法值更少,就像认为与熊搏斗比步行穿过纽约更好,因为你只有一种死亡可能性(被熊攻击导致失血),而不是在纽约有很多种死亡可能性(汽车事故,刀刃攻击,溺水等)。
所以,我们想要的是一个类型,保证只包含合法值。我们想要创建一种新类型,正好可以做到这一点。然而,有多种方法可供选择;每种方法都有不同的优缺点。
struct ScoreOfSubject(pub u8);
优点:至少API更容易理解,因为参数已经被类型解释。哪个更容易理解:
add_record("peter", 75, 47)
还是 add_record("peter", StudentId(75), ScoreOfSubject(47))
?我会说后者更好理解。
缺点:我们实际上并没有进行任何范围检查,非法值仍然可能发生;不好!
struct ScoreOfSubject(pub u8);
impl ScoreOfSubject {
pub fn new(value: u8) -> Self {
assert!(value <= 100);
ScoreOfSubject(value)
}
pub fn get(&self) -> u8 { self.0 }
}
优点: 我们通过很少的代码来实施合法值,是的 :)
缺点: 和类型打交道可能很烦人。几乎每个操作都需要程序员对值进行打包和解包。
(代码会 impl Add<_>
, impl Display
等等)
优点: 程序员可以直接使用类型并执行所有有用的操作 -- 带有范围检查! 这是相当优化的。
请看 Matthieu M. 的评论:
[...] 通常将分数相乘或相除不会产生分数! 强类型不仅强制有效值,而且还强制执行有效操作,这样您就不会真正地将两个分数相除以获得另一个分数。
我认为这是我之前未能清楚表达的非常重要的一点。强类型防止程序员在值上执行非法操作(不合理的操作)。一个很好的例子是箱子cgmath
,它区分点和方向向量,因为两者都支持不同的操作。您可以在这里找到更多的解释。
缺点: 很多代码 :(
幸运的是,Rust 宏/编译器插件系统可以减少缺点。有像newtype_derive
或bounded_integer
这样的箱子为您生成此类代码 (免责声明: 我从未使用过它们)。
但现在你说: "你不会认真的吧? 我应该花时间写新类型吗?"。
不一定,但如果您正在处理生产代码(== 至少有些重要),那么我的答案是: 是的,你应该。
abs()
)。使用较小的类型可能会带来重大好处,但您需要在目标平台上对应用程序进行基准测试以确保。
较小的内存占用量带来的第一个且最容易实现的好处是更好的缓存。不仅您的数据更有可能适合缓存,而且还不太可能丢弃缓存中的其他数据,从而可能改善应用程序的完全不同部分。这是否触发取决于应用程序接触的内存及其顺序。做基准测试!
网络数据传输从使用较小的类型中获得明显优势。
较小的数据允许“更大”的指令。128位SIMD单元可以处理4个32位数据或16个8位数据,使某些操作快4倍。在我的基准测试中,这些指令确实执行得快4倍,但整个应用程序的性能提高不到1%,而且代码变得更混乱。塑造程序以更好地利用SIMD可能很棘手。
就有符号/无符号讨论而言,无符号具有略微更好的属性,编译器可能会或可能不会利用它们。
f32
/f64
类型,且其值可以小于 0 或大于 14。) - kennytmf
哦 :D - Abdillah