# How to Use Moka Sync Cache in Rust: Thread-Safe Memory Caching

> Learn how to use Moka Sync Cache in Rust for high-performance, thread-safe in-memory caching. Leverage lock-free structures for safe concurrent access without mutexes.

- Repository: [moka-rs/moka](https://github.com/moka-rs/moka)
- Tags: how-to-guide
- Published: 2026-03-07

---

**The moka sync cache provides a high-performance, thread-safe in-memory cache for Rust that uses lock-free data structures to enable safe concurrent access without requiring external synchronization primitives like Mutex or RwLock.**

The **moka sync cache** is the synchronous API of the moka-rs/moka crate, a popular high-performance caching library for Rust. Unlike the asynchronous variant, the synchronous cache lives under `moka::sync` and is built on top of a lock-free hash table, making all operations safe to call from multiple threads without additional synchronization overhead. This implementation is ideal for CPU-bound workloads and traditional multi-threaded applications where you need deterministic, blocking cache operations.

## Creating a Moka Sync Cache

You can instantiate a cache using either the default constructor for quick setup or the builder API for fine-grained control over capacity, expiration, and eviction policies.

### Using Cache::new for Default Configuration

The fastest way to create a cache is with `Cache::new(max_capacity)`, which allocates a lock-free hash table with default settings. According to the implementation in [[`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs)](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs), this method constructs a `BaseCache` internally and wraps it with the public `Cache` API.

```rust
use moka::sync::Cache;

// Create a cache holding up to 10,000 entries
let cache = Cache::new(10_000);

```

### Using CacheBuilder for Custom Policies

For production workloads requiring expiration policies or custom weigher functions, use `CacheBuilder` defined in [[`src/sync/builder.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/builder.rs)](https://github.com/moka-rs/moka/blob/main/src/sync/builder.rs). The builder validates expiration limits during construction to prevent invalid configurations.

```rust
use moka::sync::Cache;
use std::time::Duration;

let cache = Cache::builder()
    .max_capacity(5_000)
    .time_to_live(Duration::from_secs(60))   // TTL: 60 seconds
    .time_to_idle(Duration::from_secs(10))   // TTI: 10 seconds
    .build();

```

## Core Cache Operations

The `Cache` struct in [`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs) provides standard operations for inserting, retrieving, and removing entries. All methods are internally synchronized using atomic operations, so you **must not** wrap the cache in a `Mutex` or `RwLock`.

### Inserting and Retrieving Entries

Use `insert` to store key-value pairs and `get` to retrieve them. The `get` method returns `Option<V>` and performs O(1) lookups on the lock-free hash table.

```rust
// Insert a value
cache.insert("key", "value");

// Retrieve a value
if let Some(value) = cache.get(&"key") {
    println!("Found: {}", value);
}

```

### Removing Entries with invalidate

To remove a specific entry, use `invalidate`, which immediately removes the key from the internal hash table and schedules any associated eviction listener callbacks.

```rust
cache.invalidate(&"key");

```

## Lazy Initialization with get_with

The moka sync cache prevents the **thundering herd** problem through the `get_with` family of methods. As implemented in [[`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs)](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs) (lines 46-52), these methods guarantee that concurrent calls for the same missing key coalesce into a single evaluation of the closure, even under high contention.

```rust
use moka::sync::Cache;
use std::sync::Arc;

let cache: Cache<usize, Arc<Vec<u8>>> = Cache::new(100);

// Expensive computation runs only once per key
let data = cache.get_with(1, || {
    println!("Computing expensive value...");
    Arc::new(vec![0u8; 10 * 1024 * 1024]) // 10 MiB
});

// Subsequent calls return the cached Arc without re-running the closure
let data2 = cache.get_with(1, || unreachable!());
assert!(Arc::ptr_eq(&data, &data2));

```

For fallible operations, use `try_get_with` for `Result` types or `optionally_get_with` for `Option` types.

## Configuring Expiration and Eviction

The cache supports automatic expiration through the `Housekeeper` background task (defined in [[`src/common/housekeeper.rs`](https://github.com/moka-rs/moka/blob/main/src/common/housekeeper.rs)](https://github.com/moka-rs/moka/blob/main/src/common/housekeeper.rs)) which periodically scans for expired entries using a timer wheel.

### Setting Time-to-Live and Time-to-Idle

Configure expiration policies via the builder:

- **Time-to-Live (TTL)**: Maximum duration an entry remains in the cache regardless of access.
- **Time-to-Idle (TTI)**: Duration after which an entry expires if not accessed.

```rust
use moka::sync::Cache;
use std::time::Duration;

let cache = Cache::builder()
    .time_to_live(Duration::from_secs(3600))  // Entries live 1 hour max
    .time_to_idle(Duration::from_secs(300))   // Expire if idle 5 minutes
    .build();

```

### Adding an Eviction Listener

Register a closure to be called when entries are evicted or expired. The listener receives the key, value, and eviction cause.

```rust
let listener = |key: &u32, value: &String, cause| {
    println!("Evicted {} => {} because {:?}", key, value, cause);
};

let cache = Cache::builder()
    .max_capacity(100)
    .eviction_listener(listener)
    .build();

```

## Thread Safety and Housekeeping

The moka sync cache is designed for extreme concurrency using a lock-free core in [[`src/sync/base_cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/base_cache.rs)](https://github.com/moka-rs/moka/blob/main/src/sync/base_cache.rs).

### Cheap Clone Operations

The `Cache` struct is `Clone`, but cloning does not duplicate the underlying data. According to [[`src/sync.rs`](https://github.com/moka-rs/moka/blob/main/src/sync.rs)](https://github.com/moka-rs/moka/blob/main/src/sync.rs) (lines 30-38), cloning creates another `Arc` pointer to the same internal tables, making the operation **O(1)** and ideal for sharing across threads.

```rust
use std::thread;

let cache = Cache::new(1_000);
let handles: Vec<_> = (0..4).map(|i| {
    let thread_cache = cache.clone(); // Cheap O(1) clone
    thread::spawn(move || {
        thread_cache.insert(i, format!("thread-{}", i));
    })
}).collect();

for h in handles { h.join().unwrap(); }

```

### Forcing Immediate Cleanup with sync()

By default, expired entry removal and eviction listener callbacks run in a background thread. For deterministic state in tests or shutdown sequences, call `sync()` from the `ConcurrentCacheExt` trait (exposed in [[`src/sync.rs`](https://github.com/moka-rs/moka/blob/main/src/sync.rs)](https://github.com/moka-rs/moka/blob/main/src/sync.rs), lines 30-34) to force pending maintenance tasks to run synchronously on the current thread.

```rust
use moka::sync::Cache;
use std::time::Duration;

let cache = Cache::builder()
    .max_capacity(2)
    .time_to_live(Duration::from_secs(1))
    .build();

cache.insert(1, "a");
std::thread::sleep(Duration::from_secs(2));

cache.sync(); // Forces expiration cleanup immediately
assert_eq!(cache.entry_count(), 0);

```

Alternatively, use `cache.run_pending_tasks()` for the same effect.

## Summary

- **The moka sync cache** provides lock-free, thread-safe caching under `moka::sync` without requiring external locks.
- **Clone freely**: Sharing across threads costs O(1) because `Cache` uses `Arc` internally.
- **Use `get_with`** to prevent duplicate expensive computations during concurrent cache misses.
- **Configure expiration** via `CacheBuilder` with TTL and TTI policies enforced by a background `Housekeeper`.
- **Force synchronization** using `sync()` or `run_pending_tasks()` when you need deterministic state for testing or shutdown.

## Frequently Asked Questions

### Is the moka sync cache thread-safe?

Yes. All operations are internally synchronized via atomic operations on a lock-free hash table implemented in [`src/sync/base_cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/base_cache.rs). You must not wrap a `Cache` in `Mutex` or `RwLock`; doing so would hurt performance without adding safety.

### When should I use sync() versus run_pending_tasks()?

Both methods force the background housekeeper to run immediately. According to [`src/sync.rs`](https://github.com/moka-rs/moka/blob/main/src/sync.rs), `sync()` is the trait method from `ConcurrentCacheExt` while `run_pending_tasks()` is the concrete implementation. They are functionally equivalent; use either when you need expired entries removed deterministically, such as in unit tests or before checking `entry_count()`.

### How does get_with prevent duplicate computations?

The `get_with` method uses a `ValueInitializer` (internal to [`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs)) to ensure that when multiple threads simultaneously request a missing key, only one thread executes the initialization closure while others block until the value is ready. This prevents cache stampedes or "thundering herd" problems on expensive operations.

### What is the difference between moka sync and async caches?

The **sync** cache (`moka::sync`) uses blocking operations and is ideal for synchronous, multi-threaded applications. The **async** cache (`moka::future`) provides the same API but returns `Future` types, making it suitable for async/await contexts like Tokio runtimes. Both share the same lock-free core but differ in their blocking behavior and thread pool integration.