Reading and Writing Files
Recipe | Crates | Categories |
---|---|---|
Read from and Write to a File | ||
Avoid Writing and Reading from the Same File | ||
Access a File Randomly Using a Memory Map |
File reading and writing happens primarily through the std::fs
module of the standard library, and the traits found in std::io
, like Read
and Write
.
Read from and Write to a File
The following example opens a file, writes to it, then reads its contents back in several different ways.
std::fs::File::create
↗ opens astd::fs::File
↗ for writing,std::fs::File::open
↗ for reading.std::fs::OpenOptions
may be used to open a file in another mode.File
implements thestd::io::Read
↗ trait for reading bytes from a source, thestd::io::Write
↗ trait for writing bytes to a destination, andstd::io::Seek
for moving within a stream of bytes.- You may use the
std::write!
andwriteln!
macros to write formatted data. File
does not buffer reads and writes. Wrap theFile
in astd::io::BufReader
↗ orBufWriter
(which implementBufRead
andBufWrite
), when performing many small read or write calls.std::io::BufRead::lines
returns astd::io::Lines
↗ iterator over individual lines of the file. LF or CRLF line endings are removed.std::fs::write
writes a slice as the entire contents of a file. This is a convenience function for usingFile::create
andstd::io::Write::write_all
with fewer imports.std::fs::read
andstd::fs::read_to_string
read the entire contents of a file into a bytes vector or a string, respectively. They are convenience functions for usingFile::open
andstd::io::Read::read_to_end
orread_to_string
without an intermediate variable.
//! Demonstrates writing to and reading from a file. use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::io; use std::io::BufRead; use std::io::BufReader; use std::io::Write; fn write_to_file() -> Result<(), io::Error> { // The simplest way is to write a slice as the entire contents of a file // with `fs::write`. This function will create a file if it does not // exist, and will entirely replace its contents if it does. let path_str = "temp/lines.txt"; fs::write(path_str, b"Lorem ipsum")?; // Or open a file in write-only mode, then write. // `create` creates a file if it does not exist, and truncates it if it // does: let mut output = File::create(path_str)?; // Write an entire buffer to the file: output.write_all(b"Writing some bytes.\n")?; // Or write a formatted string to the file using e.g. the `write!` macro: let text = "Rust 💖 Fun\n"; write!(&mut output, "Writing: {text}")?; // Or open the file in append mode: let mut file = OpenOptions::new() .append(true) // Set the append mode to true. .create(true) // Create the file if it doesn't exist. .open(path_str)?; // `writeln!` writes a (plain or formatted) string followed by a newline: writeln!(file, "This is a new line appended to the file.")?; Ok(()) } fn read_from_file() -> Result<(), io::Error> { let path_str = "temp/lines.txt"; // Read the entire contents of a file into a string. // The contents of the file must be valid UTF-8. let message: String = fs::read_to_string(path_str)?; println!("`read_to_string`:\n {message}"); // Or read the entire contents of a file into a bytes vector: let data: Vec<u8> = fs::read("temp/lines.txt")?; assert_eq!(data[0..3], [87, 114, 105]); // Or open the file for reading: let input = File::open(path_str)?; // Call a method of the `Read` trait, // or add buffering: let buffered = BufReader::new(input); // Iterate over the lines of this reader. // The `?` operator propagates the error if one occurs. for line in buffered.lines() { println!("{}", line?); } Ok(()) } fn main() -> Result<(), io::Error> { write_to_file()?; read_from_file()?; Ok(()) }
Avoid Writing and Reading from the Same File
This example demonstrates how to check whether two files refer to the same file or directory, using same-file::Handle
↗. Use this technique to check whether the standard input and standard output (or error) of a process are the same (due to piping / redirection) and avoid reading and writing from the same file. You may also use same-file
check whether two file paths correspond to the same file:
use std::io::Error; use same_file::Handle; /// Check whether we are reading & writing to the same file. fn main() -> Result<(), Error> { // Get a `Handle` for the standard input, output, and error: let stdin = Handle::stdin()?; let stdout = Handle::stdout()?; let stderr = Handle::stderr()?; // Check if the standard input is the same as the standard output or error // handle: if (stdin == stdout) | (stdin == stderr) { println!("You are reading and writing to the same file!"); // Return an error to exit the process: // return Err(Error::other( // "You are reading and writing to the same file!", // )); } if stdout == stderr { println!("stdout == stderr"); } // Use `from_path` to get a `Handle` for a file. // Note that the underlying `File` is opened in read-only mode on all // platforms: // let path_to_read = Path::new("temp/new.txt"); // let _handle = Handle::from_path(path_to_read)?; // You may also check whether two file paths correspond to the same file // (due to hard or symbolic links): #[cfg(unix)] assert!(same_file::is_same_file("/bin/sh", "/usr/bin/sh")?); Ok(()) }
Access a File Randomly Using a Memory Map
In the following example, we create a memory map of a file using memmap2
↗ and simulate some non-sequential reads from the file. Using a memory map simplifies navigation of a std::fs::File
↗ - by indexing into a slice rather than dealing with std::fs::File::seek
↗.Memory mapping may also enhance performance in applications that require large data processing or frequent file access.
Note that the memmap2::Mmap::map
↗ function assumes the file behind the memory map is not being modified at the same time by another process or else a race condition↗ may occur.
It is also possible to write to a File
as if it were a &mut [u8]
by using mutable memory maps.
//! Demonstrates reading from a memory-mapped file. use std::fs::File; use std::io::Error; use std::io::Write; use memmap2::Mmap; use memmap2::MmapMut; use memmap2::MmapOptions; fn main() -> Result<(), Error> { write!( File::create("temp/content.txt")?, "My hovercraft is full of eels!" )?; // Open the file for reading: let file = File::open("temp/content.txt")?; // Create a read-only memory map of the file. This is unsafe because it // relies on the file not being modified externally: let map = unsafe { Mmap::map(&file)? }; // `Mmap` is `Deref<Target = [u8]>` and therefore can be used like a slice. // Assert that a slice of the memory map matches the expected bytes: assert_eq!(&map[3..13], b"hovercraft"); // Collect bytes from the memory map at random indexes: let random_indexes = [0, 1, 2, 19, 22, 10, 11, 29]; let random_bytes: Vec<u8> = random_indexes.iter().map(|&idx| map[idx]).collect(); assert_eq!(&random_bytes[..], b"My loaf!"); // A file-backed memory map remains valid even after the File is dropped. // You also create mutable memory-mapped buffers - either file-backed or // anonymous - with `MmapMut`. // - A file-backed buffer may be used to read from or write to a file. // - An anonymous buffer may be used any place that an in-memory byte buffer // is needed. let mut mmap_mut: MmapMut = MmapOptions::new().len(13).map_anon()?; mmap_mut.copy_from_slice(b"Hello, world!"); Ok(()) }
Related Topics
- File Watching.
- Paths.
- Standard Input and Output.
- Temporary Files.