Moka Cache Value Types Supported: Trait Bounds and Generic Constraints Explained
Moka caches support any value type V that implements Clone + Send + Sync + 'static, enabling thread-safe storage of everything from primitive strings to complex custom structs.
The moka-rs/moka crate is a high-performance caching library for Rust that uses generic type parameters to accommodate diverse data structures. Whether you are building a synchronous API or an async service, understanding the exact trait requirements for cache values ensures you can store data efficiently without compilation errors.
Core Trait Requirements for Cache Values
Moka enforces strict compile-time guarantees to maintain thread safety and internal consistency. According to the source code in src/sync/builder.rs and src/future/builder.rs, every cache value must satisfy four fundamental bounds.
Required Traits for All Cache Variants
| Cache API | Required Traits for V |
Source Location |
|---|---|---|
Synchronous cache (sync::Cache) |
Clone + Send + Sync + 'static |
src/sync/builder.rs (lines 68–73) |
Asynchronous cache (future::Cache) |
Clone + Send + Sync + 'static |
src/future/builder.rs (lines 75–80) |
Entry API (Cache::entry) |
Inherits from parent cache | Entry<K, V> definition |
The 'static lifetime bound ensures values contain no non-static references, which is essential when the cache may be accessed from arbitrary threads. The Send and Sync traits guarantee safe transfer and sharing across thread boundaries, while Clone enables Moka to return copies to callers without exposing internal mutable references.
Why the Clone Trait Is Mandatory
All write operations—including insert, get_with, and entry upserts—must return a fresh V to the caller while keeping the stored entry alive inside the cache.
In src/sync/base_cache.rs and src/future/base_cache.rs, the internal implementation relies on Clone to produce these copies. Without this trait, Moka cannot safely hand data back to your application thread while maintaining its own internal copy for future cache hits. This design eliminates the need for complex lifetime management or reference counting within the cache structure itself.
Optional Traits for Advanced Features
While the four core traits enable basic caching, additional capabilities require extra bounds on your value type.
Debug for Eviction Listeners and Inspection
If you use eviction listeners or call Cache::debug, your value must implement std::fmt::Debug. The implementation in src/sync/cache.rs requires V: Debug when formatting the cache state for output.
Equality and Hash Constraints
Note that Eq and Hash are never required for values—these constraints apply only to key types (K). However, if your eviction listener or invalidation predicate (defined in src/sync/invalidator.rs) needs to compare values, you may optionally implement these traits for your own logic.
Weighted Size Calculations
When using a Weigher closure to calculate entry sizes, the closure itself must be Send + Sync + 'static. Defined in src/common/concurrent.rs as Arc<dyn Fn(&K, &V) -> u32 + Send + Sync>, the weigher receives references to keys and values but imposes no additional trait requirements on V beyond the core bounds.
Working with Custom Value Types
You can store complex structures—structs, enums, collections, or Arc<T>—as long as they satisfy the core bounds. For types holding non-static references, wrap them in Arc or Box to make them 'static.
use moka::sync::Cache;
use std::sync::Arc;
#[derive(Clone, Debug)]
struct User {
id: u64,
name: String,
permissions: Vec<String>,
}
let cache = Cache::builder()
.max_capacity(1000)
.build();
// Storing a custom struct
cache.insert(42, User {
id: 42,
name: "Alice".into(),
permissions: vec!["read".into(), "write".into()],
});
// Wrapping non-static data in Arc
let data = "temporary".to_string();
cache.insert(0, Arc::new(data));
Practical Code Examples
1. Basic Cache with Primitive Values
Primitive types and standard library collections automatically satisfy the required traits.
use moka::sync::Cache;
let cache = Cache::builder()
.max_capacity(100)
.build();
cache.insert(1, "one".to_string());
assert_eq!(cache.get(&1), Some("one".to_string()));
2. Async Cache with Custom Structs
The future::Cache variant enforces identical trait bounds but requires await points for async operations.
use moka::future::Cache;
use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
struct Session {
token: String,
expires_at: Instant,
}
let cache = Cache::builder()
.max_capacity(500)
.time_to_live(Duration::from_secs(3600))
.build();
cache.insert(123, Session {
token: "abc123".into(),
expires_at: Instant::now() + Duration::from_secs(3600),
}).await;
3. Weighted Cache Implementation
Use a weigher function when cache entries have different memory footprints.
use moka::sync::Cache;
fn byte_weigher(_k: &usize, v: &Vec<u8>) -> u32 {
v.len() as u32
}
let cache = Cache::builder()
.max_capacity(10_000) // Maximum total weight
.weigher(byte_weigher)
.build();
cache.insert(0, vec![0u8; 1024]); // 1 KB entry
4. Eviction Listener with Debug Output
Eviction listeners require Debug to print removed values.
use moka::sync::Cache;
use moka::notification::RemovalCause;
let cache = Cache::builder()
.max_capacity(2)
.eviction_listener(|k, v: &String, cause| {
println!("Evicted key={:?}, value={:?}, cause={:?}", k, v, cause);
})
.build();
cache.insert(1, "first".to_string());
cache.insert(2, "second".to_string());
cache.insert(3, "third".to_string()); // Triggers eviction of key 1 or 2
Key Source Files Reference
Understanding where Moka enforces these constraints helps when debugging trait bound errors:
| File Path | Purpose |
|---|---|
src/sync/builder.rs |
Defines CacheBuilder and the V: Clone + Send + Sync + 'static constraint for synchronous caches |
src/future/builder.rs |
Async cache builder with identical value type requirements |
src/sync/base_cache.rs |
Core synchronous cache implementation using generic V |
src/future/base_cache.rs |
Async cache core, mirroring generic bounds |
src/common/concurrent.rs |
Contains Weigher type definition and concurrency primitives |
src/sync/invalidator.rs |
Implements invalidation predicates operating on (&K, &V) |
Summary
- Any type implementing
Clone + Send + Sync + 'staticcan be stored in Moka caches, including bothsync::Cacheandfuture::Cachevariants. - The
Clonetrait is mandatory because Moka returns copies of values on read operations while retaining internal copies. Debugis only required when using eviction listeners or theCache::debuginspection method.- Non-static references must be wrapped in
ArcorBoxto satisfy the'staticlifetime requirement. - File locations
src/sync/builder.rsandsrc/future/builder.rscontain the definitive trait bounds enforced by the compiler.
Frequently Asked Questions
Can I store types with non-static lifetimes in a Moka cache?
No, but you can work around this by wrapping the data in Arc<T> or Box<T>. The 'static bound in src/sync/builder.rs requires values to own their data or use reference-counted pointers, ensuring thread safety when the cache moves data between threads.
Do synchronous and asynchronous caches have different value type requirements?
No. Both sync::Cache and future::Cache require exactly the same trait bounds: Clone + Send + Sync + 'static. The src/future/builder.rs file mirrors the constraints defined in src/sync/builder.rs, ensuring API consistency across blocking and async contexts.
Is the Debug trait required for all Moka cache values?
Only if you use specific features. The Debug bound is enforced when you configure an eviction listener or call the cache's debug formatting methods. Basic insert and get operations do not require V: Debug according to the implementation in src/sync/cache.rs.
Why does Moka require Send and Sync for values?
These traits ensure that cached data can safely move between threads and be accessed concurrently. Because Moka uses internal concurrency mechanisms (defined in src/common/concurrent.rs) to handle cache hits and evictions across multiple threads, values must be thread-safe to prevent data races.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →