How to create a directory and its parents
Short answer: fs::create_dir_all("demo/data/logs") creates every missing parent on the way down. fs::create_dir makes exactly one level and fails with NotFound if the parent isn’t there. Use create_dir_all unless you have a reason to want the failure.
From a fresh project — cargo new mkdir-demo, then replace the contents of src/main.rs with each block below and run it with cargo run. Each block builds what it needs from an empty directory, so it runs on its own and in any order. The remove_dir_all call at the top is a fixture; in real code the cleanup wouldn’t be there.
One level at a time
Section titled “One level at a time”create_dir is mkdir: one new directory, and the error you get when its parent is missing:
use std::fs;
fn main() -> std::io::Result<()> { // Removed first so the block can run twice. let _ = fs::remove_dir_all("demo");
fs::create_dir("demo")?; println!("created demo");
// demo/data does not exist, so demo/data/logs has a missing parent. match fs::create_dir("demo/data/logs") { Ok(()) => println!("created demo/data/logs"), Err(err) => println!("{}: {err}", err.kind()), } Ok(())}created demoentity not found: No such file or directory (os error 2)demo is created, and the nested call fails with NotFound — “No such file or directory”, the same thing mkdir demo/data/logs says in a shell. There is no flag to ask for parents; the one-call version is a different function.
The whole chain: fs::create_dir_all
Section titled “The whole chain: fs::create_dir_all”create_dir_all is mkdir -p. One call builds the path top-down, and the interesting part is what happens when you call it twice — it doesn’t complain:
use std::fs;
fn main() -> std::io::Result<()> { // Removed first so the block can run twice. let _ = fs::remove_dir_all("demo");
fs::create_dir_all("demo/data/logs")?; println!("created demo/data/logs");
// Everything already exists now. fs::create_dir_all("demo/data/logs")?; println!("create_dir_all again: ok");
// But create_dir does complain. match fs::create_dir("demo/data/logs") { Ok(()) => println!("created demo/data/logs"), Err(err) => println!("{}: {err}", err.kind()), } Ok(())}created demo/data/logscreate_dir_all again: okentity already exists: File exists (os error 17)The second create_dir_all returns Ok because the directory already exists — the standard library documents that as normal behaviour, not a special case you have to handle. create_dir on the same path gives AlreadyExists: “File exists (os error 17)”. This is the difference that matters in programs: setup code that runs on every start can call create_dir_all unconditionally and check the Result for real failures, no exists() check in front of it.
When it still fails
Section titled “When it still fails”create_dir_all can’t do the impossible. If something that isn’t a directory is sitting in the path, it stops there:
use std::fs;
fn main() -> std::io::Result<()> { // Removed first so the block can run twice. let _ = fs::remove_dir_all("demo");
fs::create_dir_all("demo")?; fs::write("demo/notes.txt", "hello\n")?;
// demo/notes.txt is a file, so it can't become a parent. match fs::create_dir_all("demo/notes.txt/archive") { Ok(()) => println!("created demo/notes.txt/archive"), Err(err) => println!("{}: {err}", err.kind()), } Ok(())}not a directory: Not a directory (os error 20)NotADirectory — the file demo/notes.txt would have to be a directory for archive to live under it. Unlike the missing-parent case this is a real conflict, not a call you should have made differently: something else created that path first, and the fix is in your program’s layout, not in the arguments.
Related
Section titled “Related”- How to delete a file or a whole directory — the mirror image:
remove_dirandremove_dir_all - How to check whether a file or directory exists — usually unnecessary in front of
create_dir_all - Reading and writing files — the rest of the chapter