# When to Use Sync vs Async Moka Cache: Complete API Guide

> Discover when to use sync vs async Moka cache for your Rust applications. Learn how to leverage blocking or async runtimes with identical performance guarantees.

- Repository: [moka-rs/moka](https://github.com/moka-rs/moka)
- Tags: api-reference
- Published: 2026-03-07

---

**Use the `sync` cache for blocking, multi-threaded code and the `future` cache for async runtimes like Tokio; both share identical eviction and expiration internals but expose different APIs to match your concurrency model.**

Choosing between the synchronous and asynchronous variants in the **moka-rs/moka** crate depends entirely on your application's concurrency architecture. While both provide the same high-performance caching capabilities, they differ in API design and runtime requirements. This guide explains the architectural differences, decision criteria, and implementation details based on the actual source code.

## Core Differences Between Sync and Async Moka Cache

The Moka crate provides two families of caches that wrap the same core data structures but expose fundamentally different interfaces.

### API Design and Runtime Requirements

The **synchronous (`sync`)** cache provides blocking methods that return values directly. According to the source code in [`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs) (line 571), the `Cache<K,V>` struct exposes methods like `fn get(&self, key) -> Option<V>` that block the current thread until the operation completes. This variant requires no async runtime and works in plain multi-threaded Rust code.

The **asynchronous (`future`)** cache returns `Future`s for every operation. Defined in [`src/future/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/future/cache.rs) (line 633), this variant exposes `async fn get(&self, key) -> Option<V>`, allowing operations to be awaited without blocking the executor thread. This requires an async runtime (Tokio, async-std) and the crate feature `future` enabled.

### Shared Internals: BaseCache Architecture

Both implementations are thin wrappers around **BaseCache**, a lock-free concurrent hash table that handles entry storage, eviction, and expiration policies. The synchronous wrapper in [`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs) forwards calls directly to `BaseCache`, while the asynchronous wrapper in [`src/future/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/future/cache.rs) adapts the same underlying storage to return awaitable futures. Because they share `src/common/*` modules for data structures, **configuration options—capacity, weigher, eviction listeners, expiration policies, and custom hashers—remain identical** regardless of which API you choose.

## When to Choose the Synchronous (`sync`) Cache

Select the `sync` cache when your application runs in a **purely synchronous context**. This includes command-line tools, background daemon threads, or `std::thread` pools where you want to avoid pulling in an async runtime as a dependency.

The `sync` variant is also ideal for **CPU-bound work** where blocking a thread is acceptable. Since the underlying operations are lock-free and fast, the blocking API provides a simpler mental model without the overhead of futures and executors. You can clone the cache cheaply (Arc-based) across threads, as shown in [`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs), and share mutable cache state without additional synchronization.

## When to Choose the Asynchronous (`future`) Cache

Use the `future` cache when your application already employs an **async runtime** such as Tokio or async-std. This is essential for async web servers (Actix-Web, Axum) or async worker processes where handlers are `async fn`s.

The async variant allows you to **await cache operations** without blocking the executor's thread, which is critical when combining cache access with other async I/O like network requests or file system operations. You can compose cache calls with other futures using combinators like `join!` or `select!`. The implementation in [`src/future/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/future/cache.rs) ensures that while the public API is async, the internal updates remain lock-free and performant.

## Code Examples

### Synchronous Cache with Thread Pools

This example demonstrates sharing a `sync::Cache` across multiple OS threads using cheap cloning:

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

fn value(n: usize) -> String {
    format!("value {n}")
}

fn main() {
    const THREADS: usize = 8;
    const KEYS_PER_THREAD: usize = 50;

    // A cache that holds up to 10,000 entries.
    let cache = Cache::new(10_000);

    // Spawn threads that share the same cache via Arc-based clone.
    let handles: Vec<_> = (0..THREADS)
        .map(|i| {
            let my_cache = cache.clone(); // cheap clone
            thread::spawn(move || {
                let start = i * KEYS_PER_THREAD;
                let end = start + KEYS_PER_THREAD;
                for k in start..end {
                    my_cache.insert(k, value(k));
                    assert_eq!(my_cache.get(&k), Some(value(k)));
                }
                for k in (start..end).step_by(5) {
                    my_cache.invalidate(&k);
                }
            })
        })
        .collect();

    for h in handles {
        h.join().expect("thread panicked");
    }

    // Verify results in main thread.
    for k in 0..(THREADS * KEYS_PER_THREAD) {
        if k % 5 == 0 {
            assert!(cache.get(&k).is_none());
        } else {
            assert!(cache.get(&k).is_some());
        }
    }
}

```

Key implementation files:
- [`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs) – Core `Cache<K,V>` struct definition (line 571)
- [`src/sync/builder.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/builder.rs) – `CacheBuilder` for configuration

### Asynchronous Cache with Tokio

This example shows the `future::Cache` with TTL expiration in an async context:

```rust
use moka::future::Cache;
use std::time::Duration;
use tokio::time;

#[tokio::main]
async fn main() {
    // Cache with 30-second TTL.
    let cache = Cache::builder()
        .max_capacity(5_000)
        .time_to_live(Duration::from_secs(30))
        .build();

    // Insert asynchronously.
    cache.insert(42, "the answer".to_string()).await;

    // Concurrent access from multiple tasks.
    let handles: Vec<_> = (0..10)
        .map(|_| {
            let c = cache.clone(); // cheap clone
            tokio::spawn(async move {
                let v = c.get(&42).await;
                assert_eq!(v, Some("the answer".to_string()));
            })
        })
        .collect();

    futures_util::future::join_all(handles).await;

    // Let entry expire.
    time::sleep(Duration::from_secs(35)).await;
    cache.run_pending_tasks().await;
    assert!(cache.get(&42).await.is_none());
}

```

Key implementation files:
- [`src/future/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/future/cache.rs) – Async `Cache<K,V>` definition (line 633)
- [`src/future/builder.rs`](https://github.com/moka-rs/moka/blob/main/src/future/builder.rs) – Async builder configuration

### Integration with Async Web Frameworks (Axum)

The async cache is required when your surrounding code is async, such as in Axum handlers:

```rust
use axum::{
    extract::Extension,
    routing::get,
    Router,
};
use moka::future::Cache;
use std::sync::Arc;

async fn handler(Extension(cache): Extension<Arc<Cache<String, usize>>>) -> String {
    let key = "hits".to_string();
    let count = cache.get_with(key.clone(), async { 0 }).await + 1;
    cache.insert(key, count).await;
    format!("Hits: {count}")
}

#[tokio::main]
async fn main() {
    let cache = Arc::new(Cache::new(100));
    let app = Router::new()
        .route("/", get(handler))
        .layer(Extension(cache));

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

```

## Key Implementation Files

| File Path | Role |
|---|---|
| [`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs) (line 571) | Synchronous `Cache<K,V>` struct implementation |
| [`src/sync/builder.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/builder.rs) | Builder API for sync cache configuration |
| [`src/future/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/future/cache.rs) (line 633) | Asynchronous `Cache<K,V>` struct implementation (requires `future` feature) |
| [`src/future/builder.rs`](https://github.com/moka-rs/moka/blob/main/src/future/builder.rs) | Builder API for async cache configuration |
| `src/common/*` | Shared data structures and eviction logic used by both variants |

## Summary

- **Choose `sync`** when working with blocking code, thread pools, or CPU-bound tasks where an async runtime is unnecessary.
- **Choose `future`** when integrating with Tokio, async-std, or other async runtimes to avoid blocking executor threads.
- Both variants share the same **BaseCache** internals, providing identical eviction, expiration, and concurrency guarantees.
- Both support cheap **Arc-based cloning** to share state across threads or async tasks.
- The `future` cache requires the `future` feature flag and an async runtime dependency.

## Frequently Asked Questions

### Can I use both sync and async caches in the same project?

Yes, you can import both `moka::sync::Cache` and `moka::future::Cache` in the same crate. They are independent types that share no state between them, allowing you to use the sync variant for background thread pools and the async variant for your web server within the same application.

### Do sync and async caches have different performance characteristics?

No, both use the same lock-free concurrent hash table and eviction algorithms from `src/common/`. The only difference is the API wrapper—sync methods block the current thread while async methods return futures. The underlying cache operations have identical throughput and latency characteristics once the cache is accessed.

### How do I enable the async cache features?

Add `moka` to your [`Cargo.toml`](https://github.com/moka-rs/moka/blob/main/Cargo.toml) with the `future` feature enabled: `moka = { version = "0.12", features = ["future"] }`. This compiles the [`src/future/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/future/cache.rs) module and exposes `moka::future::Cache`. Without this feature, only the sync cache is available.

### Is the sync cache thread-safe?

Yes, the sync cache is fully thread-safe and designed for concurrent access from multiple threads. It uses internal lock-free data structures, and cloning the cache (as implemented via `Arc` in [`src/sync/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/cache.rs)) creates a new handle to the same underlying data without requiring mutexes or additional synchronization in your code.