Why It Never Touches Your Filesystem

My thinking behind making this a pure path resolver with zero filesystem calls.

When I designed xdgdir, I made one very important rule for myself: it must never perform any I/O. This means it doesn’t read files, write files, create directories, or even check if a path exists. It is a pure path resolver, and nothing more.

This was a very intentional choice. Here’s my thinking behind it.

Why I Made This Choice

  1. It’s Fast. Filesystem operations can be slow. Even just checking if a directory exists adds overhead. By sticking to path manipulation, xdgdir stays super fast. It’s just joining strings together based on some simple rules.
  2. It’s Predictable. A function that talks to the filesystem can fail for many reasons like permissions errors. xdgdir is totally predictable. Given the same environment variables, it will always give you the same PathBuf. This makes your code easier to test and reason about.
  3. It’s Flexible. Because it doesn’t do I/O, xdgdir works everywhere. You can use it in an async function without worrying about blocking the runtime. You can use it in a sandboxed environment where filesystem access might be restricted. The library doesn’t care.

What This Means For You

The key takeaway is that xdgdir only tells you where a directory should be. It does not guarantee that the directory exists.

It is your application’s job to handle the filesystem part. For example, before you write a config file, you should first make sure its parent directory exists. The easiest way to do this is with std::fs::create_dir_all.

Rust
use std::fs;
use xdgdir::BaseDir;

fn main() {
    let dirs = BaseDir::new("my-app").unwrap();

    // This will create the config directory if it doesn't exist.
    // If it already exists, it does nothing.
    fs::create_dir_all(&dirs.config).unwrap();

    let config_file_path = dirs.config.join("settings.toml");
    fs::write(config_file_path, "theme = 'dark'").unwrap();
}

This separation of concerns is a feature. xdgdir does one thing well: it resolves paths according to the spec. Your code then takes those paths and uses them however it needs to. I’m using .unwrap() in the example to keep it focused, but in a real app, you would want to handle these potential I/O errors.