Step 4 of 6
Reading files
Most tools work on files. std::fs has the functions you'll use every day:
use std::fs;
use std::io::ErrorKind;
fn main() {
fs::write("notes.txt", "buy milk\nfeed the crab\n").expect("could not write notes.txt");
let text = fs::read_to_string("notes.txt").expect("could not read notes.txt");
println!("{} lines", text.lines().count());
match fs::read_to_string("missing.txt") {
Ok(t) => println!("read {} bytes", t.len()),
Err(e) if e.kind() == ErrorKind::NotFound => println!("missing.txt does not exist"),
Err(e) => println!("error: {e}"),
}
}
2 lines
missing.txt does not exist
Piece by piece:
fs::write(path, contents)creates the file (or replaces it) with the given text.fs::read_to_string(path)reads a whole file into aString. It returnsio::Result<String>, which isResult<String, io::Error>.- A relative path like
notes.txtis relative to the current working directory: the folder the user ran the tool from, not where the program file lives. e.kind()tells you what kind of I/O error it was:ErrorKind::NotFound,ErrorKind::PermissionDenied,ErrorKind::IsADirectory... Match on it when your tool should react differently (a missing config file might just mean "use defaults"). Printingegives the OS's message, likeNo such file or directory (os error 2).
Big files: line by line
read_to_string loads the whole file into memory. That's fine for configs and most inputs; for a 20 GB log, read it line by line with a BufReader, which keeps only a buffer in memory:
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() -> std::io::Result<()> {
std::fs::write("app.log", "INFO start\nERROR disk full\nINFO stop\nERROR timeout\n")?;
let reader = BufReader::new(File::open("app.log")?);
for (i, line) in reader.lines().enumerate() {
let line = line?;
if line.starts_with("ERROR") {
println!("{}: {line}", i + 1);
}
}
Ok(())
}
2: ERROR disk full
4: ERROR timeout
File::openopens for reading;BufReader::newwraps it so reads happen in big chunks instead of one system call per byte..lines()(from theBufReadtrait, hence the import) yieldsio::Result<String>for each line, because reading can fail halfway (a disk error, invalid UTF-8).let line = line?;handles it.- To add to a file instead of replacing it, open it with
OpenOptions::new().append(true).create(true).open(path)?and write lines withwriteln!(file, "...")?(fromstd::io::Write).
Common mistakes
- Checking
Path::new(p).exists()before opening. The file can disappear between the check and the open. Just open it and handle the error. unwraponread_to_stringof a path the user typed: a typo becomes a panic. Print a message that names the file and exit with a failure code.- Forgetting
use std::io::BufRead;: then.lines()on aBufReaderfails witherror[E0599]: no method named `lines` found for struct `BufReader<R>`, and the compiler suggests the import.
Your task
Write wc. For each file named in the arguments print <lines> <words> <bytes> <name> (words are whitespace-separated; bytes is the length of the text). If more than one file was given, end with a <lines> <words> <bytes> total line summing the files that could be read. A file that can't be read prints wc: <name>: <error> to stderr, the tool carries on with the other files, and exits with code 1 at the end. No arguments: usage: wc <file>... to stderr, exit code 2.
Challenge 2
Write head [-n N] <file>: print the first N lines of the file (default 10). A bad or missing N (-n x, -n at the end) or a missing file name prints a usage line to stderr and exits 2; a file that can't be read prints head: <name>: <error> to stderr and exits 1.
Challenge 3
Write colsum <file> <column>, a tiny CSV summarizer. The file's first line is a header of comma-separated column names; every other non-empty line has the same number of comma-separated fields. Print sum of <column>: <total> with two decimals. Errors go to stderr: wrong number of arguments exits 2; a file that can't be read, an unknown column (unknown column "x") or a value that isn't a number (line 3: "abc" is not a number) exits 1.
Previous: Exit codes and stderr Next: Reading stdin when no file is given