Create portable applications that keep files together with the executable.
Simple, zero-dependency library for creating portable applications where configuration, data, and executable stay together as a deployable unit.
use app_path::app_path;
// Files relative to your executable - not current directory!
let config = app_path!("config.toml"); // β /path/to/exe_dir/config.toml
let database = app_path!("data/users.db"); // β /path/to/exe_dir/data/users.db
// Environment override for deployment
let logs = app_path!("logs/app.log", env = "LOG_PATH");
// β Uses LOG_PATH if set, otherwise /path/to/exe_dir/logs/app.log
// Acts like std::path::Path + creates directories
if !config.exists() {
config.create_parents()?; // Creates parent directories
std::fs::write(&config, "default config")?;
}
Approach | Problem | AppPath Solution |
---|---|---|
Hardcoded paths | Breaks when moved | β Works anywhere |
current_dir() |
Depends on where user runs program | β Always relative to executable |
System directories | Scatters files across system | β Self-contained, portable |
current_exe() |
Manual path joining, no caching, verbose error handling | β Clean API, automatic caching, ergonomic macros |
- π Zero dependencies - Only standard library
- β¨ Ergonomic macro - Clean syntax with
app_path!
- π Cross-platform - Windows, Linux, macOS
- β‘ High performance - Static caching, minimal allocations
- π§ Flexible deployment - Environment overrides
- π‘οΈ Thread-safe - Concurrent access safe
- π¦ Portable - Entire app moves as one unit
use app_path::app_path;
// Application base directory
let app_base = app_path!(); // β /path/to/exe_dir/
// Simple paths
let config = app_path!("config.toml");
let database = app_path!("data/users.db");
// Environment overrides
let logs = app_path!("logs/app.log", env = "LOG_PATH");
let cache = app_path!("cache", env = "CACHE_DIR");
// Custom override logic
let data_dir = app_path!("data", override = {
std::env::var("DATA_DIR")
.or_else(|_| std::env::var("XDG_DATA_HOME").map(|p| format!("{p}/myapp")))
.ok()
});
// Function-based override (great for XDG support)
let config_dir = app_path!("config", fn = || {
std::env::var("XDG_CONFIG_HOME")
.or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.config/myapp")))
.ok()
});
// Variable capturing
let version = "1.0";
let versioned_cache = app_path!(format!("cache-{version}"));
// Directory creation
app_path!("logs/app.log").create_parents()?; // Creates `logs/` for the `app.log` file
app_path!("temp").create_dir()?; // Creates `temp/` directory itself
Note: Use
try_app_path!
instead ofapp_path!
when you needResult
return values for explicit error handling (same syntax, just returnsResult<AppPath, AppPathError>
instead of panicking).
use app_path::AppPath;
// Basic constructors
let app_base = AppPath::new(); // Executable directory
let config = AppPath::with("config.toml"); // App base + path
// Override constructors
let config = AppPath::with_override("config.toml", std::env::var("CONFIG_PATH").ok());
// Function-based override constructors
let logs = AppPath::with_override_fn("logs", || {
std::env::var("XDG_STATE_HOME")
.or_else(|_| std::env::var("HOME").map(|h| format!("{h}/.local/state/myapp")))
.ok()
});
Note: All constructors have
try_*
variants that returnResult
instead of panicking (e.g.,try_new()
,try_with()
,try_with_override()
,try_with_override_fn()
).
use app_path::app_path;
fn load_config() -> Result<Config, Box<dyn std::error::Error>> {
let config_path = app_path!("config.toml", env = "CONFIG_PATH");
if !config_path.exists() {
std::fs::write(&config_path, include_str!("default_config.toml"))?;
}
let content = std::fs::read_to_string(&config_path)?;
Ok(toml::from_str(&content)?)
}
use app_path::app_path;
fn process_templates(name: &str) -> Result<(), Box<dyn std::error::Error>> {
let template = app_path!("templates").join(format!("{name}.hbs"));
let output = app_path!("output", env = "OUTPUT_DIR").join("result.html");
output.create_parents()?; // Creates output/ directory
let content = std::fs::read_to_string(&template)?;
std::fs::write(&output, render_template(&content)?)?;
Ok(())
}
use app_path::app_path;
// Same binary, different environments:
// Development: uses "./config/app.toml"
// Production: CONFIG_PATH="/etc/myapp/config.toml" overrides to absolute path
let config = app_path!("config/app.toml", env = "CONFIG_PATH");
// Conditional deployment paths
let logs = if cfg!(debug_assertions) {
app_path!("debug.log")
} else {
app_path!("logs/production.log", env = "LOG_FILE")
};
AppPath uses fail-fast by default for better developer experience:
app_path!
andAppPath::new()
- Panic on critical system errors (executable location undetermined)try_app_path!
andAppPath::try_new()
- ReturnResult
for explicit error handling
This design makes sense because if the system can't determine your executable location, there's usually no point continuing - it indicates severe system corruption or unsupported platforms.
For most applications: Use the panicking variants (app_path!
) - they fail fast on unrecoverable errors.
For libraries: Use the fallible variants (try_app_path!
) to let callers handle errors gracefully.
use app_path::{AppPath, AppPathError};
// Libraries should handle errors explicitly
match AppPath::try_with("config.toml") {
Ok(path) => println!("Config: {}", path.display()),
Err(AppPathError::ExecutableNotFound(msg)) => {
eprintln!("Cannot find executable: {msg}");
}
Err(AppPathError::InvalidExecutablePath(msg)) => {
eprintln!("Invalid executable path: {msg}");
}
Err(AppPathError::IoError(io_err)) => {
eprintln!("I/O operation failed: {io_err}");
// Access original error details:
match io_err.kind() {
std::io::ErrorKind::PermissionDenied => {
eprintln!("Permission denied - try running with elevated privileges");
}
std::io::ErrorKind::NotFound => {
eprintln!("Parent directory doesn't exist");
}
_ => eprintln!("Other I/O error: {io_err}"),
}
}
}
app-path
integrates seamlessly with popular Rust path crates, letting you combine the best tools for your specific needs:
Crate | Use Case | Integration Pattern |
---|---|---|
camino |
UTF-8 path guarantees for web apps | Utf8PathBuf::from_path_buf(app_path.into())? |
typed-path |
Cross-platform type-safe paths | WindowsPath::new(app_path.to_bytes()) |
use app_path::app_path;
use camino::Utf8PathBuf;
let static_dir = app_path!("web/static", env = "STATIC_DIR");
let utf8_static = Utf8PathBuf::from_path_buf(static_dir.into())
.map_err(|_| "Invalid UTF-8 path")?;
let config = serde_json::json!({ "static_files": utf8_static });
use app_path::app_path;
use typed_path::{WindowsPath, UnixPath};
let dist_dir = app_path!("dist");
let path_bytes = dist_dir.to_bytes();
let win_path = WindowsPath::new(path_bytes); // Uses \ on Windows
let unix_path = UnixPath::new(path_bytes); // Uses / on Unix
use app_path::AppPath;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Config {
log_file: String, // Standard approach - readable and portable
data_dir: String, // Works across all platforms
}
// Convert when using - clean separation of concerns
let config: Config = serde_json::from_str(&config_json)?;
let log_path = AppPath::with(&config.log_file);
let data_path = AppPath::with(&config.data_dir);
[dependencies]
app-path = "1.1"
For comprehensive API documentation, examples, and guides, see docs.rs/app-path.