RustArena

Converting between number types in Rust (as, From and TryFrom)

How to convert i32 to f64, u64 to usize and back in Rust with as, From and TryFrom, and the truncation and wrapping traps of as.

Rust never converts between number types behind your back. An i32 won't silently become an i64, even though that would always be safe:

fn main() {
    let items: i32 = 7;
    let total: i64 = items;
    println!("{total}");
}
error[E0308]: mismatched types

That strictness is deliberate: implicit conversions are a classic source of bugs in C and JavaScript. You choose the conversion, and there are three tools.

1. as: fast, never fails, can lose information

value as Type converts between any number types (and char/u8, bool to integer):

fn main() {
    let sum = 87 + 92 + 78;
    let count = 3;
    let average = sum as f64 / count as f64;
    println!("{average:.2}");
    println!("{}", 3.99_f64 as i32);
    println!("{}", 300_i32 as u8);
    println!("{}", -1_i32 as u32);
    println!("{}", 1e10_f64 as i32);
    println!("{}", 'A' as u8);
}
85.67
3
44
4294967295
2147483647
65

Read the output carefully, because each line is a rule:

2. From / into(): lossless conversions

When every value of the source type fits in the target, the standard library implements From: i64::from(x) for an i32, f64::from(x) for an i32 or f32, u32::from(c) for a char. The same conversion is available as x.into() when the target type is known. If someone later changes the source type so the conversion could lose data, From stops compiling, while as keeps quietly truncating.

3. TryFrom / try_into(): checked conversions

For conversions that might not fit, u8::try_from(x) returns a Result: Ok(value) if it fits, Err(...) if not. This is how you safely turn a u64 from a file into a usize index, or an i32 from user input into a u8.

At work

Example

fn main() {
    let cents: i32 = 1999;
    let dollars = f64::from(cents) / 100.0;
    println!("price ${dollars:.2}");

    let big: i64 = i64::from(cents) * 1_000_000;
    println!("wide math: {big}");

    for raw in [200_i32, 300, -5] {
        match u8::try_from(raw) {
            Ok(b) => println!("{raw} fits in a u8: {b}"),
            Err(e) => println!("{raw} does not fit: {e}"),
        }
    }

    let ratio = 2.0_f64 / 3.0;
    println!("truncated {} rounded {}", (ratio * 100.0) as i32, (ratio * 100.0).round() as i32);
}

Output:

price $19.99
wide math: 1999000000
200 fits in a u8: 200
300 does not fit: number too large to fit in target type
-5 does not fit: number too small to fit in target type
truncated 66 rounded 67

Practice it