Reading text files
The topic of today's post is opening and reading text files. Simple stuff, and something that you can find described on countless pages, including Rust's official documentation. I am doing this one for my record.
Reading the whole file with one function call
The first program:
gets file name from the command line
reads file contents into a string by calling read_to_string
writes the contents to the standard output
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: read_file <input-file>");
exit(0);
}
let file_path = &args[1];
let s = fs::read_to_string(file_path).unwrap();
println!("File contents:\n{}", s);
}
Reading file line by line
For this, we:
open file
create a reader, an instance of BufReader
from the reader, get an iterator to the lines
iterate through all the lines and write them to the standard output
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: read_file <input-file>");
exit(0);
}
let file_path = &args[1];
let file = File::open(file_path).unwrap();
let reader = io::BufReader::new(file);
let lines = reader.lines();
for line in lines {
let s = line.unwrap();
println!("Got line: {}", s);
}
}
Reporting error if the file cannot be opened
The only difference from the previous step is that now we don't call unwrap():
let file = File::open(file_path).unwrap();
If file opening fails for whatever reason, the program panics and displays something like this:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', textfiles-02.rs:16:38
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
This is OK for learning and practicing, but not so for production code. unwrap(), just like expect(), is something that should only be called when we expect the call to succeed. In other words, if this causes panic, then we have a bug.
In case of opening a file, if a file is not found or our program is not authorized to open it, it should not panic but rather display an error message and exit (or not, depending on what we want to do in the program).
For example, instead of calling unwrap, we can do this:
let file = match File::open(file_path) {
Ok(f) => f,
Err(e) => {
eprintln!("Error opening {}: {}", file_path, e.to_string());
std::process::exit(1);
}
};
That code behaves much more decently, and if the file is not found, writes an error message:
Error opening input.txt2: No such file or directory (os error 2)
and exits with code 1 (any code other than 0 means there was an error).
Get the code at
https://github.com/ajanicij/hashnode-code/tree/master/2023-10-02-text-files