Rust File Builder pattern for file read writes

  • March 20, 2021
  • Rust

Consider the following test code that creates a file, writes to it and then reads back to confirm what it wrote.

#[cfg(test)]
mod tests {
    #[allow(unused_imports)]
    use super::*;
    use std::fs::{self, File, OpenOptions};
    use std::io::{Read, Seek, SeekFrom, Write};
    use tempfile::{tempdir, tempfile, NamedTempFile};

    #[test]
    fn test_file() {
        let dir = tempdir().unwrap();
        let file_path = dir.path().join("foo.txt");
        let mut file = File::create(file_path.to_owned()).unwrap();
        file.write_all("Bar Baz".as_bytes())
           .unwrap();
        file.seek(SeekFrom::Start(0)).unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        assert_eq!(contents, "Bar Baz");
        drop(file);
        dir.close().unwrap();
     }
}

This test fails with a very cryptic and unhelpful message:

thread 'tests::test_tempfiles' panicked at 'called `Result::unwrap()` 
on an `Err` value: Os { code: 9, kind: Other, message: "Bad file descriptor" }'

It will panic at this line file.read_to_string(&mut contents).unwrap();

What is happening is that File::create and File::open in rust are actually syntactic sugar for File Builder pattern. Here the file is being created and when the first write happens, the file mode become write only. If you tried to read from this file-handle, it would panic. It would have been helpful if the error message was less cryptic.

Moving on to file builder pattern, OpenOptions. Actually its just the builder pattern with an another unhelpful name. If it was named FileBuilder, it would have been super helpful. But for some reason, it is cryptically named OpenOption. Further rant. You would then proceed to std::fs::File documentation but that does not help either. If you were familiar with OpenOptions, then good, if not, you are in for some head scratching. In my opinion, using OpenOption should have been detailed first and then its syntactic sugars File::open and File::create.

If you want to achieve file create, then file write and then file read, you need to give the file-handle permissions for both read and write. This is how you will do it:

#[cfg(test)]
mod tests {
    #[allow(unused_imports)]
    use super::*;
    use std::fs::{self, File, OpenOptions};
    use std::io::{Read, Seek, SeekFrom, Write};
    use tempfile::{tempdir, tempfile, NamedTempFile};

    #[test]
    fn test_file() {
        let mut file = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open("foo.txt")
            .unwrap();
        file.write_all(b"Hello, world!").unwrap();
        file.seek(SeekFrom::Start(0)).unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        assert_eq!(contents, "Hello, world!");
    }
}
Tek Shinobi
Author: Tek Shinobi

Hiya Ninjas, I am the ninja who invented the fire, wheel, science, technology and everything intelligent this humanity has ever experienced since its evolution from monkeys and germs. Actually, above is a partial list. I also was involved with the Big Bang that created this universe. What was it like before the Big Bang is so secret that if I tell you, I will have to turn you into a hobbit and force you to be my gardener. Okay. Sayonanra, Namaste Tek Shinobi




No Comments


You can leave the first : )



Leave a Reply

Your email address will not be published. Required fields are marked *