5 releases
new 0.4.0 | Mar 1, 2025 |
---|---|
0.3.3 | Feb 25, 2025 |
0.3.2 | Feb 25, 2025 |
0.3.1 | Feb 25, 2025 |
0.3.0 | Feb 25, 2025 |
#421 in Rust patterns
306 downloads per month
55KB
441 lines
sovran-typemap
A thread-safe, type-safe heterogeneous container library for Rust.
sovran-typemap
provides a flexible way to store different types in a single container while maintaining type-safety through runtime checks. This is particularly useful for applications that need to share state between components without requiring all components to know about all types.
Key Features
- Type-safe: Values are checked at runtime to ensure type correctness
- Thread-safe: Built on
Arc<Mutex<_>>
for safe concurrent access - Ergonomic API: Simple methods with closures for storing, retrieving, and modifying values
- Flexible: Supports any type that implements
Any + Send + Sync
with any hashable key type - Comprehensive Error Handling: Detailed error types for better debugging and recovery
- No macros: Pure runtime solution without complex macro magic
- No Unsafe Code: Relies entirely on safe Rust with no
unsafe
blocks
Installation
Add this to your Cargo.toml
:
[dependencies]
sovran-typemap = "0.3"
Basic Usage
use sovran_typemap::{TypeStore, StoreError};
fn main() -> Result<(), StoreError> {
// Create a new store with string keys
let store = TypeStore::<String>::new();
// Store values of different types
store.set("number".to_string(), 42i32)?;
store.set("text".to_string(), "Hello, world!".to_string())?;
store.set("data".to_string(), vec![1, 2, 3, 4, 5])?;
// Retrieve values in a type-safe way
let num = store.get::<i32>(&"number".to_string())?;
let text = store.get::<String>(&"text".to_string())?;
println!("Number: {}", num);
println!("Text: {}", text);
// Handle errors properly
match store.get::<bool>(&"nonexistent".to_string()) {
Ok(value) => println!("Value: {}", value),
Err(StoreError::KeyNotFound) => println!("Key doesn't exist"),
Err(StoreError::TypeMismatch) => println!("Type doesn't match"),
Err(e) => println!("Other error: {}", e),
}
Ok(())
}
Using with_mut to Modify Values In-Place
use sovran_typemap::{TypeStore, StoreError};
use std::collections::HashMap;
fn main() -> Result<(), StoreError> {
let store = TypeStore::<String>::new();
// Initialize a counter map
let mut counters = HashMap::new();
counters.insert("visits".to_string(), 0);
store.set("counters".to_string(), counters)?;
// Update a counter in-place
store.with_mut(&"counters".to_string(), |counters: &mut HashMap<String, i32>| {
let visits = counters.entry("visits".to_string()).or_insert(0);
*visits += 1;
})?;
// Add a new counter
store.with_mut(&"counters".to_string(), |counters: &mut HashMap<String, i32>| {
counters.insert("api_calls".to_string(), 1);
})?;
// Read current values
let visit_count = store.with(&"counters".to_string(), |counters: &HashMap<String, i32>| {
counters.get("visits").copied().unwrap_or(0)
})?;
println!("Visit count: {}", visit_count);
Ok(())
}
Sharing State Between Components
use sovran_typemap::{TypeStore, StoreError};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
struct UserService {
store: Arc<TypeStore<String>>,
}
struct LogService {
store: Arc<TypeStore<String>>,
}
impl UserService {
fn new(store: Arc<TypeStore<String>>) -> Self {
Self { store }
}
fn get_user_count(&self) -> Result<usize, StoreError> {
self.store.with(&"users".to_string(), |users: &Vec<String>| {
users.len()
})
}
fn add_user(&self, username: String) -> Result<(), StoreError> {
// Initialize users vector if it doesn't exist yet
if !self.store.contains_key(&"users".to_string())? {
self.store.set("users".to_string(), Vec::<String>::new())?;
}
// Add a user
self.store.with_mut(&"users".to_string(), |users: &mut Vec<String>| {
users.push(username);
})
}
}
impl LogService {
fn new(store: Arc<TypeStore<String>>) -> Self {
Self { store }
}
fn log(&self, message: String) -> Result<(), StoreError> {
// Initialize logs if they don't exist
if !self.store.contains_key(&"logs".to_string())? {
self.store.set("logs".to_string(), Vec::<String>::new())?;
}
// Add log entry with timestamp
self.store.with_mut(&"logs".to_string(), |logs: &mut Vec<String>| {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
logs.push(format!("[{}] {}", now, message));
})
}
fn get_recent_logs(&self, count: usize) -> Result<Vec<String>, StoreError> {
self.store.with(&"logs".to_string(), |logs: &Vec<String>| {
logs.iter()
.rev()
.take(count)
.cloned()
.collect()
})
}
}
fn main() -> Result<(), StoreError> {
// Create a shared store
let store = Arc::new(TypeStore::<String>::new());
// Create services that share the store
let user_service = UserService::new(Arc::clone(&store));
let log_service = LogService::new(Arc::clone(&store));
// Use the services
user_service.add_user("alice".to_string())?;
log_service.log("User alice added".to_string())?;
user_service.add_user("bob".to_string())?;
log_service.log("User bob added".to_string())?;
// Get information from both services
println!("User count: {}", user_service.get_user_count()?);
println!("Recent logs:");
for log in log_service.get_recent_logs(5)? {
println!(" {}", log);
}
Ok(())
}
Error Handling
The library provides detailed error types to help with error handling:
use sovran_typemap::{TypeStore, StoreError};
fn main() {
let store = TypeStore::<String>::new();
// Set a value for demonstration
if let Err(e) = store.set("config".to_string(), vec!["setting1", "setting2"]) {
eprintln!("Failed to store config: {}", e);
return;
}
// Try to get a value with the wrong type
match store.get::<String>(&"config".to_string()) {
Ok(value) => println!("Config: {}", value),
Err(StoreError::KeyNotFound) => println!("Config key not found"),
Err(StoreError::TypeMismatch) => println!("Config is not a String"),
Err(StoreError::LockError) => println!("Failed to acquire lock"),
}
// Try to access a non-existent key
match store.get::<i32>(&"settings".to_string()) {
Ok(value) => println!("Setting: {}", value),
Err(StoreError::KeyNotFound) => println!("Settings key not found"),
Err(e) => println!("Other error: {}", e),
}
}
Available Methods
new()
- Create a new empty TypeStoreset(key, value)
- Store a value of any typeset_with(key, closure)
- Store a value generated by a closureget<T>(key)
- Get a clone of a valuewith<T, F, R>(key, closure)
- Access a value with a read-only closurewith_mut<T, F, R>(key, closure)
- Access a value with a read-write closureremove(key)
- Remove a valuecontains_key(key)
- Check if a key existskeys()
- Get all keyslen()
- Get the number of itemsis_empty()
- Check if the store is empty
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contribution
Contributions are welcome! Please feel free to submit a Pull Request.
Dependencies
~1MB
~23K SLoC