Rust std::fs: Creating, Writing, & Reading Files In Rust

Greetings! Today, I would like to delve into the Rust std::fs which is Rust's standard library library and its file creation, writing, and reading functions. Being a programmer, I recognize the importance of file handling in any programming language. This post can be helpful to Rust newcomers or those seeking a refresher on this topic. In this guide, we will explore the impressive capabilities of the File object, including how to create files in Rust using this object and how to read and write to them using various techniques using rust code examples.

What is the Rust File object?

When working with files in Rust, the File object is one of the most essential components. It can be found within the Rust Standard Library provides the std::fs module and can perform a wide variety of file-related tasks, such as creating, reading, writing, and manipulating files. The File::open and File::create methods are particularly useful, as they allow you to open files in different modes, including read-only, write-only, or read-write.

Once a file is open, you can interact with its content and properties using read, write, and metadata methods. Rust's robust type system is designed to ensure safe and efficient file handling by preventing common errors like null pointer dereferencing and resource leaks. Thanks to its ownership and borrowing system, Rust provides error-free file handling, making it a reliable choice for developers.

This post will focus on two essential file-related activities: creating files with File and reading files using different techniques. Whether you're an experienced developer or new to Rust, understanding how to create and manipulate files is a crucial skill that will come in handy in many different programming contexts.

Why Would You Use the Rust File Object?

Let's explore creating, writing, and reading files using Rust's File object. This function can be a game-changer in certain situations, so it's essential to consider its potential benefits.

Imagine you're working on a logging service for a Rust-based web application. Your application generates logs that must be stored in separate log files for various components such as authentication, database interactions, and error handling. Your users have requested a feature that allows them to access and download specific log files.

To meet this challenge, your goal is to develop a Rust program that can:

1. Effortlessly gather log messages from different web application components.

2. Store these log messages in dedicated log files based on the component that generated them.

3. Allow users to request and download specific log files for analysis.

You can use the File::create() method to create and write log messages to their respective log files. Additionally, the File::open() method can retrieve and download these log files upon user request. With these tools at your disposal, you can confidently create a robust logging system that meets the needs of your users. Rust makes reading data easy!

Creating Modules To Read & Write

In this example, I will leverage Rust’s powerful module system to create a mod to create and read files. This will allow us to write clean and modular code and keep your main.rs file reserved for business logic.

Inside the main projects src directory, we can create a src/mods/ directory containing a series of directories housing our mods.

file_mod structure example

We can create this structure by creating the src/mods/ directory and then the file src/mods/mod.rs. We will add a pub mod entry for our file_mod in this mod file. Using the pub keyword allows this module to be accessible to external modules.

pub mod file_mod;

Next, we can create the directory which will contain the logic for our src/mods/file_mod. We will also need to create a mod.rs inside of src/mods/file_mod this will contain a Rust file for file creation logic called create_files and reading_files.

Inside of mods/file_mod/mod.rs we will add the following lines.

pub mod create_files;
pub mod reading_files;

In order to access our mods, we can simply add the following line inside our main.rs file which allows us to use our mods within our main function.

mod mods;

Once we have our mods directory set up, we can proceed to write the logic to create and read files in Rust using the File::create() and File::open() methods.

Exploring the File Object

The following sections will explore using the Rust File object to create and read files.

Creating Files & Writing To Files In Rust

When creating files in Rust, you can use the File::create() method to obtain a file handler. Then, you can use the write_all() method to write to the file.

pub fn create_file_write_all(file_path: &str, content: &[u8]){
    let mut file = fs::File::create(file_path).unwrap();
    file.write_all(content).unwrap();
}

One way to write to a file in Rust is by using the File::create() and write_all() convenience function, the write() method. This enables us to write a Rust oneliner with fewer imports. Using one-line of code allows better programming due to its efficiency.

pub fn create_file_write(file_path: &str, msg: &[u8]){ fs::write(file_path, msg).unwrap(); }

Reading Files In Rust

Let’s explore several different methods to read files in Rust. Each of these methods will leverage the File::open() method which will open the file or reading in Rust.

Reading Files To String

We can read the contents of a file to a string in Rust by using the File::open() method to define a file handler variable, then setting a string variable to hold our file contents, and finally calling the read_to_string() method.


// READ ENTIRE FILE INTO A STRING
pub fn read_file_string(read_path: &str) -> Result<String,  std::io::Error> {
    let mut file = File::open(read_path).unwrap();
    let mut file_contents = String::new();
    file.read_to_string(&mut file_contents).unwrap();
    Ok(file_contents)
}

