# Moka Weigher Closure Usage: Configuring Size-Aware Caching in Rust

> Learn how to use Moka weigher closures in Rust to configure size-aware caching. Assign custom weights to entries for memory bounds instead of entry counts.

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

---

**Moka's weigher closure allows you to assign custom `u32` weights to cache entries by providing a function that calculates size based on key-value pairs, enabling memory-bounded caching instead of entry-count limits.**

The `moka-rs/moka` crate provides high-performance concurrent caching for Rust applications. When you need to limit cache memory consumption rather than just the number of entries, understanding **Moka weigher closure usage** becomes essential for implementing size-aware eviction policies.

## What Is the Moka Weigher Closure?

By default, Moka treats every cache entry as having a weight of `1`, meaning `max_capacity` limits the total number of entries. The **weigher closure** changes this behavior by allowing you to specify a custom weight calculation function. This function receives references to the key and value and returns a `u32` representing the entry's relative cost or size.

## How the Weigher Works Internally

Understanding the internal flow helps clarify how your closure integrates with the cache lifecycle.

### Type Definition in concurrent.rs

The weigher is defined as a type alias in [`src/common/concurrent.rs`](https://github.com/moka-rs/moka/blob/main/src/common/concurrent.rs) at lines 22-23:

```rust
type Weigher<K, V> = Arc<dyn Fn(&K, &V) -> u32 + Send + Sync + 'static>;

```

This definition requires your closure to be thread-safe (`Send + Sync`) and have a `'static` lifetime, as the cache may invoke it from multiple threads during its entire lifetime.

### Builder API in builder.rs

The public API surface resides in [`src/sync/builder.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/builder.rs) at lines 414-422. The `CacheBuilder::weigher` method accepts your closure, wraps it in an `Arc`, and stores it in the builder struct:

```rust
pub fn weigher(
    self,
    weigher: impl Fn(&K, &V) -> u32 + Send + Sync + 'static,
) -> Self {
    // Stores the closure for use during cache construction
}

```

### Runtime Calculation in base_cache.rs

During insertion, [`src/sync/base_cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/base_cache.rs) at lines 40-42 invokes the stored closure:

```rust
fn weigh(&self, key: &K, value: &V) -> u32 {
    // If weigher exists, call it; otherwise return 1
}

```

The returned weight is stored in the entry's `EntryInfo` as `policy_weight` and used by the eviction policy (TinyLFU or LRU) to maintain the total `weighted_size` below `max_capacity`.

## Configuring a Weigher in Your Application

### Basic Synchronous Usage

The most common pattern uses byte length as weight. This example from [`examples/size_aware_eviction_sync.rs`](https://github.com/moka-rs/moka/blob/main/examples/size_aware_eviction_sync.rs) demonstrates limiting the cache to approximately 32 MiB:

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

fn main() {
    let cache = Cache::builder()
        .weigher(|_key, value: &String| -> u32 {
            // Convert usize to u32, clamping on overflow
            value.len().try_into().unwrap_or(u32::MAX)
        })
        .max_capacity(32 * 1024 * 1024) // 32 MiB budget
        .build();

    cache.insert(0, "zero".to_string());
    cache.insert(1, "a".repeat(10_000)); // Consumes 10 KB of weight
}

```

### Asynchronous Caches

The `moka::future::Cache` API mirrors the synchronous version. The weigher works identically across async boundaries:

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

#[tokio::main]
async fn main() {
    let cache = Cache::builder()
        .weigher(|_key, value: &Vec<u8>| -> u32 {
            value.len().try_into().unwrap_or(u32::MAX)
        })
        .max_capacity(64 * 1024 * 1024) // 64 MiB
        .build();

    cache.insert(42, vec![0u8; 1_024 * 1024]).await; // 1 MiB entry
}

```

### Custom Weight Logic

For complex domain objects, combine multiple factors to calculate weight:

```rust
#[derive(Clone)]
struct Document {
    text: String,
    images: usize,
}

use moka::sync::Cache;

let cache = Cache::builder()
    .weigher(|_key, doc: &Document| -> u32 {
        let text_weight = doc.text.len() as u32;
        let image_weight = (doc.images * 10_240) as u32; // 10 KB per image
        text_weight.saturating_add(image_weight)
    })
    .max_capacity(100 * 1024 * 1024)
    .build();

```

## Important Semantics and Constraints

When implementing your weigher closure, adhere to these requirements defined in the Moka source:

| Aspect | Requirement |
|--------|-------------|
| **Return Type** | Must be `u32`. The cache stores this as `policy_weight` in `EntryInfo`. |
| **Thread Safety** | Closure must implement `Send + Sync + 'static` because the cache invokes it from multiple threads. |
| **Panic Safety** | The closure must never panic. Panics during weight calculation will abort the insertion operation. |
| **Overflow Handling** | If your calculation exceeds `u32`, clamp to `u32::MAX` or use `saturating_add` to prevent wraparound. |
| **Eviction Impact** | Both TinyLFU and LRU policies use the calculated weight; larger weights cause earlier eviction when capacity is reached. |

## Interaction with Other Cache Features

The weigher closure integrates with other Moka capabilities without conflict:

- **Expiration Policies** – Time-to-live (TTL) and time-to-idle (TTI) operate independently of weights. An entry can expire due to time regardless of its weight.
- **Invalidation Closures** – When using `support_invalidation_closures()`, the weight of invalidated entries is automatically subtracted from the total `weighted_size`.
- **Eviction Listeners** – When the cache evicts an entry due to weight constraints, the eviction listener receives the key and value exactly as it would for count-based eviction.

## Summary

- The **weigher closure** in `moka-rs/moka` enables **size-aware caching** by converting key-value pairs into `u32` weights.
- Define the closure using `Cache::builder().weigher(|k, v| ...)`; the implementation resides in [`src/sync/builder.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/builder.rs) (lines 414-422).
- The closure must be `Send + Sync + 'static`, return `u32`, and never panic.
- Internally, [`src/sync/base_cache.rs`](https://github.com/moka-rs/moka/blob/main/src/sync/base_cache.rs) (lines 40-42) invokes the closure during insertion to set `policy_weight` in the entry metadata.
- Use this feature to limit cache memory consumption (e.g., bytes, object complexity) rather than just entry counts.

## Frequently Asked Questions

### What happens if I don't specify a weigher closure?

If you do not call `.weigher()` on the builder, Moka defaults to a unit weight of `1` for every entry. In this mode, `max_capacity` limits the total number of entries rather than their cumulative weight.

### Can the weigher closure capture variables from the surrounding scope?

Yes, the weigher can capture variables, but they must satisfy the `'static` lifetime requirement. Avoid capturing references to short-lived data; instead, use `Arc` or owned data if the closure needs external state.

### How does the weigher affect cache performance?

The weigher is invoked synchronously during every `insert` operation. Keep the closure lightweight to avoid blocking cache operations. Complex calculations (e.g., deep object traversal) should be pre-computed and stored in the value type if possible.

### What should I do if my value size exceeds u32::MAX?

Clamp the value to `u32::MAX` using `try_into().unwrap_or(u32::MAX)` or `saturating_add` for combined calculations. While this caps individual entries at the maximum weight, it prevents overflow and ensures the cache continues to function correctly.