Flecks of Rust #8: Reading a binary file in Rust

It seems that even with a language ostensibly intended for systems programming, Rust developers seem to mostly read and write text files. Binary formats seem to be less common.

My interests lie in dealing with smallish binary files, containing MIDI System Exclusive messages. These files are typically small, from a few kilobytes to some megabytes. They can be read into memory completely for analyzing and transforming the content, possibly writing the results back to disk.

A naive way of reading a binary file in its entirety is to just use the std::fs::File::read_to_end function, wrapped in a helper:

use std::fs;

fn read_file_naive(name: &String) -> Vec<u8> {
    let mut f = fs::File::open(&name).expect("file should exist");
    let mut buffer = Vec::new();
    f.read_to_end(&mut buffer).expect("file should be readable");
    buffer
}

This will get you a Vec<u8> in the best case, or it will just panic if the file does not exist, or the read operation fails.

However, you should really have proper error checking, so that you can back out gracefully in case of errors. That implies returning an optional type: Some(Vec<u8>) or None:

fn read_file(name: &String) -> Option<Vec<u8>> {

Also, you need to handle any errors that may result from trying to open the file, or trying to read its contents:

match fs::File::open(&name) {
    Ok(mut f) => {
        let mut buffer = Vec::new();
        match f.read_to_end(&mut buffer) {
            Ok(_) => Some(buffer),
            Err(_) => None
        }
    },
    Err(_) => {
        eprintln!("Unable to open file {}", &name);
        None
    }
}

The client of this function then needs to handle the resulting optional type:

use std::env;

let args: Vec = env::args().collect();
let input_file = &args[1];
if let Some(buffer) = read_file(input_file) {
    // do what you need with the buffer
}

Note that we are not explicitly closing the file, because it will be closed when the file handle f goes out of scope.