arraysrustcasting

Reading dynamic sized byte arrays as integers


I have a binary file which has a header determining the length of each data element in the file. For example byte length of 2, 4 or 8 etc.

How to define to a function that's output can be a vector of i16, i32 or i64 for reading this data?

This would be something like this:

fn read_data<T: NoIdeaTrait>(&mut self, file: &mut File) -> Vec<T> {
        let mut data = Vec::new();
        let _ = file.read_to_end(&mut data);
        self.sampled_data = data
            // This is dynamic value
            .chunks(self.byte_per_block as usize)
            // Depending on the value this code would change
            // to take into account byte_per_block
            .map(|x| i32::from_le_bytes(x))
            .collect()
    }

What would be a sensible approach here?


Solution

  • Make up your own FromMyFile trait for it.

    use std::io::{Cursor, Read};
    
    trait FromMyFile<const SIZE: usize> {
        fn from_bytes(bytes: [u8; SIZE]) -> Self;
    }
    
    impl FromMyFile<4> for i32 {
        fn from_bytes(bytes: [u8; 4]) -> Self {
            i32::from_le_bytes(bytes)
        }
    }
    impl FromMyFile<8> for i64 {
        fn from_bytes(bytes: [u8; 8]) -> Self {
            i64::from_le_bytes(bytes)
        }
    }
    
    fn read_data<T: FromMyFile<SIZE>, const SIZE: usize>(mut bytes: impl Read) -> Vec<T> {
        let mut data = Vec::new();
    
        loop {
            let mut buf = [0; SIZE];
            match bytes.read(&mut buf) {
                Ok(s) if s == SIZE => {}
                Ok(0) => {
                    break;
                }
                Ok(_) => {
                    panic!("unexpected lenght")
                }
                Err(e) => {
                    println!("io error {:?}", e);
                    panic!()
                }
            }
            data.push(T::from_bytes(buf));
        }
    
        data
    }
    
    fn main() {
        let buffer = Cursor::new([0_u8; 256]);
    
        let vec: Vec<i32> = read_data(buffer.clone());
        println!("{:?}", vec);
    
        let vec: Vec<i64> = read_data(buffer);
        println!("{:?}", vec);
    }
    

    Edit

    Maybe something like this?

    // Result for reading my file
    #[derive(Debug)]
    pub enum MyFileResult {
        I32(Vec<i32>),
        I64(Vec<i64>),
    }
    
    impl MyFileResult {
        pub fn from_read(mut bytes: impl Read) -> Self {
            let mut header = [0];
            bytes.read(&mut header).unwrap();
            match header[0] {
                0 => MyFileResult::I32(read_data(bytes)),
                1 => MyFileResult::I64(read_data(bytes)),
                _ => unreachable!("unknown encoding format"),
            }
        }
    }
    
    fn main() {
        // data with the first byte as the header determine the encoding size
        // - 0 for i32 encoded
        // - 1 for i64 encoded 
        let mut data = [0_u8; 257];
        {
            let data = data.clone();
            let buffer = Cursor::new(data);
            let res = MyFileResult::from_read(buffer);
            assert!(matches!(res, MyFileResult::I32(_)));
            println!("{:?}", res);
        }
        {
            // change the encoding style to i64 encoded
            data[0] = 1;
            let buffer = Cursor::new(data);
            let res = MyFileResult::from_read(buffer);
            assert!(matches!(res, MyFileResult::I64(_)));
            println!("{:?}", res);
        }
    }