Reading Binary Files - A trivial program not so trivial for me

I'm attempting to write a trivial program that reads a binary file, and then prints out the binary numbers. A simple version of hexdump. But I'm stumped finding an iteration over the buffer?

fn main()
{
    let mut MyBuf = BufReader::new(File::open("./my.bin").unwrap());
    // somehow loop over the buffer printing each byte.
    println!("{:b}", MyBuf.bytes());
}

I know you can't use MyBuf.bytes() directly. Everything I tried has mismatched type errors. Ideally I just want a simple for loop

for a in 0.. MyBuf.bytes().iter()
{
println!("{:b}", a);
}

This should compile:

use std::fs::File;
use std::io::BufReader;
use std::io::Read;

fn main() {
    let my_buf = BufReader::new(File::open("./my.bin").unwrap());
    for byte_or_error in my_buf.bytes() {
        let byte = byte_or_error.unwrap();
        println!("{:b}", byte);
    }
}

The missing pieces were:

  1. The bytes() method returns an iterator over the bytes, which will read the bytes as you proceed through the file. It doesn't read all the bytes into memory immediately. For this reason, you cannot println! it directly.
  2. Since .bytes() already returns an iterator, you do not need the .iter() call.
  3. The 0..x syntax is only used for a range of integers. When something already returns an iterator, you should just use the iterator directly.
  4. The item type of the .bytes() iterator is Result<u8, io::Error> because reading the next byte might fail. In the example, I use unwrap to kill the program if this happens.
2 Likes

You might find it simpler to use std::fs::read, which is a function that reads the entire file into memory and returns a Vec<u8>, wrapped in a Result in case the operation failed. It looks like this:

fn main() {
    let bytes = std::fs::read("./my.bin").unwrap();
    
    for byte in bytes.iter() {
        println!("{:b}", byte);
    }
}

Again using unwrap to cause a panic if reading the file failed for some reason.

1 Like

Actually my next problem is reading u16 not u8. I picked this particular file because I wrote it 16 bits wide (uint16_t in C) and I want to do a version which will print binary or hex.

Try out this:

fn main() {
    let bytes = std::fs::read("./my.bin").unwrap();
    
    for byte_pair in bytes.chunks_exact(2) {
        let short = u16::from_le_bytes([byte_pair[0], byte_pair[1]]);
        println!("{:x}", short);
    }
}

This uses the fact that bytes is a Vec<u8>, and a vector can be used as a slice, so we can use the slice method chunks_exact. Then I use u16::from_le_bytes to turn each chunk into an u16.

You can look up these functions on the standard library documentation, which you can find at docs.rs/std. If the bytes are big-endian, there is also a method for that.

3 Likes

You can also use byteorder as a convenient wrapper if you don't want to construct u16 from bytes manually.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.