app_path/functions.rs
1use std::path::{Path, PathBuf};
2use std::sync::OnceLock;
3
4use crate::error::{try_exe_dir_init, AppPathError};
5
6// Global executable directory - computed once, cached forever
7static EXE_DIR: OnceLock<PathBuf> = OnceLock::new();
8
9/// Get the executable's directory.
10///
11/// **Recommended for most applications.** Returns the directory containing the current executable.
12/// Use this for building custom paths or integrating with other APIs.
13///
14/// ## Performance
15///
16/// - **First call**: Determines and caches the executable directory
17/// - **Subsequent calls**: Returns cached result immediately (no system calls)
18/// - **Thread-safe**: Safe to call from multiple threads
19///
20/// # Panics
21///
22/// Panics if executable location cannot be determined (extremely rare):
23/// - `std::env::current_exe()` fails
24/// - Executable path is empty (system corruption)
25///
26/// After first successful call, never panics (uses cached result).
27///
28/// # Examples
29///
30/// ```rust
31/// use app_path::exe_dir;
32/// use std::fs;
33///
34/// // Basic usage
35/// let exe_directory = exe_dir();
36/// println!("Executable directory: {}", exe_directory.display());
37///
38/// // Build custom paths
39/// let config = exe_dir().join("config.toml");
40/// let data_dir = exe_dir().join("data");
41///
42/// // Use with standard library
43/// if config.exists() {
44/// let content = fs::read_to_string(&config)?;
45/// }
46/// fs::create_dir_all(&data_dir)?;
47/// # Ok::<(), Box<dyn std::error::Error>>(())
48/// ```
49pub fn exe_dir() -> &'static Path {
50 match try_exe_dir() {
51 Ok(path) => path,
52 Err(e) => panic!("Failed to determine executable directory: {e}"),
53 }
54}
55
56/// Get the executable's directory (fallible).
57///
58/// **Use this only for libraries or specialized applications.** Most applications should
59/// use [`exe_dir()`] for simpler, cleaner code.
60///
61/// # Examples
62///
63/// ```rust
64/// use app_path::try_exe_dir;
65///
66/// // Library with graceful error handling
67/// match try_exe_dir() {
68/// Ok(exe_dir) => {
69/// println!("Executable directory: {}", exe_dir.display());
70/// let config = exe_dir.join("config.toml");
71/// }
72/// Err(e) => {
73/// eprintln!("Failed to get executable directory: {e}");
74/// // Implement fallback strategy
75/// }
76/// }
77///
78/// // Use with ? operator
79/// fn get_config_dir() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
80/// let exe_dir = try_exe_dir()?;
81/// Ok(exe_dir.join("config"))
82/// }
83/// ```
84///
85/// Once the executable directory is successfully determined by either this function or [`exe_dir()`],
86/// the result is cached globally and all subsequent calls to both functions will use the cached value.
87/// This means that after the first successful call, `try_exe_dir()` will never return an error.
88///
89/// # Returns
90///
91/// * `Ok(&'static Path)` - The directory containing the current executable
92/// * `Err(AppPathError)` - Failed to determine executable location
93///
94/// # Performance
95///
96/// This function is highly optimized:
97/// - **First call**: Determines and caches the executable directory
98/// - **Subsequent calls**: Returns the cached result immediately (no system calls)
99/// - **Thread-safe**: Safe to call from multiple threads concurrently
100///
101/// # Examples
102///
103/// ## Library Error Handling
104///
105/// ```rust
106/// use app_path::try_exe_dir;
107///
108/// // Handle the error explicitly
109/// match try_exe_dir() {
110/// Ok(exe_dir) => {
111/// println!("Executable directory: {}", exe_dir.display());
112/// // Use exe_dir for further operations
113/// }
114/// Err(e) => {
115/// eprintln!("Failed to get executable directory: {e}");
116/// // Implement fallback strategy
117/// }
118/// }
119/// ```
120///
121/// ## Use with ? Operator
122///
123/// ```rust
124/// use app_path::try_exe_dir;
125///
126/// // Use with the ? operator in functions that return Result
127/// fn get_config_dir() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
128/// let exe_dir = try_exe_dir()?;
129/// Ok(exe_dir.join("config"))
130/// }
131/// ```
132pub fn try_exe_dir() -> Result<&'static Path, AppPathError> {
133 // If already cached, return it immediately
134 if let Some(cached_path) = EXE_DIR.get() {
135 return Ok(cached_path.as_path());
136 }
137
138 // Try to initialize and cache the result
139 let path = try_exe_dir_init()?;
140 let cached_path = EXE_DIR.get_or_init(|| path);
141 Ok(cached_path.as_path())
142}