Rust Files
Variables disappear when a program ends. To keep information for next time, such as a high score, a to-do list or a diary, a program saves it in a file. Rust's file tools live in std::fs, short for file system. In our editor, files your program creates appear in the file list on the left, so you can open them and see what was written.
Writing and reading a whole file
The simplest tools are fs::write, which saves a piece of text into a file (creating it, or replacing what was there), and fs::read_to_string, which reads a whole file into a String:
use std::fs;
fn main() {
fs::write("hello.txt", "Hello from Rust!\nThis is line two.\n").expect("could not write");
let text = fs::read_to_string("hello.txt").expect("could not read");
print!("{}", text);
println!("The file has {} lines.", text.lines().count());
}Hello from Rust! This is line two. The file has 2 lines.
Both return a Result, because working with files can fail: the disk might be full, or the file might not exist. Here expect stops the program with a message if that happens.
Going through a file line by line
lines() splits text into its lines, which is the most common way to process a file. Here we save a few marks and then work out the average from the file:
use std::fs;
fn main() {
fs::write("marks.txt", "Asha 91\nRavi 78\nLeela 85\n").expect("could not write");
let text = fs::read_to_string("marks.txt").expect("could not read");
let mut total = 0;
let mut count = 0;
for line in text.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
let mark: i32 = parts[1].parse().expect("a number");
println!("{} scored {}", parts[0], mark);
total += mark;
count += 1;
}
println!("Average: {:.1}", total as f64 / count as f64);
}Asha scored 91 Ravi scored 78 Leela scored 85 Average: 84.7
Adding to a file
fs::write replaces the whole file. To add lines at the end instead, open the file with OpenOptions in append mode and write with writeln!, which works like println! but into the file:
use std::fs::{self, OpenOptions};
use std::io::Write;
fn main() {
fs::write("diary.txt", "Monday: started learning Rust\n").expect("could not write");
let mut file = OpenOptions::new().append(true).open("diary.txt").expect("could not open");
writeln!(file, "Tuesday: learnt about ownership").expect("could not write");
writeln!(file, "Wednesday: wrote my first struct").expect("could not write");
print!("{}", fs::read_to_string("diary.txt").expect("could not read"));
}Monday: started learning Rust Tuesday: learnt about ownership Wednesday: wrote my first struct
use std::io::Write; brings in the Write trait, which gives files their write abilities. If you forget it, the compiler suggests adding it.
When the file is missing
Instead of stopping the program, a match can handle a missing file gracefully, for example by starting fresh:
use std::fs;
fn main() {
match fs::read_to_string("settings.txt") {
Ok(text) => println!("Settings: {}", text),
Err(e) => println!("No settings yet ({}), using the defaults.", e),
}
}No settings yet (No such file or directory (os error 44)), using the defaults.
The exact wording of the error comes from the system the program runs on; on your own computer it might look slightly different.
Practice exercises
- Write a program that saves your three favourite films to
films.txt, one per line, and then prints them numbered. - Make a counter that reads a number from
count.txt, adds one, and writes it back (start at 0 if the file is missing). - Append the current step of a recipe to
recipe.txteach time you run the program.