How can I convert a buffer of a slice of bytes (&[u8]) to an integer?
Asked Answered
S

4

52

I am reading raw data from a file and I want to convert it to an integer:

fn main() {
    let buf: &[u8] = &[0, 0, 0, 1];
    let num = slice_to_i8(buf);
    println!("1 == {}", num);
}

pub fn slice_to_i8(buf: &[u8]) -> i32 {
    unimplemented!("what should I do here?")
}

I would do a cast in C, but what do I do in Rust?

Syndrome answered 27/3, 2015 at 18:11 Comment(0)
S
65

I'd suggest using the byteorder crate (which also works in a no-std environment):

use byteorder::{BigEndian, ReadBytesExt}; // 1.2.7

fn main() {
    let mut buf: &[u8] = &[0, 0, 0, 1];
    let num = buf.read_u32::<BigEndian>().unwrap();

    assert_eq!(1, num);
}

This handles oddly-sized slices and automatically advances the buffer so you can read multiple values.

As of Rust 1.32, you can also use the from_le_bytes / from_be_bytes / from_ne_bytes inherent methods on integers:

fn main() {
    let buf = [0, 0, 0, 1];
    let num = u32::from_be_bytes(buf);

    assert_eq!(1, num);
}

These methods only handle fixed-length arrays to avoid dealing with the error when not enough data is present. If you have a slice, you will need to convert it into an array.

See also:

Suburbia answered 27/3, 2015 at 18:31 Comment(10)
You can also use BigEndian::read_i32(&bytes[..]), if you're only reading a single item.Hakodate
What if buf is a vec<8>?Blenheim
@Blenheim you don't have to do anything special for a Vec<u8>. See also How do I get a slice of a Vec<T> in Rust?. You have to care about the endianness of the data. It's the difference between 12345 and 54321. So long as both the writer and the reader agree on the endianness of the data, you are fine. If you are writing it, then pick one and use it consistently. Most people would pick little endian, probably.Suburbia
@Suburbia it s rather that I need to preserve the existing byte ordering and that the code will run on cpu supportting unaligned memory access. So how to do the same without from_be_bytes since it seems there s no from_ptr() function? It also seems Vec has no read_u32.Blenheim
@Blenheim NativeEndian; How can I get an array or a slice from a raw pointer?; Why do I need to import a trait to use the methods it defines for a type?Suburbia
@Suburbia and for doing it on array while still using NativeEndian?Blenheim
@Blenheim It's not clear what part you are missing. exampleSuburbia
@Suburbia sorry I was meaning without using the byteorder crate.Blenheim
Let us continue this discussion in chat.Suburbia
@Suburbia what if buf is larger than 4 bytes play.rust-lang.org/…?Blenheim
P
27

I'd like to give this answer here to commit the following additional details:

  1. A working code snippet which converts slice to integer (two ways to do it).
  2. A working solution in no_std environment.
  3. To keep everything in one place for the people getting here from the search engine.

Without external crates, the following methods are suitable to convert from slices to integer even for no_std build starting from Rust 1.32:

Method 1 (try_into + from_be_bytes)

use core::convert::TryInto;

let src = [1, 2, 3, 4, 5, 6, 7];

// 0x03040506
u32::from_be_bytes(src[2..6].try_into().unwrap());

use core::conver::TryInto is for no_std build. And the way to use the standard crate is the following: use std::convert::TryInto;.

(And about endians it has been already answered, but let me keep it here in one place: from_le_bytes, from_be_bytes, and from_ne_bytes - use them depending on how integer is represented in memory).

Method 2 (clone_from_slice + from_be_bytes)

let src = [1, 2, 3, 4, 5, 6, 7];
let mut dst = [0u8; 4];

dst.clone_from_slice(&src[2..6]);

// 0x03040506
u32::from_be_bytes(dst);

Result

In both cases integer will be equal to 0x03040506.

Pitcher answered 13/5, 2020 at 10:35 Comment(7)
This doesn't really add anything to the existing answer, which already discusses from_le_bytes / from_be_bytes / from_ne_bytes and links to a question showing how to get fixed-size arrays from slices.Suburbia
@Suburbia Sad you think so. I don't really agree because I collected all this info across all stackoverflow answers and decided to put it here for convenience. In this answer I add a couple of points: 1. What to do in no_std environment. 2. What to do with slices as original question stated (without jumping to links). 3. What to do without byteorder.Pitcher
byteorder works in a no-std environment, clicking links isn't a burden on the internet, the existing answer talks about avoiding byteorder.Suburbia
@Suburbia By the way, I didn't notice that this is your answer/question party. ;) But you should know that "See also" note is not really noticeable in your answer, also it is pretty confusing to see a code snippet about how to convert array to integer in your answer instead of converting a slice to integer. You may think differently, I just tell how it looks like. Anyways you just can improve your own answer, right? (P.S.: "clicking links isn't a burden on the internet" - this is your subjective point of view which differs from mine)Pitcher
Let us continue this discussion in chat.Pitcher
As a novice, I really appreciated this answer because it showed how to read from the middle of a sliceExploitation
I m currently getting the following error with Rust nightly src[2..6].try_into() method not found.Blenheim
M
0

This custom serialize_deserialize_u8_i32 library will safely convert back and forth between u8 array and i32 array i.e. the serialise function will take all of your u8 values and pack them into i32 values and the deserialise function will take this library’s custom i32 values and convert them back to the original u8 values that you started with.

This was built for a specific purpose, however it may come in handy for other uses; depending on whether you want/need a fast/custom converter like this.

https://github.com/second-state/serialize_deserialize_u8_i32

Millimeter answered 15/7, 2020 at 7:54 Comment(0)
V
0

Here’s my implementation (for a different use case) that discards any additional bytes beyond 8 (and therefore doesn’t need to panic if not exact):

pub fn u64_from_slice(slice: &[u8]) -> u64 {
    u64::from_ne_bytes(slice.split_at(8).0.try_into().unwrap())
}

The split_at() method returns a tuple of two slices: one from index 0 until the specified index and the other from the specified index until the end. So by using .0 to access the first member of the tuple returned by .split_at(8), it ensures that only the first 8 bytes are passed to u64::to_ne_bytes(), discarding the leftovers. Then, of course, it calls the try_into method on that .0 tuple member, and .unwrap() since split_at does all the custom panicking for you.

Vanthe answered 21/4, 2022 at 3:46 Comment(1)
I'd suggest slice[..8] instead of slice.split_at(8).0Coppage

© 2022 - 2024 — McMap. All rights reserved.