Moka Integration with async-std: A Complete Runtime Guide

Moka's asynchronous cache works seamlessly with async-std by enabling the future feature, which provides runtime-agnostic Future implementations compatible with any async executor.

The moka-rs/moka crate provides high-performance, concurrent caching for Rust applications, and integrating Moka with async-std requires only the future feature flag to unlock async cache operations. This integration leverages the standard std::future::Future trait to ensure compatibility without runtime-specific dependencies.

How Moka Supports async-std

Moka's async support is implemented in the future module, available when the future crate feature is enabled. Unlike some async libraries that hardcode runtime dependencies, Moka's cache is runtime-agnostic—it returns impl Future types that any executor can drive, including async-std's task scheduler.

Enabling the future Feature

To use Moka with async-std, activate the feature in Cargo.toml:

[dependencies]
moka = { version = "0.12", features = ["future"] }
async-std = { version = "1", features = ["attributes"] }

This feature flag pulls in optional dependencies (async-lock, event-listener, futures-util) and compiles the src/future/cache.rs module, which contains the async-aware cache implementation.

Runtime-Agnostic Architecture

In src/future/cache.rs, the Cache type implements Send (via unsafe impl<K, V, S> Send for Cache at line 641) and Sync, allowing safe sharing across async-std tasks. All async methods—including insert, get, and invalidate—return futures that are Send + Sync, satisfying async-std's task requirements without additional synchronization primitives.

As documented in the repository's README.md, the future feature must be specified to use the cache under async runtimes such as tokio or async-std.

Code Examples for async-std Integration

Basic async-std Usage with #[async_std::main]

The simplest integration uses async-std's main macro to run cache operations directly. This pattern is documented in MIGRATION-GUIDE.md (lines 136-166):

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

#[async_std::main]
async fn main() {
    // Create a future-aware cache that can hold 10_000 entries.
    let cache = Arc::new(Cache::new(10_000));

    // Insert a value asynchronously.
    cache.insert("hello", "world".to_owned()).await;

    // Retrieve it later.
    if let Some(v) = cache.get(&"hello").await {
        println!("Got: {v}");
    }
}

Blocking from Synchronous Code with block_on

When calling Moka from synchronous contexts within an async-std application, use async_std::task::block_on to drive the futures:

use std::sync::Arc;
use async_std::task;
use moka::future::Cache;

fn main() {
    let cache = Arc::new(Cache::new(100));

    // Spawn a native OS thread that inserts a value.
    let cache_cloned = Arc::clone(&cache);
    std::thread::spawn(move || {
        // `block_on` drives the future on async-std's executor.
        task::block_on(cache_cloned.insert(42, "answer".to_owned()));
    })
    .join()
    .unwrap();

    // Verify from the normal thread (still uses block_on).
    let value = task::block_on(cache.get(&42));
    assert_eq!(value, Some("answer".to_owned()));
}

This approach, identical to the block-on example in MIGRATION-GUIDE.md, allows synchronous initialization code to populate the cache before spawning async tasks.

Concurrent Access Across Multiple Tasks

Because Cache is both Send and Sync, you can share it across many concurrent async-std tasks using Arc:

use async_std::task;
use std::sync::Arc;
use moka::future::Cache;

#[async_std::main]
async fn main() {
    const N: usize = 8;
    let cache = Arc::new(Cache::new(1_000));

    // Launch several async tasks that concurrently insert entries.
    let mut handles = Vec::new();
    for i in 0..N {
        let c = Arc::clone(&cache);
        handles.push(task::spawn(async move {
            c.insert(i, format!("value-{i}")).await;
        }));
    }

    // Wait for all tasks to finish.
    for h in handles {
        h.await;
    }

    // All entries are now present.
    for i in 0..N {
        assert_eq!(cache.get(&i).await, Some(format!("value-{i}")));
    }
}

Each task receives a cloned Arc<Cache>, enabling cheap, lock-free sharing of the cache state across async-std's task scheduler.

Key Source Files and Implementation Details

Understanding these files clarifies how Moka achieves async-std compatibility:

  • Cargo.toml – Defines the future feature flag that enables async cache support and pulls in required dependencies (async-lock, event-listener, futures-util).
  • src/future/cache.rs – Contains the core Cache implementation, including the Send trait implementation (line 641) that guarantees thread safety across async-std tasks.
  • MIGRATION-GUIDE.md – Provides concrete async-std usage patterns, including the async_std::task::block_on snippet and #[async_std::main] examples.
  • README.md – Documents supported runtimes (Tokio, async-std, Actix-rt) and explains that the future feature is required for async runtime integration.

Summary

  • Enable the future feature in Cargo.toml to compile Moka's async cache module.
  • Use moka::future::Cache for all async operations; it returns standard Future types compatible with async-std.
  • The cache implements Send + Sync, allowing safe sharing across async_std::task::spawn without additional synchronization.
  • From synchronous code, use async_std::task::block_on to drive cache futures on async-std's executor.
  • No runtime-specific code is required in src/future/cache.rs, making the integration runtime-agnostic.

Frequently Asked Questions

Does Moka require Tokio to work with async-std?

No. According to the moka-rs/moka source code, the cache is runtime-agnostic. When the future feature is enabled, the cache works with any executor implementing std::future::Future, including async-std, Tokio, or Actix-rt. The README.md explicitly lists async-std as a supported runtime.

How do I enable async support in Moka?

Add features = ["future"] to your Cargo.toml dependency declaration for moka. This activates the future module in src/future/cache.rs and pulls in necessary async dependencies (async-lock, event-listener, futures-util) without binding to a specific runtime.

Is Moka's async cache thread-safe for async-std tasks?

Yes. The Cache type in src/future/cache.rs implements both Send and Sync (see the unsafe impl block at line 641), meaning it can safely be shared between async-std tasks using Arc and accessed concurrently without data races.

Can I use Moka from synchronous code when using async-std?

Yes. Use async_std::task::block_on to execute cache operations from synchronous contexts. As shown in MIGRATION-GUIDE.md, this pattern allows you to bridge sync and async code when pre-populating the cache or accessing it from non-async threads.

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 →