# expire_cache: High-performance generational cache
`expire_cache` implements an efficient expiration cache using generational collection strategy. Instead of tracking individual item expiration times, it maintains two data buckets (generations), significantly reducing memory overhead and CPU usage for expiration checks.
## Features
- **High Performance**: O(1) amortized expiration overhead per item
- **Concurrent Access**: Built on DashMap for thread-safe operations
- **Async Support**: Native async initialization with `get_or_init_async`
- **Flexible Storage**: Support for both key-value maps and sets
- **Simple API**: Clean interface with `get`, `insert`, and initialization methods
## Installation
```toml
[dependencies]
expire_cache = { version = "0.1.17", features = ["dashmap", "get_or_init_async"] }
```
Available features:
- `dashmap`: Enable DashMap support
- `dashset`: Enable DashSet support
- `get_or_init`: Enable synchronous initialization
- `get_or_init_async`: Enable asynchronous initialization
## Quick Start
### Basic Usage
```rust
use expire_cache::Expire;
use dashmap::DashMap;
use std::time::Duration;
#[tokio::main]
async fn main() {
let cache: Expire<DashMap<&str, &str>> = Expire::new(60);
cache.insert("key", "value");
if let Some(val) = cache.get("key") {
println!("Found: {}", *val);
}
// Wait for expiration
tokio::time::sleep(Duration::from_secs(120)).await;
assert!(cache.get("key").is_none());
}
```
### Async Initialization
```rust
use expire_cache::Expire;
use dashmap::DashMap;
#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
let cache: Expire<DashMap<String, String>> = Expire::new(60);
let value = cache
.get_or_init_async("user_123", |key| async move {
Ok(format!("data_for_{key}"))
})
.await?;
println!("Loaded: {}", *value);
Ok(())
}
```
### Sync Initialization
```rust
use expire_cache::Expire;
use dashmap::DashMap;
fn main() -> Result<(), std::io::Error> {
let cache: Expire<DashMap<String, String>> = Expire::new(60);
let value = cache.get_or_init("user_123", |key| {
Ok(format!("data_for_{key}"))
})?;
println!("Loaded: {}", *value);
Ok(())
}
```
### Set Usage
```rust
use expire_cache::Expire;
use dashmap::DashSet;
#[tokio::main]
async fn main() {
let cache: Expire<DashSet<&str>> = Expire::new(60);
cache.insert("active_session", ());
if cache.get("active_session").is_some() {
println!("Session exists");
}
}
```
## API Reference
### `Expire<T: Map>`
- `new(expire: u64) -> Self`: Create cache with expiration period in seconds
- `get(&self, key) -> Option<RefVal>`: Retrieve value from cache
- `insert(&self, key, val)`: Insert value into cache
- `get_or_init(&self, key, func) -> Result<RefVal, E>`: Sync initialization
- `get_or_init_async(&self, key, func) -> Result<RefVal, E>`: Async initialization
## Design
### Generational Collection
The cache uses a double-buffer approach with two generations:
1. **Insertion**: New entries always go to the active generation
2. **Lookup**: Check active generation first, then passive generation
3. **Expiration**: Background task periodically clears passive generation and swaps roles
4. **Lifecycle**: Items live between `expire` and `2 * expire` seconds
This approach trades absolute precision for significant throughput improvements and reduced memory fragmentation.
## License
MulanPSL-2.0