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/// # Errors
95///
96/// Returns [`AppPathError`] if the executable location cannot be determined:
97/// - [`AppPathError::ExecutableNotFound`] - `std::env::current_exe()` fails (extremely rare)
98/// - [`AppPathError::InvalidExecutablePath`] - Executable path is empty (system corruption)
99///
100/// These errors represent unrecoverable system failures that occur at application startup.
101/// After the first successful call, the executable directory is cached and this function
102/// will never return an error.
103///
104/// # Performance
105///
106/// This function is highly optimized:
107/// - **First call**: Determines and caches the executable directory
108/// - **Subsequent calls**: Returns the cached result immediately (no system calls)
109/// - **Thread-safe**: Safe to call from multiple threads concurrently
110///
111/// # Examples
112///
113/// ## Library Error Handling
114///
115/// ```rust
116/// use app_path::try_exe_dir;
117///
118/// // Handle the error explicitly
119/// match try_exe_dir() {
120/// Ok(exe_dir) => {
121/// println!("Executable directory: {}", exe_dir.display());
122/// // Use exe_dir for further operations
123/// }
124/// Err(e) => {
125/// eprintln!("Failed to get executable directory: {e}");
126/// // Implement fallback strategy
127/// }
128/// }
129/// ```
130///
131/// ## Use with ? Operator
132///
133/// ```rust
134/// use app_path::try_exe_dir;
135///
136/// // Use with the ? operator in functions that return Result
137/// fn get_config_dir() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
138/// let exe_dir = try_exe_dir()?;
139/// Ok(exe_dir.join("config"))
140/// }
141/// ```
142pub fn try_exe_dir() -> Result<&'static Path, AppPathError> {
143 // If already cached, return it immediately
144 if let Some(cached_path) = EXE_DIR.get() {
145 return Ok(cached_path.as_path());
146 }
147
148 // Try to initialize and cache the result
149 let path = try_exe_dir_init()?;
150 let cached_path = EXE_DIR.get_or_init(|| path);
151 Ok(cached_path.as_path())
152}