Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic way to parse binary data into primitive types

Tags:

rust

I've written the following method to parse binary data from a gzipped file using GzDecoder from the Flate2 library

fn read_primitive<T: Copy>(reader: &mut GzDecoder<File>) -> std::io::Result<T>
{
    let sz = mem::size_of::<T>();
    let mut vec =  Vec::<u8>::with_capacity(sz);
    let ret: T;
    unsafe{
        vec.set_len(sz);
        let mut s = &mut vec[..];
        try!(reader.read(&mut s));

        let ptr :*const u8 = s.as_ptr();
        ret = *(ptr as *const T)
    }
    Ok(ret)
}

It works, but I'm not particularly happy with the code, especially with using the dummy vector and the temporary variable ptr. It all feels very inelegant to me and I'm sure there's a better way to do this. I'd be happy to hear any suggestions of how to clean up this code.

like image 794
tomsgd Avatar asked May 06 '15 00:05

tomsgd


1 Answers

Your code allows any copyable T, not just primitives. That means you could try to parse in something with a reference, which is probably not what you want:

#[derive(Copy)]
struct Foo(&str);

However, the general sketch of your code is what I'd expect. You need a temporary place to store some data, and then you must convert that data to the appropriate primitive (perhaps dealing with endinaness issues).

I'd recommend the byteorder library. With it, you call specific methods for the primitive that is required:

reader.read_u16::<LittleEndian>()

Since these methods know the desired size, they can stack-allocate an array to use as the temporary buffer, which is likely a bit more efficient than a heap-allocation. Additionally, I'd suggest changing your code to accept a generic object with the Read trait, instead of the specific GzDecoder.

You may also want to look into a serialization library like rustc-serialize or serde to see if they fit any of your use cases.

like image 105
Shepmaster Avatar answered Sep 24 '22 00:09

Shepmaster