# Easy Cast

Introducing: easy-cast.

## Problem: correct casts aren't easy

Let me ask you, have you ever done this?

``````
#![allow(unused)]
fn main() {
fn make_some_vec() -> Vec<u8> { vec![] }
let v: Vec<_> = make_some_vec();

// We need a fixed-size type (not usize), or just want to save space.
// We can assume length <= u32::MAX.
let len = v.len() as u32;
}
``````

Of course, you could write this instead:

``````
#![allow(unused)]
fn main() {
fn make_some_vec() -> Vec<u8> { vec![] }
let v: Vec<_> = make_some_vec();

// We need a fixed-size type (not usize), or just want to save space.
use std::convert::TryFrom;
let len = u32::try_from(v.len()).unwrap();
}
``````

``````
#![allow(unused)]
fn main() {
let x: f32 = 1234.5678;

// Convert to the nearest integer, assuming it fits
let x = (x.round()) as i32;
assert_eq!(x, 1235);
}
``````

Or this?

``````
#![allow(unused)]
fn main() {
use rand_distr::{Poisson, Distribution};
let poi = Poisson::new(1e6_f64).unwrap();
let num = poi.sample(&mut rand::thread_rng());

// Note: u64::MAX as f64 rounds to 2^64, so we need to use < not <= here!
assert!(0.0 <= num && num < u64::MAX as f64);
let num = num as u64;
}
``````

Or even this?

``````
#![allow(unused)]
fn main() {
fn some_value() -> i32 { 0 }
let x: i32 = some_value();
let y = x as f32;
if y as i32 == x {
println!("No loss of precision!");
// WARNING: this test is WRONG since e.g. i32::MAX rounds to 2^31 on cast
// to f32, then rounds back to i32::MAX on cast to i32.
}
}
``````

### Summary

It should be easy and safe to convert one numeric type to another, but frequently it's not:

• Using `x as T` is easy but not safe: it may truncate, sign-convert, round, or saturate. (Before Rust 1.45.0 behaviour might even be undefined.)
• Using `T::try_from(x).unwrap()` is clunky when you expect the conversion to succeed.
• `TryFrom` doesn't even support float conversions.
• Conceptually, "convert a float to the nearest integer" is a single operation, yet most solutions require separate rounding and conversion steps.

It turns out this is not an easy problem to solve in general. I wrote an RFC on this. Nearly three years and a long discussion later, it's still unsolved.

### Existing solutions

The standard library provides:

• `From`, but it only covers portably infallible conversions
• `TryFrom`, but it misses float conversions and expects you to do your own error handling

There are existing libraries on `crates.io`, yet all of them fall short in some way:

• num_traits::NumCast: API is not extensible to user types; error handling is the user's responsibility; it's a relatively big library if you just want casts
• cast: different API for fallible and infallible conversions; API around `isize` and `usize` is platform-dependent; error handling is the user's responsibility

## Introducing easy-cast

Eventually I gave up trying to find a standard solution and wrote my own, starting with one particular problem (converting between `usize` and `u32` indices in KAS-text, which stores many text and list indices). This tiny beginning grew into the easy-cast library:

• a `From`-like trait, `Conv`, for all integer conversions
• direct support for float-to-int-with-rounding via `ConvFloat`
• easier usage via the `Cast` trait (like `Into`)
• `try_cast` and `try_conv`, allowing user error handling on all conversions
• and of course a few bug fixes and diagnostic improvements

### Examples

Now, we can revisit the above problems using `easy-cast`:

``````
#![allow(unused)]
fn main() {
use easy_cast::Conv;

fn make_some_vec() -> Vec<u8> { vec![] }
let v: Vec<_> = make_some_vec();
// Expect success, panic on failure
let len = u32::conv(v.len());
}
``````
``````
#![allow(unused)]
fn main() {
use easy_cast::CastFloat;

let x: f32 = 1234.5678;
// Convert to the nearest integer, panic if out-of-range
let x: i32 = x.cast_nearest();
}
``````
``````
#![allow(unused)]
fn main() {
use easy_cast::CastFloat;

use rand_distr::{Poisson, Distribution};
let poi = Poisson::new(1e6_f64).unwrap();
let num = poi.sample(&mut rand::thread_rng());

let num: u64 = num.cast_trunc();
}
``````
``````
#![allow(unused)]
fn main() {
use easy_cast::Conv;

fn some_value() -> i32 { 0 }
let x: i32 = some_value();
match f32::try_conv(x) {
Ok(y) => println!("{x} is exactly representable by f32: {y}"),
Err(e) => println!("{x} is not representable by f32: {e}"),
}
}
``````

For any integer `x` and any integer or float type `T`, we get:

• `T::conv(x)`
• `x.cast()` with type inference
• `T::try_conv(x)` and `x.try_cast()` returning `Result<T, Error>`

And for any float value `x` and integer type `T` we have:

• `x.cast_nearest()`, `x.cast_trunc()`, `x.cast_floor()`, `x.cast_ceil()`
• `T::conv_nearest(x)`, etc.
• `T::try_conv_trunc(x)`, `x.try_cast_floor()`, etc.

### Don't need the fuses?

One of the design goals for easy-cast was check everything in Debug builds, maybe not in Release builds. In a Debug build, or when the `always_assert` crate feature is used, `cast` and `conv` will panic on inexact conversions. In Release builds without this flag, the cast reduces to just `x as T`.

The same is not true for `try_cast` and `try_from` which always fail on inexact conversions.

We may make some adjustments here in future versions, e.g. to use `always_assert` by default (but if so, it will be considered a breaking change).