In simple application-level Rust code programmers perform operations like x*y knowing that if their reasoning is wrong regarding the range of those values (or even if they haven’t given much thinking about their ranges), the compiler in debug builds will usually catch the overflow bug. This is quite different from writing down some kind of proof that the expression x*y will never overflow, but it’s a big improvement over equally simple C code that often is a types soup and has wild wrap-around and other problems (but it’s only a moderate improvement on alternative languages like Object Pascal, that was doing something similar since many years: https://www.freepascal.org/docs-html/prog/progsu64.html ). Rust offers several other tricks, like wrapping_mul and so on to tell the compiler a more precise semantics that’s desired (such explicit code is common in Ada language). So overall the integral story in Rust is a big step forward (but I miss ranged integrals in Rust).
But in the same application-level Rust code and even in some library level code you see plenty of “as” casts (including the Rustc compiler, I’ve counted more than 5000 “as” casts in its source outside comments and not including other usages of “as”). Every time you use an “as” cast you’re out the safety net given by overflow tests of debug builds because they truncate silently. This is like trying to sell bullet proof cars without windows. For me this is unreasonable.
So perhaps we can find some solution for Rust 2018… I am not suggesting to remove hard casts from Rust, because it’s a system language, but to let Rust programmers use them only when they explicitly don’t want a safety net (or when they can’t use it). For the short and common use case in application code I’d like truncate-panics in debug builds as default behaviour. I don’t know how this could be done, it sounds like a significant breaking of pre-Rust2018 code… Suggestions are welcome.
In some of my code I don’t use “as” casts for integral values, I use a to!{} macro like this:
#![feature(try_from)]
#[cfg(debug_assertions)]
use std::convert::TryFrom;
#[cfg(not(debug_assertions))]
macro_rules! to { ($e:expr, $t:ident) => (($e) as $t) }
#[cfg(debug_assertions)]
macro_rules! to { ($e:expr, $t:ident) => ($t::try_from($e).unwrap()) }
fn main() {}
It doesn’t work with f64/f32 values and it doesn’t work in const contexts:
const N: u128 = 50;
let a = [0u32; to!{N, usize}];
But in many cases it makes me a bit more relaxed and confident about the reliability of my Rust code.