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 (fallible).
10///
11/// **Use this only for libraries or specialized applications.** Most applications should
12/// use [`crate::AppPath::try_new()`] for simpler, cleaner code.
13///
14/// # Examples
15///
16/// ```rust
17/// use app_path::AppPath;
18///
19/// // Library with graceful error handling
20/// match AppPath::try_new() {
21/// Ok(app_base) => {
22/// println!("Application base directory: {}", app_base.display());
23/// let config = AppPath::with("config.toml");
24/// }
25/// Err(e) => {
26/// eprintln!("Failed to get application base directory: {e}");
27/// // Implement fallback strategy
28/// }
29/// }
30///
31/// // Use with ? operator for paths
32/// fn get_config_dir() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
33/// let config = AppPath::try_with("config")?;
34/// Ok(config.into())
35/// }
36/// ```
37///
38/// Once the executable directory is successfully determined by this function,
39/// the result is cached globally and all subsequent calls will use the cached value.
40/// This means that after the first successful call, `try_exe_dir()` will never return an error.
41///
42/// # Returns
43///
44/// * `Ok(&'static Path)` - The directory containing the current executable
45/// * `Err(AppPathError)` - Failed to determine executable location
46///
47/// # Errors
48///
49/// Returns [`AppPathError`] if the executable location cannot be determined:
50/// - [`AppPathError::ExecutableNotFound`] - `std::env::current_exe()` fails (extremely rare)
51/// - [`AppPathError::InvalidExecutablePath`] - Executable path is empty (system corruption)
52///
53/// These errors represent unrecoverable system failures that occur at application startup.
54/// After the first successful call, the executable directory is cached and this function
55/// will never return an error.
56///
57/// # Performance
58///
59/// This function is highly optimized:
60/// - **First call**: Determines and caches the executable directory
61/// - **Subsequent calls**: Returns the cached result immediately (no system calls)
62/// - **Thread-safe**: Safe to call from multiple threads concurrently
63///
64/// # Examples
65///
66/// ## Library Error Handling
67///
68/// ```rust
69/// use app_path::AppPath;
70///
71/// // Handle the error explicitly
72/// match AppPath::try_new() {
73/// Ok(app_base) => {
74/// println!("Application base directory: {}", app_base.display());
75/// // Use app_base for further operations
76/// }
77/// Err(e) => {
78/// eprintln!("Failed to get application base directory: {e}");
79/// // Implement fallback strategy
80/// }
81/// }
82/// ```
83///
84/// ## Use with ? Operator
85///
86/// ```rust
87/// use app_path::AppPath;
88///
89/// // Use with the ? operator in functions that return Result
90/// fn get_config_dir() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
91/// let config = AppPath::try_with("config")?;
92/// Ok(config.into())
93/// }
94/// ```
95pub fn try_exe_dir() -> Result<&'static Path, AppPathError> {
96 // If already cached, return it immediately
97 if let Some(cached_path) = EXE_DIR.get() {
98 return Ok(cached_path.as_path());
99 }
100
101 // Try to initialize and cache the result
102 let path = try_exe_dir_init()?;
103 let cached_path = EXE_DIR.get_or_init(|| path);
104 Ok(cached_path.as_path())
105}