easy_prefs
A simple, safe, and performant preferences library for Rust applications that makes storing and retrieving settings as easy as reading and writing struct fields.
This macro-based library lets you define your preferences—including default values and custom storage keys—and persist them to disk using TOML. It emphasizes data safety by using atomic writes and enforces a single-instance rule to prevent race conditions.
Now with WebAssembly support! Use the same API in browser extensions, web apps, and native applications. When compiled to WASM, preferences are stored in localStorage instead of the file system.
Created by Ever Accountable – an app dedicated to helping people overcome compulsive porn use and become their best selves. More info at everaccountable.com.
Quick Start
1. Add Dependencies
In your Cargo.toml, add:
[]
= "3.0" # Use the latest version
= { = "1.0", = ["derive"] }
(The library re-exports paste, toml, and once_cell so you don’t need to add them separately.)
2. Define Your Preferences
Create a preferences struct with default values and customizable storage keys:
use easy_prefs;
easy_prefs!
3. Load and Use Preferences
WebAssembly Support
easy_prefs works seamlessly in WebAssembly environments like Safari extensions and web applications. When compiled to WASM, it automatically uses localStorage instead of the file system.
Enabling WASM Support
Easy_prefs automatically detects WASM targets, no special features needed:
[]
= "3.0"
Building for WASM
Usage in Safari Extensions
use easy_prefs;
use *;
easy_prefs!
Storage Locations
- Native platforms: Files stored in the specified directory (directory will be created if it doesn't exist)
- WASM/Browser: Data stored in localStorage with keys prefixed by your app ID (slashes and dots in the app ID are replaced with underscores)
Detailed Information
Error Handling
The library provides two methods for loading preferences:
-
load()- Simple API that always succeeds:- In release builds: Returns defaults on errors (logs them)
- In debug/test builds: Panics on errors to catch issues early
- Always panics if another instance is already loaded
- When returning defaults due to errors, storage is still properly configured for future saves
- Use this for the simplest API where the app should continue even if preferences can't be loaded
-
load_with_error()- ReturnsResult<Self, LoadError>:- For explicit error handling when needed
- Returns
LoadErrorenum with these variants:- InstanceAlreadyLoaded: Only one instance can be loaded at a time
- DeserializationError: Errors while parsing TOML data (includes location info)
- StorageError: General storage operation failures (wraps std::io::Error)
Example:
// Simple approach - always succeeds
let prefs = load;
// Explicit error handling
match load_with_error
Use Across Threads
Use Arc<Mutex<>> to share the preferences struct between threads.
The single-instance constraint prevents loading the same preferences from multiple locations simultaneously - attempting to do so will panic (with load()) or return an error (with load_with_error()).
Atomic Writes
To ensure data integrity, writes are atomic on all platforms:
Native platforms:
- Data is first written to a temporary file
- The temporary file is atomically renamed to the final file
- This ensures the preferences file is never left in a partially written state
WASM/Browser environments:
- localStorage provides atomic writes by specification
- The
setItem()method either fully succeeds or leaves the old data untouched - If the browser crashes or runs out of storage, your existing data remains intact
Testing with load_testing()
For unit tests, use load_testing(), which:
- Creates a temporary file (cleaned up after the test).
- Bypasses the single-instance constraint, making testing simpler.
Migration from Version 2.x
Breaking Changes in Version 3.0:
load_default()has been removed. It bypassed the single-instance constraint which could lead to data corruption.load()now always succeeds instead of returning aResult. In release builds it returns defaults on errors, in debug builds it panics to catch issues early.- For explicit error handling, use the new
load_with_error()method which returnsResult<Self, LoadError>.
Migration Guide:
// Old (v2.x):
let prefs = load.unwrap_or_else;
// New (v3.0) - Simple approach:
let prefs = load; // Always succeeds
// New (v3.0) - With explicit error handling:
let prefs = match load_with_error ;
Edit Guards and Debug Checks
When batching updates with an edit guard:
- A warning (active only in debug builds) ensures the guard isn’t held for more than 1 second to prevent blocking.
- This safety check helps catch long-held locks during development.
Utility Methods
-
get_preferences_file_path():
Returns the full path of the preferences file as a string, useful for debugging. -
load():
Loads preferences, always succeeding by using defaults if needed. Panics in debug mode on errors to catch issues early. -
load_with_error():
Loads preferences with explicit error handling, returningResult<Self, LoadError>. -
load_testing():
Creates a temporary instance for unit testing, bypassing the single-instance constraint.
Customizable Storage Keys
The macro’s syntax (=> "field_name") lets you define a stored key that differs from the struct field name. This is helpful when renaming fields or preserving legacy data formats.
Dependencies & Serialization
The macro requires Serde for serialization/deserialization and re-exports helpful crates like paste, toml, once_cell, and web_time to manage lazy statics, code generation, and cross-platform time handling.
Limitations
- Not for Large Data:
All data is kept in memory and the entire file is rewritten on every save. Use a full database if you need to handle large datasets. - Blocking Writes:
File writes happen on the calling thread, so be mindful of performance in critical sections.
License
MIT OR Apache-2.0
Copyright (c) 2023 Ever Accountable
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.