Converting number primitives (i32, f64, etc) to byte representations
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 u8
s (in a Vec<u8>
say) into an f32
?
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.
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.
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);
}
}
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.