Rust File Error Kinds
File error kinds are the named categories of an io error. A missing file has one kind.
Ask for the kind
kind() answers with an ErrorKind. Debug printing shows its name:
let err = fs::read_to_string("ghost.txt").unwrap_err();
println!("kind = {:?}", err.kind());React to the kind
A missing settings file is normal on a first run. Give it defaults. Every other failure still travels up with ?:
let text = match fs::read_to_string("settings.txt") {
Ok(text) => text,
Err(e) if e.kind() == ErrorKind::NotFound => String::from("default settings"),
Err(e) => return Err(e),
};
println!("{text}");A taken name says AlreadyExists
create_new is the strict creator from the Temporary Files lesson. Its refusal has its own kind:
fs::write("taken.txt", "x")?;
let again = OpenOptions::new().write(true).create_new(true).open("taken.txt");
println!("kind = {:?}", again.unwrap_err().kind());A folder is not a file
The path exists. It is just the wrong kind of thing:
let err = fs::read_to_string("some_folder").unwrap_err();
println!("kind = {:?}", err.kind());A missing folder says NotFound too
The failing piece is the path, not the file. A missing folder on the way gives NotFound:
let err = fs::write("no/such/folder/f.txt", "x").unwrap_err();
println!("kind = {:?}", err.kind());Example
use std::fs::{self, OpenOptions};
use std::io::ErrorKind;
fn main() {
let _ = fs::remove_file("settings.txt");
let _ = fs::remove_dir_all("a_folder");
fs::create_dir_all("a_folder").expect("could not make the folder");
// first run ever: the settings file is missing, so write the defaults
match fs::read_to_string("settings.txt") {
Ok(_) => println!("settings found"),
Err(e) if e.kind() == ErrorKind::NotFound => {
println!("missing: {:?}", e.kind());
fs::write("settings.txt", "theme = dark").expect("could not write defaults");
println!("wrote defaults");
}
Err(e) => panic!("unexpected error: {e}"),
}
// a create-only open now refuses, because the name is taken
let again = OpenOptions::new().write(true).create_new(true).open("settings.txt");
println!("second create: {:?}", again.unwrap_err().kind());
// a folder cannot be read as a text file
let err = fs::read_to_string("a_folder").unwrap_err();
println!("folder as file: {:?}", err.kind());
// this time the file loads
let text = fs::read_to_string("settings.txt").expect("could not load settings");
println!("loaded: {text}");
fs::remove_file("settings.txt").expect("could not clean up");
fs::remove_dir_all("a_folder").expect("could not clean up");
}Key points
- Use error.kind() to ask an io::Error what kind of failure it is: the kind is an ErrorKind value
- ErrorKind::NotFound means the file does not exist: a missing folder on its path counts too
- ErrorKind::PermissionDenied means the system refused: the file is read-only, or the folder is not yours to write in
- ErrorKind::AlreadyExists is the answer create_new gives when the name is already taken
- ErrorKind::IsADirectory is the answer when a file call meets a folder: reading a folder as text is one case
- An io error carries a kind that names which category of failure happened.
In this example, one small program meets three kinds in a row. The settings file is missing, so the kind is NotFound. The program writes defaults instead of crashing. A create-only open then meets the file it just wrote. The kind is AlreadyExists. Reading a folder as text gives IsADirectory. The last read succeeds. It loads the defaults written earlier.
Note
Match on the kind, never on the message text.
Messages change between systems and languages.
Kinds are stable API.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Your program should start with defaults when the user has deleted the config file. Which kind do you match?