# Moka Integration with Tokio: A Complete Guide to Async Caching

> Learn Moka integration with Tokio. Discover how to use moka::future::Cache for efficient async caching without Tokio specific dependencies. Unlock high performance.

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

---

**Moka integrates with Tokio through its runtime-agnostic `moka::future::Cache` type, which implements standard `Future` traits that Tokio can poll directly without requiring any Tokio-specific dependencies in the crate.**

The `moka-rs/moka` crate provides high-performance concurrent caching for Rust, with dedicated async support designed to work naturally with Tokio's task model. Because Moka's async implementation relies solely on the standard `Future` trait and lock-free data structures, it composes seamlessly with Tokio executors while maintaining compatibility with other async runtimes.

## Core Architecture of the Async Cache

Moka's async functionality centers on the `future::Cache` type defined in [`src/future/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/future/cache.rs), which wraps a lock-free hash table with async-friendly APIs.

### The `future::Cache` Struct

At `src/future/cache.rs#L33-L43`, the `Cache` struct defines the main async cache type. It stores entries in a lock-free hash table and performs maintenance operations—including eviction and expiration—lazily when cache methods are called. The cache is runtime-agnostic by design, meaning Tokio is only required to drive the async tasks that interact with the cache, not to operate the cache internals themselves.

### Async Constructors and Methods

The cache provides standard constructors that work immediately with Tokio tasks:

- **`Cache::new`** and **`Cache::builder`** – Create caches with specified capacities and policies. Documentation comments at `src/future/cache.rs#L77-L86` demonstrate Tokio-specific usage patterns.
- **`Cache::get`**, **`Cache::insert`**, **`Cache::invalidate`** – These async methods return `Future` types that Tokio can await. The signature `pub async fn get` appears at `src/future/cache.rs#L74-L86`.

### Entry Selectors for Advanced Patterns

For richer cache manipulation, Moka provides `Future`-based entry selectors defined in `src/future/entry_selector.rs#L17-L30`. The `OwnedKeyEntrySelector` and `RefKeyEntrySelector` types offer methods like `or_insert` and `or_insert_with_if` that return futures, enabling complex conditional insertion logic within Tokio tasks.

### Explicit Maintenance with `run_pending_tasks`

When using time-based expiration (TTL/TTI), the `run_pending_tasks` method forces immediate processing of pending maintenance work. Located around `src/future/cache.rs#L540-L547`, this method drains bounded channels and cleans up expired entries. Tokio tasks often invoke this after sleeping to ensure expirations are applied promptly.

## Why Moka Requires No Tokio-Specific Code

Moka intentionally depends only on the standard `Future` trait and core async primitives (`Arc`, `Pin`, etc.). This architectural decision means the crate contains no Tokio imports, allowing the same binary to run on async-std, smol, or other executors without modification. The repository includes Tokio-focused examples and tests to demonstrate typical usage, but the library itself remains runtime-agnostic.

## How Tokio Drives Moka's Async Operations

When you annotate a function with `#[tokio::main]` or `#[tokio::test]`, the Tokio runtime polls the futures returned by Moka's methods. Each call—such as `cache.insert(key, value).await`—schedules work on the runtime's thread pool, but the underlying cache operations remain non-blocking and lock-free.

Cloning the cache is cheap because it only increments an internal `Arc` reference count, making it safe to share across multiple Tokio tasks. The async version of the cache is effectively a thin async façade over the same lock-free core used by the synchronous `moka::sync::Cache`.

## Practical Tokio Integration Examples

The following patterns demonstrate common Moka integration scenarios with Tokio.

### 1. Basic Shared Cache Across Tokio Tasks

This example shows cheap cache cloning and concurrent access from multiple tasks:

```rust
use moka::future::Cache;

#[tokio::main]
async fn main() {
    const NUM_TASKS: usize = 8;
    const KEYS_PER_TASK: usize = 100;

    // Create a cache that can hold up to 20,000 entries.
    let cache = Cache::new(20_000);

    // Spawn a bunch of concurrent Tokio tasks.
    let handles: Vec<_> = (0..NUM_TASKS)
        .map(|task_id| {
            let my_cache = cache.clone();
            tokio::spawn(async move {
                let start = task_id * KEYS_PER_TASK;
                let end = start + KEYS_PER_TASK;

                // Insert and immediately read each key.
                for key in start..end {
                    my_cache.insert(key, format!("value-{key}")).await;
                    assert_eq!(my_cache.get(&key).await, Some(format!("value-{key}")));
                }

                // Invalidate every third key.
                for key in (start..end).step_by(3) {
                    my_cache.invalidate(&key).await;
                }
            })
        })
        .collect();

    // Wait for all tasks to finish.
    futures_util::future::join_all(handles).await;

    // Verify the final state.
    for key in 0..(NUM_TASKS * KEYS_PER_TASK) {
        if key % 3 == 0 {
            assert!(cache.get(&key).await.is_none());
        } else {
            assert_eq!(cache.get(&key).await, Some(format!("value-{key}")));
        }
    }
}

```

