Extra methods on Path for ergonomic and chainable fs operations.
Stop juggling fs functions, start chaining Path methods.
Supports std (default) and tokio (feature flag).
The standard library scatters file system operations across std::fs as free functions:
// Path is always the odd one out
let config_file = config_dir.join("config.toml");
fs::create_dir_all(&config_dir)?;
fs::write(&config_file, config)?;
fs::set_permissions(&config_file, permissions)?;With PathExt, file system operations are now where they belong — on the Path itself:
use path_extra::std::path::PathExt as _;
// Path stays in focus and chains naturally
config_dir.create_dir_all()?
.join("config.toml")
.write(config)?
.set_permissions(permissions)?;# Supports std by default
cargo add path-extra
# To enable Tokio support
cargo add path-extra --features=tokioOperations on the same Path compose elegantly into a single expression:
let metadata = config_file.metadata()?;
// Atomic file update
config_file.with_extension("tmp")
.write_new(config)?
.set_permissions(metadata.permissions())?
.rename(config_file)?;Use ergonomic _if_exists / _if_not_exists variants to deal with Ok(None) instead:
// Before: Tedious and easy to get wrong
let config = match fs::read(&config_file) {
Ok(config) => config,
Err(err) if err.kind() == io::ErrorKind::NotFound => DEFAULT_CONFIG,
Err(err) => return Err(err),
};
// After: Intent is clear, noise is gone
let config = config_file.read_if_exists()?.unwrap_or(DEFAULT_CONFIG);use path_extra::std::path::{OptionPathExt as _, PathExt as _};
// Idempotent file creation
config_dir
.create_dir_all()?
.join("config.toml")
.write_new_if_not_exists(DEFAULT_CONFIG)?
.set_permissions(permissions)?;target.symlink(&link)?; // Store target as-is
target.symlink_absolute(&link)?; // Resolve target to absolute
target.symlink_relative(&link)?; // Compute relative target from link parenttarget.symlink_atomic(&link)?;
target.symlink_absolute_atomic(&link)?;
target.symlink_relative_atomic(&link)?;
target.hard_link_atomic(&link)?;Both .exists() and .try_exists() don't work with broken symlinks:
// Before: Broken symlink
broken.exists() // false
broken.try_exists()? // false
// After: See the symlink itself
broken.exists_nofollow() // true
broken.try_exists_nofollow()? // trueThe following _nofollow variants are also provided for convenience:
// Infallible — Return false on error like .exists()
path.exists_nofollow()
path.is_file_nofollow()
path.is_dir_nofollow()
// Fallible — Propagate errors just like .try_exists()
path.try_exists_nofollow()
path.try_is_file_nofollow()
path.try_is_dir_nofollow()The standard Path only has sync methods, using the wrong ones will block your async runtime.
Enable the tokio feature flag and PathExt gives you async methods directly on Path:
use path_extra::tokio::path::PathExt as _;
let metadata = config_file.metadata_async().await?;
config_file.with_extension("tmp")
.write_new(config).await?
.set_permissions(metadata.permissions()).await?
.rename(config_file).await?;Import AsyncPathExt to chain the entire expression and .await only once at the end:
use path_extra::tokio::path::{AsyncPathExt as _, PathExt as _};
config_file.with_extension("tmp")
.write_new(config)
.set_permissions(metadata.permissions())
.rename(config_file)
.await?;Both OptionPathExt and AsyncOptionPathExt are available under tokio as well.