How do I convert between numeric types safely and idiomatically?

Converting values

From a type that fits completely within another

There’s no problem here. Use the From trait to be explicit that there’s no loss occurring:

fn example(v: i8) -> i32 {
    i32::from(v) // or v.into()
}

You could choose to use as, but it’s recommended to avoid it when you don’t need it (see below):

fn example(v: i8) -> i32 {
    v as i32
}

From a type that doesn’t fit completely in another

There isn’t a single method that makes general sense – you are asking how to fit two things in a space meant for one. One good initial attempt is to use an OptionSome when the value fits and None otherwise. You can then fail your program or substitute a default value, depending on your needs.

Since Rust 1.34, you can use TryFrom:

use std::convert::TryFrom;

fn example(v: i32) -> Option<i8> {
    i8::try_from(v).ok()
}

Before that, you’d have to write similar code yourself:

fn example(v: i32) -> Option<i8> {
    if v > std::i8::MAX as i32 {
        None
    } else {
        Some(v as i8)
    }
}

From a type that may or may not fit completely within another

The range of numbers isize / usize can represent changes based on the platform you are compiling for. You’ll need to use TryFrom regardless of your current platform.

See also:

What as does

but 4294967296us as u32 will silently overflow and give a result of 0

When converting to a smaller type, as just takes the lower bits of the number, disregarding the upper bits, including the sign:

fn main() {
    let a: u16 = 0x1234;
    let b: u8 = a as u8;
    println!("0x{:04x}, 0x{:02x}", a, b); // 0x1234, 0x34

    let a: i16 = -257;
    let b: u8 = a as u8;
    println!("0x{:02x}, 0x{:02x}", a, b); // 0xfeff, 0xff
}

See also:

About ToPrimitive / FromPrimitive

RFC 369, Num Reform, states:

Ideally […] ToPrimitive […] would all be removed in favor of a more principled way of working with C-like enums

In the meantime, these traits live on in the num crate:

Leave a Comment