### 2. Using `get_with` to Coalesce Expensive Initialization

The `get_with` method ensures that concurrent tasks share a single initialization future, preventing duplicate work:

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

#[tokio::main]
async fn main() {
    // A cache that holds large binary blobs.
    let cache: Cache<u32, Arc<Vec<u8>>> = Cache::new(100);

    // Simulate four tasks that need the same heavy data.
    let handles: Vec<_> = (0..4)
        .map(|id| {
            let c = cache.clone();
            tokio::spawn(async move {
                let data = c
                    .get_with(42, async {
                        // This block runs once even though four tasks call it.
                        println!("Task {id} creating the blob…");
                        Arc::new(vec![0u8; 10 * 1024 * 1024]) // 10 MiB
                    })
                    .await;
                println!("Task {id} got {} bytes", data.len());
            })
        })
        .collect();

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

```

### 3. Time-to-Live (TTL) Expiration with Explicit Housekeeping

When using TTL policies, call `run_pending_tasks` after delays to force expiration processing:

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

#[tokio::main]
async fn main() {
    // TTL of 5 seconds, no idle timeout.
    let cache = Cache::builder()
        .time_to_live(Duration::from_secs(5))
        .max_capacity(10)
        .build();

    cache.insert("temp", "data".to_string()).await;
    assert!(cache.contains_key(&"temp"));

    // Wait longer than the TTL.
    tokio::time::sleep(Duration::from_secs(6)).await;

    // Force the cache to process pending expirations.
    cache.run_pending_tasks().await;
    assert!(!cache.contains_key(&"temp"));
}

```

## Key Source Files in moka-rs/moka

Understanding these implementation files helps when debugging Moka integration with Tokio:

- **[`src/future/cache.rs`](https://github.com/moka-rs/moka/blob/main/src/future/cache.rs)** – Contains the core async cache implementation, including method signatures for `get`, `insert`, and `run_pending_tasks`, plus doc examples showing Tokio usage.
- **[`src/future/entry_selector.rs`](https://github.com/moka-rs/moka/blob/main/src/future/entry_selector.rs)** – Implements async entry selectors (`OwnedKeyEntrySelector`, `RefKeyEntrySelector`) that provide the `or_insert` API.
- **[`tests/runtime_tokio.rs`](https://github.com/moka-rs/moka/blob/main/tests/runtime_tokio.rs)** – Test harness spawning Tokio tasks to validate thread-safety and runtime integration.
- **[`tests/entry_api_tokio.rs`](https://github.com/moka-rs/moka/blob/main/tests/entry_api_tokio.rs)** – Additional Tokio-based tests covering the entry API methods.
- **[`examples/basics_async.rs`](https://github.com/moka-rs/moka/blob/main/examples/basics_async.rs)** – Minimal example demonstrating cache creation and basic operations in an async context.

## Summary

- **Moka's async cache is runtime-agnostic**, relying only on the standard `Future` trait rather than Tokio-specific APIs.
- **`moka::future::Cache`** provides lock-free concurrent access suitable for Tokio's multi-threaded scheduler.
- **Cheap cloning** via `Arc` allows efficient sharing across Tokio tasks without blocking.
- **`get_with`** enables request coalescing, ensuring expensive initialization runs only once across concurrent tasks.
- **`run_pending_tasks`** forces immediate expiration processing when TTL policies require deterministic cleanup.

## Frequently Asked Questions

### Does Moka require Tokio as a dependency?

No. According to the `moka-rs/moka` source code, the crate depends only on the standard `Future` trait and async primitives. It works with any async executor including Tokio, async-std, and smol.

### How do I share a Moka cache across multiple Tokio tasks?

Clone the cache handle before spawning tasks. The cache uses `Arc` internally, making clones inexpensive and safe for concurrent access across Tokio's thread pool.

### When should I call `run_pending_tasks` in a Tokio application?

Call `run_pending_tasks().await` after `tokio::time::sleep` or other delays when you need to guarantee that TTL-based expirations have been processed immediately, rather than waiting for the next cache operation to trigger maintenance.

### Can I use the synchronous Moka cache with Tokio?

Yes, but you should use `moka::future::Cache` instead. The sync cache blocks threads, which can starve Tokio's runtime. The async version provides the same lock-free core with non-blocking APIs designed for async/await contexts.