Elemental Shared Library
A lightweight, extensible Rust library for managing application profiles and persistent configurations with built-in versioning and migration support.
Features
✨ Key Capabilities
- Profile Management: Load, modify, and persist user configurations
- Version Control: Track and migrate data across schema versions
- Flexible Persistence: JSON, TOML, or custom serialization formats
- Cross-Platform: Support for standard system directories (Documents, Config, Home, etc.)
- Thread-Safe: Built on
Arc<RwLock<T>>for safe concurrent access - Async/Await: Non-blocking I/O operations throughout
- Zero Configuration: Smart defaults for common scenarios
Installation
Add to your Cargo.toml:
[]
= { = "crates/shared" }
Optional Features
[]
= { = "crates/shared", = ["json", "toml"] }
json(default): JSON serialization supporttoml: TOML serialization support
Quick Start
Basic Profile Loading
use ;
use ;
async
Core Concepts
1. Profiles
A Profile<C> contains:
name: Human-readable profile identifierconfig: Your custom configuration type (genericC)version: Schema version for migrations
2. Loaders
Loader<M, V, P> provides thread-safe access to your profile:
// Immutable read
let value = loader.get.await;
// Mutable modification (auto-saves)
loader.set.await?;
// Clone entire profile
let profile_copy = loader.cloned.await;
3. Storage Scopes
Choose where files are saved:
use Scope;
// Standard locations
Document // User's Documents folder
Home // User's home directory (~)
Config // System config directory (~/.config)
ConfigLocal // Local config directory
Dot // Current working directory
// Custom path
Custom
Files are always stored in a .elemental/ subdirectory:
~/.config/.elemental/my_app.json
~/Documents/.elemental/game_save.json
./.elemental/config.toml
4. Serialization Formats
JSON (default)
use json_persistor;
let persistor = json_persistor;
TOML (requires toml feature)
use toml_persistor;
let persistor = toml_persistor;
Custom Format
For custom serialization formats, create a helper function:
use ;
use Serialize;
use DeserializeOwned;
use Result;
// Usage
let persistor = ;
Advanced Usage
Version Migration
Implement custom migration logic to handle schema evolution:
use ;
use Result;
;
// Usage
let loader = load.await?;
No Persistence (Testing)
Use NoPersistor for testing without file I/O:
use NoPersistor;
// Data loads with default values and saves are discarded
let loader = load.await?;
Direct Loader Usage
For more control, use Loader directly:
use Loader;
let loader = load.await?;
// Access the raw inner value
let value = loader.cloned.await;
API Reference
Profile
Loader
Persistors
// JSON (requires 'json' feature)
..>;
// TOML (requires 'toml' feature)
..>;
// No-op persistor (testing)
;
Scope
Traits
Examples
Example: Game Save System
use ;
use ;
async
Example: Application Settings
use ;
use ;
async
Performance Considerations
- Lock Contention:
RwLockallows concurrent readers; minimize time inset()closures - File I/O: Operations are async but file operations may block; consider using a dedicated runtime executor for I/O-heavy workloads
- Serialization: Large configurations may slow down save/load; consider streaming or partial updates
Common Pitfalls
❌ Long-Running Operations in Closures
// BAD: Holds write lock during network request
loader.set.await?;
✅ Proper Pattern
// GOOD: Fetch data, then update
let data = connect.await?;
loader.set.await?;
❌ Ignoring Version Mismatches
Always check that your schema version matches your data structure.
✅ Proper Pattern
// Define version as constant, use consistently
const APP_VERSION: usize = 2;
load.await?
Testing
The library includes built-in tests demonstrating:
- Loading profiles with default values
- Modifying and persisting data
- Round-trip serialization verification
Run tests with:
Contributing
When extending this library:
- Implement new Migrators: Extend the trait for custom upgrade paths
- Add Persistor implementations: Support new storage backends
- Enhance Scope options: Add standard directory conventions for new platforms
- Write comprehensive tests: Use
#[tokio::test]for async tests
License
See the main repository LICENSE file.
Related Crates
serde: Serialization frameworktokio: Async runtimedirs: Cross-platform directory resolutionanyhow: Error handling
FAQ
Q: Can I use this without async/await?
A: The API requires async, but you can use tokio::task::block_in_place() to run async code in synchronous contexts.
Q: Can I share a Loader across threads?
A: Yes! Loader<M, V, P> is Send + Sync when all generic parameters are, so you can wrap it in Arc and share freely.
Q: What happens if the file is corrupted?
A: The load() method returns an error; you can handle this by falling back to defaults.
Q: Can I have multiple profiles?
A: Yes! Create multiple Loader instances with different IDs or scopes.
Q: Is data encrypted?
A: No. Implement encryption in a custom Persistor if needed.
For detailed architectural information, see DESIGN.md