Reading Files To A Vector

We can read the contents of a file to a vector in Rust by using the File::open() method to define a file handler variable, then setting a vector variable to hold our data, and finally calling the read_to_end() method.


// READ ENTIRE FILE INTO A BYTES VECTOR
pub fn read_file_vec(read_path: &str) -> Result<Vec<u8>,  std::io::Error>{
    let mut file = File::open(read_path)?;
    let mut data = vec![];
    file.read_to_end(&mut data).unwrap();
    Ok(data)
}

Increasing Read Efficiency With BufReader

A buffered Reader (bufread) is the most efficient way to read from a file in Rust. Using a buffered Reader allows us to reduce the overhead of disk access, minimize system call frequency, optimize memory usage, and improve read performance by prefetching data into memory. Let’s explore how to use this powerful Rust feature.

Reading to String Using BufReader

To use a BufReader object to return a string, we first use the File::open() method to define a file handler variable, then we define a new BufReader. Next, we set a string variable to hold our file contents, and finally, we call the read_to_string() method.


// READ USING BufReader INTO STRING
pub fn read_with_bufreader_str(read_path: &str) -> Result<String,  std::io::Error> {
    let mut file = File::open(read_path).unwrap();
    let mut buf_reader = BufReader::new(file);
    let mut file_contents = String::new();
    buf_reader.read_to_string(&mut file_contents).unwrap();
    Ok(file_contents)
}

Reading to Vector Using BufReader

To use a BufReader object to return a vector, we first use the File::open() method to define a file handler variable, then we define a new BufReader. Next, we set a vector variable to hold our file contents, and finally, we call the read_to_end() method.

// READ USING BufReader INTO VECTOR
pub fn read_with_bufreader_vec(read_path: &str) -> Result<Vec<u8>,  std::io::Error> {
    let mut file = File::open(read_path).unwrap();
    let mut buf_reader = BufReader::new(file);
    let mut data = vec![];
    buf_reader.read_to_end(&mut data).unwrap();
    Ok(data)
}

Putting It All Together - Creating, Reading, & Writing Files in Rust

Now it's time to put everything we've learned in this tutorial into action by assembling it in our /src/main.rs file. By importing our mods and utilizing the knowledge we've gained, we can showcase various methods of creating, writing, and reading files in Rust directly from main.rs within or fn main function. Using simple println statements, we can output the various file contents.

mod mods;

fn main() {
    let msg_write_all = b"Hello from write_all()!";
    let msg_write = b"Hello from write()!";
    let file_to_read = "./example.txt";
    // Create file using the write_all() method
    mods::file_mod::create_files::create_file_write_all(file_to_read, msg_write_all);
    // Create file using the write()
    mods::file_mod::create_files::create_file_write(file_to_read, msg_write);
    // Reading a file to a string
    let file_string_contents = mods::file_mod::reading_files::read_file_string(file_to_read).unwrap();
    println!("read_file_string(): {:?}", file_string_contents);
    // Reading a file to a vector
    let file_vec_contents = mods::file_mod::reading_files::read_file_vec(file_to_read).unwrap();
    println!("read_file_string_vec(): {:?}", file_vec_contents);
    // Reading a file to a string using a Buffered Reader
    let bufreader_str = mods::file_mod::reading_files::read_with_bufreader_str(file_to_read).unwrap();
    println!("read_with_bufreader_str(): {:?}", bufreader_str);
    // Reading a file to a vector using a Buffered Reader
    let bufreader_vec = mods::file_mod::reading_files::read_with_bufreader_vec(file_to_read).unwrap();
    println!("read_with_bufreader_vec(): {:?}", bufreader_vec);
}

Conclusion

In conclusion, it is very important for programmers to have a good understanding of how to handle files. The Rust std::fs library offers a reliable set of tools that can help programmers efficiently handle files, including methods to read and write files. We even saw how we can use a Rust convenience function for writing files in Rust! Whether you're new to Rust or need a refresher, this guide has shown you the impressive capabilities of the File object in the Rust standard library.

In this guide, we covered the basics of creating, writing, and reading files in Rust and various techniques for effectively manipulating files. With the knowledge gained from this post, you can now effectively handle files in your Rust projects.

While mastering file handling is important, it's also good to remember that Rust programming has other aspects to explore. Rust has a rich ecosystem and a supportive community that offers endless opportunities to create innovative solutions. So, keep experimenting, learning, and building amazing things with Rust!

Previous
Previous

Rust vs. C/C++: Ensuring Memory Safety & Security

Next
Next

Breaking Down CISA's Open Source Software Security Roadmap