Rust Reading Files
Reading a file loads words from outside a program into it. fs::read_to_string returns the file as a String.
Read a whole file into a String
File work lives in std::fs — fs is short for file system. read_to_string opens a file and reads all of it. It returns a Result<String>. The file might be missing or unreadable. Add .expect("message") in a simple program. Or use ? in a function that returns Result. Then the caller handles a missing file.
use std::fs;
let contents = fs::read_to_string("story.txt")
.expect("Should have been able to read the file");Walk the text line by line
.lines() walks the String one line at a time — each line comes without its ending newline. For the three-line story file of the closing example, this prints:
for line in contents.lines() {
println!("{line}");
}Read raw bytes instead
When the file is not text, fs::read gives you the raw bytes as a Vec<u8>. The same story file measures:
let bytes = fs::read("story.txt").expect("readable file");
println!("bytes = {}", bytes.len());Open the file itself
File::open gives you the file rather than its whole text at once. A File handle can be read a piece at a time. The file closes itself when the handle goes out of scope. The reading methods are not built into File itself. read_to_string and its friends come from the std::io::Read trait. So the trait must be in scope before the methods appear:
use std::io::Read;
let mut file = File::open("story.txt")?;
let mut text = String::new();
file.read_to_string(&mut text)?;Stream a big file with BufReader
Wrap a File in io::BufReader. Use .lines() from the BufRead trait to stream a big file. It goes one line at a time. Each line arrives as its own io::Result, so a reading error can surface in the middle of the file:
use std::io::{BufRead, BufReader};
let file = File::open("story.txt")?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line?;
println!("{line}");
}Example
use std::fs;
fn main() {
let contents = fs::read_to_string("story.txt").expect("Should have been able to read the file");
for line in contents.lines() {
if line.contains("cat") {
println!("{line}");
}
}
}Key points
- A plain file name like story.txt is looked up in one folder.
- That is the folder the program runs from.
- It is not the folder the code lives in.
- Reading a file brings outside text into a program as a value the program owns.
In this example, the file story.txt holds three lines: The cat sat, A dog ran fast, and The cat slept. read_to_string reads the whole file into contents. If the file were missing, expect would stop the program with its message. The for loop then walks contents.lines() one line at a time. contains("cat") is true for the first and third lines, so only those two print. The dog line is skipped, quietly and correctly. The Tokio lessons meet the idea of reading files in pieces later.
Note
read_to_string reads the whole file at once.
That is simple and fine for small files.
Very large files are read in pieces instead.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
What does fs::read_to_string return when the file cannot be read?