Converting number primitives (i32, f64, etc) to byte representations

TypesRust

Types Problem Overview


I am writing a library that encodes/decodes data to/from a binary format. Part of the format is numbers, which I'm using Rust's native primitive types for (like i8, i64, f32 etc.).

Is there an easy, built-in way to convert these data types into/from binary, i.e. convert a f64/f32/i64/etc. into a Vec<u8>? Likewise is there a way to convert 4 u8s (in a Vec<u8> say) into an f32?

Types Solutions


Solution 1 - Types

As of Rust 1.32 you can use {to,from}_{ne,le,be}_bytes for integral types.

let begin = 1234_i32;
let bytes = begin.to_ne_bytes();
let and_back = i32::from_ne_bytes(bytes);

For floating point you still have to rely on prior methods.

Solution 2 - Types

Rust 1.40 has: {to,from}_{ne,le,be}_bytes.

Converting a number to bytes and back (works for floats and integers after rust 1.40):

let x = 65535_i32;
let x_bytes = x.to_be_bytes();                  // x_bytes = [0, 0, 255, 255]
let original_x = i32::from_be_bytes(x_bytes);   // original_x = 65535 = x

Converting float before Rust 1.40

Rust 1.32 has: {to,from}_{ne,le,be}_bytes (only for integers), to_bits, and from_bits.

Converting a float to bytes and back:

let y = 255.255_f32;
let y_bytes = y.to_bits().to_be_bytes();
let original_y = f32::from_bits(u32::from_be_bytes(y_bytes)); // original_y = 255.255 = y

According to the Rust documentation from_bits can have portability issues.

Solution 3 - Types

Unfortunately, there is no safe built-in support for reading/writing primitives from/to a byte array in Rust at the moment. There are several community libraries for that, however, byteorder being the most used one:

extern crate byteorder;

use byteorder::{LittleEndian, WriteBytesExt};
use std::mem;

fn main() {
    let i: i64 = 12345;
    let mut bs = [0u8; mem::size_of::<i64>()];
    bs.as_mut()
        .write_i64::<LittleEndian>(i)
        .expect("Unable to write");

    for i in &bs {
        println!("{:X}", i);
    }
}

Of course, you can always cast raw pointers. For example, you can turn *const i64 into *const i8 and then convert it into an appropriate byte slice &[u8]. However, this is easy to get wrong, unsafe and platform-dependent due to endiannness, so it should be used only as a last resort:

use std::{mem, slice};

fn main() {
    let i: i64 = 12345;
    let ip: *const i64 = &i;
    let bp: *const u8 = ip as *const _;
    let bs: &[u8] = unsafe { slice::from_raw_parts(bp, mem::size_of::<i64>()) };
    
    for i in bs {
        println!("{:X}", i);
    }
}

Solution 4 - Types

std::mem::transmute can be used, although it is unsafe:

fn main() {
    let var1 = 12345678_i64;
    let raw_bytes: [i8; 8] = unsafe { std::mem::transmute(var1) };
    for byte in &raw_bytes {
        println!("{}", byte);
    }
}

Note: Please be sure the size of the two variables are exactly equal.

Solution 5 - Types

If your goal is to print the bytes or have them in a str representation, simply use the :b notation in a format brace

fn main() {
    println!("This is the binary of int {:b}", 4 as i32);
}

This prints

This is the binary of int 100

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAmandasaurusView Question on Stackoverflow
Solution 1 - TypesNicholas RishelView Answer on Stackoverflow
Solution 2 - TypesMikeView Answer on Stackoverflow
Solution 3 - TypesVladimir MatveevView Answer on Stackoverflow
Solution 4 - TypesF001View Answer on Stackoverflow
Solution 5 - TypesJosh WeinsteinView Answer on Stackoverflow