How to Clone a Moka Cache in Rust: Thread-Safe Sharing Explained
Cloning a Moka cache is a cheap, O(1) operation that creates new reference-counted handles to the same underlying data structures without copying stored entries.
The moka crate provides high-performance concurrent caching for Rust applications. Whether you are using moka::sync::Cache or moka::future::Cache, understanding how to clone moka cache correctly allows you to share the same data store across threads or async tasks without external locking.
How Cloning Works in Moka
According to the moka-rs/moka source code, both synchronous and asynchronous caches are built on thread-safe reference-counted containers. When you clone a cache, you are not duplicating the stored key-value pairs; you are simply incrementing reference counts on the internal Arc pointers.
The Internal Architecture
In src/sync/cache.rs, a Cache<K, V, S> consists of two primary Arc-wrapped components:
BaseCache<K, V, S>: Contains the concurrent hash table, eviction policies, and housekeeping data.Arc<ValueInitializer<K, V>>: Wraps the closure used for lazy value initialization.
Because these components live behind Arc pointers, cloning only requires incrementing reference counts. This design avoids adding K: Clone bounds on the cache itself while enabling cheap sharing.
The Clone Implementation
The Clone trait implementation in src/sync/cache.rs (lines 592-603) is deliberately handwritten rather than derived:
impl<K, V, S> Clone for Cache<K, V, S> {
/// Makes a clone of this shared cache.
///
/// This operation is cheap as it only creates thread‑safe reference counted
/// pointers to the shared internal data structures.
fn clone(&self) -> Self {
Self {
base: self.base.clone(),
value_initializer: Arc::clone(&self.value_initializer),
}
}
}
This implementation ensures that all clones point to the same BaseCache instance. Any mutation—such as insert or invalidate—performed through one handle is immediately visible to all other handles.
Basic Cloning Example
For the synchronous cache (moka::sync::Cache), cloning works exactly like any standard Rust clone:
use moka::sync::Cache;
fn main() {
// Create a cache holding up to 10,000 entries
let cache = Cache::new(10_000);
// Cheap O(1) clone
let cache2 = cache.clone();
// Both handles share the same data
cache.insert(1, "one".to_string());
assert_eq!(cache2.get(&1), Some("one".to_string()));
}
Because the clone shares the same underlying storage, you can read values through one handle that were inserted through another without synchronization overhead.
Sharing Across Multiple Threads
The primary use case for cloning is zero-cost sharing across threads. Since Moka handles concurrency internally, you never need a Mutex or RwLock around the cache:
use moka::sync::Cache;
use std::thread;
fn main() {
let cache = Cache::new(5_000);
// Spawn threads with cloned handles
let handles: Vec<_> = (0..4)
.map(|i| {
let c = cache.clone(); // Cheap clone
thread::spawn(move || {
c.insert(i, i * 10);
// Reads see all threads' inserts
assert_eq!(c.get(&i), Some(i * 10));
})
})
.collect();
for h in handles {
h.join().expect("thread panicked");
}
// Original cache sees all inserts
for i in 0..4 {
assert_eq!(cache.get(&i), Some(i * 10));
}
}
This pattern applies to both the standard cache and the SegmentedCache variant found in src/sync/segment.rs.
Cloning the Async Cache
The asynchronous moka::future::Cache (defined in src/future/cache.rs) follows the same cloning semantics. It is safe to clone across async boundaries and tasks:
use moka::future::Cache;
use tokio::runtime::Runtime;
fn main() {
let rt = Runtime::new().unwrap();
rt.block_on(async {
let cache = Cache::builder()
.max_capacity(1_000)
.weigher(|_k: &u64, v: &String| v.len() as u32)
.build();
// Clone the async cache
let cache2 = cache.clone();
cache.insert(42, "answer".to_string()).await;
assert_eq!(cache2.get(&42).await, Some("answer".to_string()));
});
}
The async implementation mirrors the sync API, using identical Arc-based cloning to maintain O(1) performance characteristics.
Summary
- Cheap operation: Cloning a Moka cache only increments Arc reference counts in
src/sync/cache.rs, making it an O(1) operation regardless of cache size. - Shared state: All clones reference the same
BaseCacheinstance, so changes are visible across all handles immediately. - Thread safety: The design requires no external locking—Moka's internal concurrent hash table handles synchronization.
- Universal pattern: Both
moka::sync::Cacheandmoka::future::Cache(andSegmentedCache) implement identical cloning behavior through Arc pointers. - No trait bounds: The manual
Cloneimplementation avoids requiringK: Cloneon your key types.
Frequently Asked Questions
Does cloning a Moka cache copy all the stored entries?
No. Cloning creates new handles to the same underlying data structures via Arc pointers. The stored entries remain in the shared BaseCache instance defined in src/sync/base_cache.rs, so no key-value pairs are duplicated in memory.
Is it safe to clone a Moka cache and send it to another thread?
Yes. moka::sync::Cache implements both Clone and Send, allowing you to safely move clones across thread boundaries. The cache handles all internal synchronization, so you do not need to wrap it in Mutex or Arc yourself.
Do cache configuration settings persist after cloning?
Yes. Since configuration (capacity, eviction policy, weigher) is stored inside the shared BaseCache, all clones inherit the same settings. Modifying the configuration through one handle affects the behavior of all other handles.
Can I clone the async cache (moka::future::Cache) the same way?
Yes. The async cache in src/future/cache.rs implements Clone identically to the synchronous version. You can safely clone it before spawning async tasks or moving it between runtimes.
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 →