OS Standard Directories
Recommended: Use the
dirscrate directly withPathBoundary::try_new()
This page shows how to integrate strict-path with the dirs crate for cross-platform OS standard directories.
Quick Start
[dependencies]
strict-path = "0.1"
dirs = "5.0" # Add dirs directly
#![allow(unused)] fn main() { use strict_path::PathBoundary; // Platform-specific config directory let config_base = dirs::config_dir() .ok_or("No config directory")?; // Create app-specific boundary let app_config = config_base.join("myapp"); let boundary = PathBoundary::try_new_create(&app_config)?; // Safe operations within boundary let settings = boundary.strict_join("settings.toml")?; settings.write(b"[app]\nversion = '1.0'\n")?; }
Cross-Platform Standards
The dirs crate follows established cross-platform directory standards:
Linux (XDG Base Directory Specification)
- Config:
$XDG_CONFIG_HOMEor~/.config - Data:
$XDG_DATA_HOMEor~/.local/share - Cache:
$XDG_CACHE_HOMEor~/.cache - Runtime:
$XDG_RUNTIME_DIRor/tmp
Windows (Known Folder API)
- Config:
%APPDATA%(Roaming) - Data:
%APPDATA%(Roaming) - Cache:
%LOCALAPPDATA% - Local Config:
%LOCALAPPDATA% - Local Data:
%LOCALAPPDATA%
macOS (Apple Standard Directories)
- Config:
~/Library/Application Support - Data:
~/Library/Application Support - Cache:
~/Library/Caches
Integration Patterns
Application Configuration
#![allow(unused)] fn main() { use strict_path::PathBoundary; struct AppConfig; fn setup_config() -> Result<(), Box<dyn std::error::Error>> { let config_base = dirs::config_dir() .ok_or("No config directory available")?; let app_config = config_base.join("myapp"); let boundary: PathBoundary<AppConfig> = PathBoundary::try_new_create(&app_config)?; // Platform-specific locations: // Linux: ~/.config/myapp/ // Windows: C:\Users\Alice\AppData\Roaming\myapp\ // macOS: ~/Library/Application Support/myapp/ let settings = boundary.strict_join("settings.toml")?; settings.write(b"[app]\ntheme = 'dark'\n")?; println!("Config saved to: {}", settings.strictpath_display()); Ok(()) } }
Multi-Directory Application
#![allow(unused)] fn main() { use strict_path::PathBoundary; struct Config; struct Data; struct Cache; struct AppDirectories { config: PathBoundary<Config>, data: PathBoundary<Data>, cache: PathBoundary<Cache>, } impl AppDirectories { fn new(app_name: &str) -> Result<Self, Box<dyn std::error::Error>> { let config_base = dirs::config_dir() .ok_or("No config directory")?; let data_base = dirs::data_dir() .ok_or("No data directory")?; let cache_base = dirs::cache_dir() .ok_or("No cache directory")?; Ok(Self { config: PathBoundary::try_new_create(config_base.join(app_name))?, data: PathBoundary::try_new_create(data_base.join(app_name))?, cache: PathBoundary::try_new_create(cache_base.join(app_name))?, }) } } fn use_app_directories() -> Result<(), Box<dyn std::error::Error>> { let dirs = AppDirectories::new("MyApp")?; // Config: user preferences let prefs = dirs.config.strict_join("preferences.json")?; prefs.write(br#"{"theme": "dark"}"#)?; // Data: persistent application data let database = dirs.data.strict_join("app.db")?; database.write(b"database content")?; // Cache: temporary/regenerable data let thumbnail = dirs.cache.strict_join("thumbs/image1.jpg")?; thumbnail.create_parent_dir_all()?; thumbnail.write(b"thumbnail data")?; Ok(()) } }
User Content Directories
#![allow(unused)] fn main() { use strict_path::PathBoundary; struct Downloads; struct Documents; fn access_user_content() -> Result<(), Box<dyn std::error::Error>> { // Downloads directory if let Some(downloads) = dirs::download_dir() { let boundary: PathBoundary<Downloads> = PathBoundary::try_new(&downloads)?; // Safe access to user-selected file let user_input = "report.pdf"; // From file picker or CLI let file = boundary.strict_join(user_input)?; if file.exists() { let data = file.read()?; println!("Processing: {} bytes", data.len()); } } // Documents directory if let Some(documents) = dirs::document_dir() { let boundary: PathBoundary<Documents> = PathBoundary::try_new(&documents)?; let export = boundary.strict_join("exports/data.csv")?; export.create_parent_dir_all()?; export.write(b"col1,col2\nval1,val2\n")?; println!("Exported to: {}", export.strictpath_display()); } Ok(()) } }
Available Directories (via dirs crate)
| Function | Linux | Windows | macOS |
|---|---|---|---|
config_dir() | ~/.config | %APPDATA% | ~/Library/Application Support |
data_dir() | ~/.local/share | %APPDATA% | ~/Library/Application Support |
cache_dir() | ~/.cache | %LOCALAPPDATA% | ~/Library/Caches |
config_local_dir() | ~/.config | %LOCALAPPDATA% | ~/Library/Application Support |
data_local_dir() | ~/.local/share | %LOCALAPPDATA% | ~/Library/Application Support |
download_dir() | ~/Downloads | %USERPROFILE%\Downloads | ~/Downloads |
document_dir() | ~/Documents | %USERPROFILE%\Documents | ~/Documents |
picture_dir() | ~/Pictures | %USERPROFILE%\Pictures | ~/Pictures |
video_dir() | ~/Videos | %USERPROFILE%\Videos | ~/Movies |
audio_dir() | ~/Music | %USERPROFILE%\Music | ~/Music |
Complete Example: Cross-Platform App
use strict_path::PathBoundary; use serde::{Deserialize, Serialize}; struct AppConfig; struct AppData; struct AppCache; #[derive(Serialize, Deserialize)] struct UserPreferences { theme: String, language: String, } struct MyApp { config_dir: PathBoundary<AppConfig>, data_dir: PathBoundary<AppData>, cache_dir: PathBoundary<AppCache>, } impl MyApp { fn new(app_name: &str) -> Result<Self, Box<dyn std::error::Error>> { // Get OS-specific directories let config_base = dirs::config_dir() .ok_or("No config directory")?; let data_base = dirs::data_dir() .ok_or("No data directory")?; let cache_base = dirs::cache_dir() .ok_or("No cache directory")?; // Create app-specific boundaries Ok(Self { config_dir: PathBoundary::try_new_create( config_base.join(app_name) )?, data_dir: PathBoundary::try_new_create( data_base.join(app_name) )?, cache_dir: PathBoundary::try_new_create( cache_base.join(app_name) )?, }) } fn save_preferences(&self, prefs: &UserPreferences) -> Result<(), Box<dyn std::error::Error>> { let prefs_file = self.config_dir.strict_join("preferences.json")?; let json = serde_json::to_string_pretty(prefs)?; prefs_file.write(json.as_bytes())?; Ok(()) } fn load_preferences(&self) -> Result<UserPreferences, Box<dyn std::error::Error>> { let prefs_file = self.config_dir.strict_join("preferences.json")?; let json = prefs_file.read_to_string()?; Ok(serde_json::from_str(&json)?) } fn save_data(&self, filename: &str, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> { let file = self.data_dir.strict_join(filename)?; file.create_parent_dir_all()?; file.write(data)?; Ok(()) } fn cache_thumbnail(&self, id: &str, thumbnail: &[u8]) -> Result<(), Box<dyn std::error::Error>> { let cache_file = self.cache_dir .strict_join(&format!("thumbnails/{}.jpg", id))?; cache_file.create_parent_dir_all()?; cache_file.write(thumbnail)?; Ok(()) } } fn main() -> Result<(), Box<dyn std::error::Error>> { let app = MyApp::new("MyAwesomeApp")?; // Save user preferences let prefs = UserPreferences { theme: "dark".to_string(), language: "en".to_string(), }; app.save_preferences(&prefs)?; // Save application data app.save_data("database/users.db", b"user data")?; // Cache a thumbnail app.cache_thumbnail("image123", b"thumbnail bytes")?; // Load preferences back let loaded_prefs = app.load_preferences()?; println!("Theme: {}", loaded_prefs.theme); Ok(()) }
Why Direct Integration?
Benefits of using dirs directly:
- ✅ Full control - Access all
dirsfunctions and options - ✅ No version coupling - Use any version of
dirsyou want - ✅ Explicit dependencies - Clear what your project uses
- ✅ Reduced bloat - No unnecessary feature flags
- ✅ One extra line - Small price for explicit security
Pattern:
#![allow(unused)] fn main() { // Get directory from dirs crate let base_dir = dirs::config_dir().ok_or("No config")?; // Create boundary with strict-path let boundary = PathBoundary::try_new_create(base_dir.join("myapp"))?; // Use safe operations let file = boundary.strict_join(user_input)?; }
See Also
- Ecosystem Integration Guide - Comprehensive integration patterns
- Tutorial: Stage 6 - Ecosystem integration tutorial
- Best Practices - Application architecture patterns
Legacy Feature Flag (Deprecated)
⚠️ Deprecated: The
dirsfeature flag is deprecated in favor of direct integration.
The previous feature-based approach:
strict-path = { version = "0.1", features = ["dirs"] }
Provided methods like PathBoundary::try_new_os_config(), but this couples strict-path to the dirs crate version and hides the integration.
Migration is trivial:
#![allow(unused)] fn main() { // OLD (with dirs feature): let boundary = PathBoundary::try_new_os_config("myapp")?; // NEW (direct integration): let config_base = dirs::config_dir().ok_or("No config")?; let boundary = PathBoundary::try_new_create(config_base.join("myapp"))?; }
The new approach gives you full control and makes the integration explicit.