76

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Amandasaurus
  • 58,203
  • 71
  • 188
  • 248
  • 9
    Please, can you mark the Nicholas Rishel's answer as accepted? That's the canonical answer now, and the oldest ones with `transmute` examples can be misleading. – Boiethios Jul 26 '19 at 09:58

5 Answers5

107

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.

Nicholas Rishel
  • 1,201
  • 2
  • 8
  • 6
53

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.

Community
  • 1
  • 1
Mike
  • 531
  • 5
  • 7
39

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);
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • 2
    Using `std::slice::from_raw_parts` is less code and also marked stable. Still unsafe for the same reasons, though! – bluss Apr 04 '15 at 23:23
  • 6
    `byteorder` solves it exactly. When I said "built in" I should have specified that something from crates.io would suffice. – Amandasaurus Apr 07 '15 at 07:53
16

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
F001
  • 742
  • 4
  • 11
  • 2
    Ensuring the correct size can be done by using `std::mem::size_of::()` instead of `8` in the array – Nic Jul 06 '19 at 19:41
  • Few things: raw_bytes should be `[u8; 8]` (or `mem::size_of` as suggested) and if the sizes do not match, `transmute` won't work: *"cannot transmute between types of different sizes, or dependently-sized types"* so it is "kind of safe". – hellow Jul 26 '19 at 10:01
2

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

Josh Weinstein
  • 2,788
  • 2
  • 21
  • 38