我发现我认为这是一种非常奇怪的行为。当变量在运行时溢出时,Rust 会出现恐慌;这对我来说很有意义。但是,只有在编译时分配溢出值时,它才会发出警告。这不应该是编译时错误吗?否则,两种行为就会显得不一致。
我预计会出现编译时错误:
fn main() {
let b: i32 = 3_000_000_000;
println!("{}", b);
}
产品:
<anon>:2:18: 2:31 warning: literal out of range for i32, #[warn(overflowing_literals)] on by default
<anon>:2 let b: i32 = 3_000_000_000;
这对我来说很有意义:
fn main() {
let b: i32 = 30_000;
let c: i32 = 100_000;
let d = b * c;
println!("{}", d);
}
产品:
thread '<main>' panicked at 'arithmetic operation overflowed', <anon>:4
playpen: application terminated with error code 101
编辑:
鉴于 FrancisGagné 的评论,以及我发现 Rust 实现了在操作期间检查溢出的运算符,例如 checked_mul,我发现需要自己实现溢出检查。这是有道理的,因为发布版本应该进行优化,并且不断检查溢出可能会变得昂贵。所以我不再看到“不一致”。然而,我仍然感到惊讶的是,分配一个会溢出的值不会导致编译时错误。在
golang
中,它会: Go Playground
实际上,您的评论与您观察到的行为不一致:
Go 示例与第一个 Rust 示例类似(除了 Go 在设计上没有警告)。
在 Rust 中,下溢或上溢会导致未指定值,在计算机科学中可以是
!
或bottom,这是一个表示控制流发散的特殊值,通常意味着中止或异常。
该规范允许:
并且两种模式均符合规范。
1 默认情况下不进行检测,如果您选择的话,您可以在繁重的数字代码之外以相对适中的性能成本激活发布中的溢出检查,并使用一个简单的标志。
关于溢出检查的成本:目前Rust/LLVM的情况对调试有帮助,但还没有真正优化。因此,在这个框架中,溢出检查成本。如果情况有所改善,那么有一天,rustc 可能会决定默认激活溢出检查,即使在发布版本中也是如此。
在Midori(用类似于C#的语言开发的微软实验性操作系统)中,即使在Release版本中也打开了溢出检查:
在 Midori 中,我们默认启用溢出检查进行编译。这与普通 C# 不同,在 C# 中,您必须显式传递 /checked 标志才能实现此行为。根据我们的经验,发现的意外溢出数量所带来的不便和成本是值得的。但这确实意味着我们的编译器需要真正擅长理解如何消除不必要的编译器。
显然,他们改进了编译器:
后者只能在发布中完成(你会失去精度),但会减少分支数量。
那么,还剩下什么成本?
妨碍优化的潜在不同算术规则:
64 + x - 128
可以优化为x - 64
;激活溢出检查后,编译器可能无法执行此优化不过,除非代码大量数字化(例如科学模拟或图形),否则它确实可能会产生影响。