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 BaseCache instance, 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::Cache and moka::future::Cache (and SegmentedCache) implement identical cloning behavior through Arc pointers.
  • No trait bounds: The manual Clone implementation avoids requiring K: Clone on 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →