Native Integration
Filesystem access, notifications, and OS APIs from Rust desktop apps - portable patterns and platform caveats.
Recipe
fn app_data_dir() -> std::io::Result<std::path::PathBuf> {
let base = dirs::data_local_dir().ok_or(std::io::ErrorKind::NotFound)?;
Ok(base.join("com.example.myapp"))
}When to reach for this:
- Open/save dialogs and drag-drop paths
- System notifications for long tasks
- Autostart, deep links, and custom URL schemes
- Secure credential storage in OS keychain
Working Example
fn ensure_data_dir() -> std::io::Result<std::path::PathBuf> {
let dir = app_data_dir()?;
std::fs::create_dir_all(&dir)?;
Ok(dir)
}
fn app_data_dir() -> std::io::Result<std::path::PathBuf> {
let base = dirs::data_local_dir().ok_or(std::io::ErrorKind::NotFound)?;
Ok(base.join("com.example.myapp"))
}
fn pick_config_file() -> Option<std::path::PathBuf> {
rfd::FileDialog::new()
.add_filter("toml", &["toml"])
.pick_file()
}What this demonstrates:
- Platform-appropriate data directory via
dirs - Create-on-first-run directory setup
- Filtered file picker for config import
Deep Dive
How It Works
- Tauri exposes scoped FS APIs via permissions in config.
- rfd blocks briefly for modal dialogs - call from UI thread.
- notify-rust / Tauri notification plugin posts OS toasts.
- keyring crate abstracts macOS Keychain, Windows Credential Manager, Secret Service.
Capability Examples
| Need | Crate / API |
|---|---|
| App data path | dirs, Tauri paths |
| File dialog | rfd, Tauri dialog plugin |
| Notifications | tauri-plugin-notification |
| Secrets | keyring |
Gotchas
- Hard-coded
/Users/...paths - breaks Linux/Windows. Fix: alwaysdirsor Tauri resolver. - Following symlinks out of sandbox - security escape. Fix: canonicalize and verify prefix under grant root.
- Dialog off UI thread on macOS - undefined behavior warnings. Fix: main thread only for modal dialogs.
- Notification permission not requested (macOS) - silent failure. Fix: prompt once with explanation.
- Plaintext API keys in config.toml - leaked in backups. Fix: keychain entry per user machine.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| CLI only | Server tools | Desktop UX required |
Electron @electron/remote | Legacy app | Tauri/Rust security model |
| Platform-specific FFI | Missing abstraction | Portable app priority |
| Web app only | No native APIs | Need filesystem offline |
FAQs
Tauri FS scopes?
Declare allowed directories in capabilities; use app_data_dir for default writable root.
Drag and drop?
Tauri webview events or winit drop handlers in raw egui/iced - validate paths before read.
Shell execute?
Open URLs and folders with open crate or Tauri shell plugin - never pass unsanitized user strings to shell.
Single instance app?
tauri-plugin-single-instance or file lock on startup for Windows tray apps.
Dark mode detection?
dark-light crate or Tauri theme events to sync UI with OS setting.
Auto-start login item?
Platform-specific crates or installer scripts - document privacy impact to users.
Linux portals?
Flatpak/snap use XDG portals for file access - test packaged builds not only cargo run.
Windows ACLs?
Writing to Program Files fails - use %LOCALAPPDATA% via dirs.
Testing without GUI?
Inject trait FileSystem mock; integration tests use tempfile directories.
Packaging ties?
Installers register MIME handlers - see Packaging & Distribution.
Related
- Tauri - permissions model
- State & Architecture - where paths config lives
- Packaging & Distribution - signed bundles
- GUI Basics - file dialog example
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.