I am finding what I think is a very strange behaviour. Rust panics when when a variable overflows at runtime; this makes sense to me. However, it only raises a warning when value that overflows is assigned at compile time. Shouldn't that be a compile time error? Otherwise, two behaviours seem inconsistent.
I expect a compile time error:
fn main() {
let b: i32 = 3_000_000_000;
println!("{}", b);
}
Produces:
<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;
This makes sense to me:
fn main() {
let b: i32 = 30_000;
let c: i32 = 100_000;
let d = b * c;
println!("{}", d);
}
Produces:
thread '<main>' panicked at 'arithmetic operation overflowed', <anon>:4
playpen: application terminated with error code 101
Edit:
Given the comment by FrancisGagné, and me discovering that Rust implements operators that check for overflow during the operation, for example checked_mul, I see that one needs to implement overflow checks themselves. Which makes sense, because release version should be optimized, and constantly checking for overflows could get expensive. So I no longer see the "inconsistency". However, I am still surprised, that assigning a value that would overflow does not lead to compile time error. In golang
it would: Go Playground
Actually, your comments are not consistent with the behavior you observe:
The Go example is similar to the first Rust example (except that Go, by design, does not have warnings).
In Rust, an underflow or overflow results in an unspecified value which can be !
or bottom in computer science, a special value indicating that the control flow diverge which in general means either abortion or exception.
This specification allows:
and yet have both modes be consistent with the specification.
1 Not instrumenting by default, you can if you choose and for a relatively modest performance cost outside of heavy numeric code activate the overflow checks in Release with a simple flag.
On the cost of overflow checks: the current Rust/LLVM situation is helpful for debugging but has not really been optimized. Thus, in this framework, overflow checks cost. If the situation improves, then rustc might decide, one day, to activate overflow checking by default even in Release.
In Midori (a Microsoft experimental OS developed in a language similar to C#), overflow check was turned on even in Release builds:
In Midori, we compiled with overflow checking on by default. This is different from stock C#, where you must explicitly pass the /checked flag for this behavior. In our experience, the number of surprising overflows that were caught, and unintended, was well worth the inconvenience and cost. But it did mean that our compiler needed to get really good at understanding how to eliminate unnecessary ones.
Apparently, they improved their compiler so that:
The latter is only to be done in Release (you lose precision) but reduces the number of branches.
So, what cost remain?
Potentially different arithmetic rules that get in the way of optimizations:
64 + x - 128
can be optimized to x - 64
; with overflow checks activated the compiler might not be able to perform this optimizationStill, unless the code is heavily numeric (scientific simulations or graphics, for example), then it might impact it indeed.