NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
crates.io · #1556 most downloaded on crates.io
Types used by the cached proc-macro crate
Last release 1 months ago
23 Aug 2026
Ships unpredictably
gaps range from 1 weeks to 3.1 years
Some releases are documented
notes for 2 of 4 stable releases
Nothing withdrawn
no release was ever pulled
6 years old
10 releases · first in 2020
One column per quarter.
feat: remove CacheSetError , rename TtlSortedCache insert* to set* , wrap order-method values in CacheValue by @jaemk in #293
CacheSetError, rename TtlSortedCache insert* to set*, wrap order-method values in CacheValue by @jaemk in #293peek, builder new(), ext alias parity, concurrent cache_try_get_or_set_with, retain on map stores by @jaemk in #294cache_contains implementor listing and docsrs gate, note Arc<T> return pattern, add peek recency test by @jaemk in #295Clone error for #[cached] return types, async_core docsrs badges, doc and spec corrections by @jaemk in #296TtlSortedCache::set_with builder, ConcurrentCachePeek, ConcurrentCachedExt::try_get_or_set_with by @jaemk in #297retain on TtlSortedCache and the six sharded stores by @jaemk in #298cache_set overwrite by @jaemk in #299max_size, make cache_contains dyn-callable by @jaemk in #300on_evict, clear Clone error for #[once], no-callback retain fast path by @jaemk in #301retain returns removed count, ConcurrentCachePeekAsync, MSRV 1.92 by @jaemk in #302tag-release.sh by @jaemk in #304*Base types, split refresh-on-hit traits, fix redis key collisions by @jaemk in #305serde feature, fix stale docs by @jaemk in #306on_evict key, migration guide corrections, proc_macro alias by @jaemk in #308Full Changelog: v3.0.0-rc.9...cached_proc_macro_types-v3.0.0
CachedGetOrSetAsync by @jaemk in #276sync_writes_buckets, drop expiring-store struct bounds, derive ConnectionString eq, store format and doc fixes by @jaemk in #284set_max_size to sharded LRU stores, rename RedbCacheBuilder::disk_dir, add resilience example by @jaemk in #285ExpiringLruCache::retain, docs and spec fixes by @jaemk in #286SetMaxSizeError::ZeroSize to ZeroMaxSize, document redis/redb format stability, correct spec drift by @jaemk in #287Acquire load for sharded metrics().capacity, document concurrent set_max_size semantics, sync specs by @jaemk in #288Error types, add cache_contains, peek/reset aliases by @jaemk in #290cache_contains, peek-based contains parity, remove inherent TtlSortedCache::set_ttl by @jaemk in #291CacheSetError, rename TtlSortedCache insert* to set*, wrap order-method values in CacheValue by @jaemk in #293peek, builder new(), ext alias parity, concurrent cache_try_get_or_set_with, retain on map stores by @jaemk in #294cache_contains implementor listing and docsrs gate, note Arc<T> return pattern, add peek recency test by @jaemk in #295Clone error for #[cached] return types, async_core docsrs badges, doc and spec corrections by @jaemk in #296TtlSortedCache::set_with builder, ConcurrentCachePeek, ConcurrentCachedExt::try_get_or_set_with by @jaemk in #297retain on TtlSortedCache and the six sharded stores by @jaemk in #298cache_set overwrite by @jaemk in #299max_size, make cache_contains dyn-callable by @jaemk in #300on_evict, clear Clone error for #[once], no-callback retain fast path by @jaemk in #301retain returns removed count, ConcurrentCachePeekAsync, MSRV 1.92 by @jaemk in #302tag-release.sh by @jaemk in #304*Base types, split refresh-on-hit traits, fix redis key collisions by @jaemk in #305serde feature, fix stale docs by @jaemk in #306on_evict key, migration guide corrections, proc_macro alias by @jaemk in #308Full Changelog: v2.0.2...v3.0.0
This entry describes the complete 2.0.2 -> 3.0.0 delta. The ten release candidates
(3.0.0-rc.1 through 3.0.0-rc.10) are folded in here, and API that was introduced and
then changed again across the candidates is recorded only in its final shipped form; the rc
git tags remain. The upgrade is documented step by step in the
migration guide, with the mechanical breaking-change
list in the agent-oriented guide.
redb 4.x set the 1.89 floor, and the async_core feature
(enabled by async) does not compile before 1.92: the two CachedGetOrSetAsync RPIT
default bodies hit a rustc borrowck limitation (rust-lang/rust#100013). Verified by
bisection (fails on 1.89.0, 1.90.0, 1.91.0; clean on 1.92.0). Non-async feature sets built
on 1.89, but rust-version is a single crate-level value, so the floor moves for every
feature set.cached_proc_macro_types moved to edition 2024, and its version now tracks cached in
lockstep rather than a standalone 1.0.DiskCache is renamed RedbCache (naming the backend, like RedisCache) and is backed by
redb 4.x instead of the unmaintained sled, dropping the
RustSec-flagged fxhash transitive dependency. Still pure-Rust (no C toolchain). There are
no DiskCache* aliases: rename DiskCache / DiskCacheBuilder / DiskCacheError /
DiskCacheBuildError to RedbCache* at the call site. The on-disk format changed and
DISK_FILE_VERSION was bumped, so existing caches are not read and entries are recomputed.RedbCache::connection() / connection_mut(), RedbCacheBuilder::connection_config, and
the connection_config macro attribute are removed; the backend handle is not exposed.DiskCacheBuilder::sync_to_disk_on_cache_change is renamed durable and the default flipped
from false to true (fsync per write), so a disk cache persists by default. durable(false)
uses Durability::None, which can lose writes on process exit or crash; call
RedbCache::flush() / async_flush() to force a durable commit.RedbCacheBuilder::disk_directory is renamed disk_dir, matching the disk_dir attribute on
#[concurrent_cached].ShardedCache is renamed ShardedUnboundCache (with ShardedCacheBuilder ->
ShardedUnboundCacheBuilder); the old name read as the umbrella for the whole sharded family
while naming only the unbounded variant. No deprecated alias.ShardedX<K, V, H = DefaultShardHasher>, mirroring HashMap<K, V, S = RandomState>. The 2.x
ShardedCacheBase pattern is gone: there is no ShardedUnboundCacheBase, ShardedLruCacheBase,
ShardedTtlCacheBase, ShardedLruTtlCacheBase, ShardedExpiringCacheBase, or
ShardedExpiringLruCacheBase. Migration is a mechanical rename dropping Base.cached::TimedEntry is now pub(crate), and the store() accessors on UnboundCache,
TtlCache, LruTtlCache, and ExpiringLruCache are removed. They exposed the internal
backing map and leaked the internal entry wrapper; use the public Cached API instead.get, set, remove, remove_entry, clear, len, is_empty,
delete, try_set, contains, hits, misses, metrics, and the short get_or_set_with
family) moved off Cached / ConcurrentCached onto the blanket extension traits CachedExt /
ConcurrentCachedExt. The core traits keep only the cache_-prefixed methods, so a custom
store implements a smaller surface. Callers using cached::prelude::* need no change; others
add use cached::CachedExt; / use cached::ConcurrentCachedExt;, or use the cache_ names.
Custom impl Cached / impl ConcurrentCached blocks must drop any short-alias methods.ConcurrentCachedAsync's cache operations carry an async_ prefix (async_cache_get,
async_cache_set, async_cache_remove, async_cache_remove_entry, async_cache_delete),
removing the E0034 "multiple applicable items" error when both concurrent traits are in scope.type Error, cache_size,
cache_is_empty) lives on ConcurrentCacheBase, the supertrait of both concurrent traits;
the global-TTL controls (ttl, set_ttl, try_set_ttl, unset_ttl) live on
ConcurrentCacheTtl, implemented only by the TTL-capable concurrent stores. len is removed
from the base trait as a duplicate of cache_size. Custom impls must move type Error (and
any size override) into an impl ConcurrentCacheBase block and TTL behavior into
impl ConcurrentCacheTtl.refresh_on_hit / set_refresh_on_hit live on their own CacheRefreshOnHit and
ConcurrentCacheRefreshOnHit traits rather than on CacheTtl / ConcurrentCacheTtl.
CacheRefreshOnHit is implemented by TtlCache and LruTtlCache;
ConcurrentCacheRefreshOnHit by RedisCache, AsyncRedisCache, RedbCache,
ShardedTtlCache, and ShardedLruTtlCache. TtlSortedCache implements neither: its
deadline-ordered index cannot refresh an entry's expiry on read, and its 2.x
set_refresh_on_hit was a no-op that discarded its argument. Both new traits are in the
prelude. The inherent refresh_on_hit / set_refresh_on_hit on TtlCache and LruTtlCache
are removed (they shadowed the trait methods and the setter returned ()), as are the
inherent TTL controls on the sharded TTL stores and TtlSortedCache::set_ttl: runtime TTL
control is trait-only.CacheTtl and CacheEvict are single-owner (&mut self) traits only, since &mut self is
unusable on a store held through Arc/static. Concurrent stores set TTL through
ConcurrentCacheTtl::set_ttl (&self) and evict through the new ConcurrentCacheEvict
(fn evict(&self) -> usize).Cached and ConcurrentCacheBase gained an associated type Error, bounded by
std::error::Error + Send + Sync + 'static. Every built-in in-memory store (UnboundCache,
LruCache, TtlCache, LruTtlCache, TtlSortedCache, ExpiringCache, ExpiringLruCache,
and the six sharded stores) is infallible: type Error = std::convert::Infallible. A TTL that
would overflow Instant bounds stores the entry with no expiry instead of failing, so
cache_try_set no longer has a dedicated error type; the 2.x TtlSortedCacheError and the
boxed Box<dyn std::error::Error> return are both gone.Cached::cache_get_or_set_with / cache_try_get_or_set_with (and their aliases) return
&V / Result<&V, E> instead of &mut V (#179). The new *_mut variants
(cache_get_or_set_with_mut, cache_try_get_or_set_with_mut, and the async spellings)
preserve the mutable-reference behavior. External impls must update their signatures and
implement the new required *_mut methods.CachedAsync trait is renamed CachedGetOrSetAsync, naming the job it actually does
(memoizing an async closure over a synchronous in-memory Cached store). Its four sync
passthroughs (async_cache_get / async_cache_set / async_cache_remove /
async_cache_clear) and the misleading Self: Cached bound are removed, and its get-or-set
methods use the async_cache_* namespace (async_cache_get_or_set_with,
async_cache_try_get_or_set_with, and their _mut variants).cache_clear / cache_reset on ConcurrentCached
(and the async counterparts), whose 2.x no-op Ok(()) defaults silently did nothing;
cache_peek_with_expiry_status on CloneCached / ConcurrentCloneCached, whose defaults
returned a wrong result that silently broke force_refresh + result_fallback; and
cache_contains / async_cache_contains on ConcurrentCached / ConcurrentCachedAsync,
which carry no V: Clone bound so contains works for non-Clone values.
ConcurrentCached::cache_contains has no where Self: Sized bound and is dyn-callable.SerializeCached::cache_set_ref and SerializeCachedAsync::async_cache_set_ref return
Result<(), Self::Error> instead of Result<Option<V>, Self::Error>, removing a per-write
read-and-decode round trip on the IO stores. Call cache_get first if you need the prior value.ShardHasher requires Clone as a supertrait, and any thread-safe std::hash::BuildHasher
now implements it through a blanket impl, so std::hash::RandomState and ahash::RandomState
are accepted directly by the sharded builders' .hasher(...). DefaultShardHasher implements
BuildHasher and reaches ShardHasher through that one blanket path, which also makes it
usable with HashMap::with_hasher and LruCacheBuilder::hasher. A type cannot implement both
BuildHasher and a hand-written ShardHasher (coherence rejects the pair), so a custom
shard-routing hasher must not implement BuildHasher.Expires::expires_at returns crate::time::Instant (web-time backed, correct under wasm)
instead of std::time::Instant, and CloneCached::cache_get_with_expiry_status requires
V: Clone, matching its peek sibling.cache_set over an existing key promotes that key to most-recently-used on LruCache,
LruTtlCache, ExpiringLruCache, ShardedLruCache, ShardedLruTtlCache, and
ShardedExpiringLruCache. In 2.x the value was replaced in place and the entry kept its
position, so this changes which entry a capacity eviction selects in overwrite-heavy
workloads. It also resolves a divergence where configuring an on_evict callback changed
eviction order on two sharded stores. cache_peek, cache_peek_with_expiry_status, and
cache_contains remain non-promoting; inserting a new key is unchanged. No public API writes
a value without touching recency.on_evict receives the displaced entry's own stored key rather than the caller's Eq-equal
instance, on every store and every removal path, matching HashMap::insert. Observable only
for key types whose Eq/Hash ignore part of the payload.on_evict, on every removal path (evict,
retain, cache_remove / cache_remove_entry, lazy expiry sweeps, cache_set over an
expired entry, capacity evictions, and the get-or-set families), so a panicking callback can
no longer remove an entry without counting it.retain returns usize (the number of entries removed) instead of () on all 13 stores that
have it. The count includes entries the predicate rejected and, on the expiry-aware stores,
entries removed for having expired regardless of the predicate. This diverges from
HashMap::retain deliberately, because this retain does strictly more than filter, and it
matches TtlSortedCache::retain_latest. There is no #[must_use], so existing
cache.retain(...); statements keep compiling; only call sites binding the result as ()
need a discard.set_max_size returns Option<usize> (the previous bound) on LruCache, LruTtlCache, and
ExpiringLruCache, unifying the return type with TtlSortedCache.ShardedLruCache,
ShardedLruTtlCache, ShardedExpiringLruCache) is capped by the requested max_size
instead of derived solely from available_parallelism(): on the default path the count is
next_power_of_two(max_size / 16).clamp(1, default_shard_count()). This changes the
observable capacity(), shards(), and shard_sizes() for small caches on high-core-count
hosts (ShardedLruCache::new(100) resolves to 8 shards / capacity 128, where a 64-core box
previously produced 256 shards / capacity 4096). An explicit .shards(n) is authoritative;
the per_shard_max_size path and the unbounded stores keep default_shard_count().ShardedTtlCache and ShardedLruTtlCache decide expiry against a clock sample taken before
the shard lock is acquired, so an entry that crosses its expiry while the caller queues for
the lock is judged live. This stays within the documented lazy-expiry contract, which makes
no promise of prompt removal.set_ttl applies to future inserts only; existing
entries keep their computed expiry, and refresh_on_hit recomputes expiry from the current
TTL at access time. A zero Duration passed to any set_ttl surface means "expiry disabled",
exactly equivalent to unset_ttl(): it no longer panics on the sharded stores and no longer
means "expire immediately" on TtlSortedCache. build() still rejects a zero TTL, and
try_set_ttl(0) still returns SetTtlError::ZeroTtl. For the redis stores a disabled TTL
writes keys without expiry (a plain SET) and the refresh path issues no EXPIRE.TtlSortedCache gains a set family in place of insert / insert_ttl / insert_evict /
insert_ttl_evict: set(k, v) plus the set_with(k, v) entry-setter builder, which chains
.ttl(Duration) / .ttl_secs(n) / .ttl_millis(n) for a per-entry override and .evict()
for the post-insertion sweep before the terminal .set() -> Option<V>. TtlSortedSetBuilder
is re-exported from the crate root.TtlSortedCache's get-or-set family no longer removes an expired entry before running the
initializer, so a cancelled or panicking initializer leaves the expired entry in place and
fires no on_evict; on success on_evict fires after the initializer. All four variants now
agree with each other and with TtlCache / LruTtlCache.iter_order / value_order on LruCache, LruTtlCache, and ExpiringLruCache return
CacheValue-wrapped values (Vec<(K, CacheValue<V, M>)> and Vec<CacheValue<V, M>>), one
shape across the LRU family. M is per-entry metadata: () for LruCache /
ExpiringLruCache, Option<Instant> for LruTtlCache (read through CacheValue::expires_at).
LruTtlCache no longer leaks bare (Option<Instant>, V) tuples. key_order is unchanged.cache_reset (and the concurrent counterparts) no longer preserves the preallocated backing
capacity: it clears and shrinks to initial_capacity, so later inserts may reallocate.
Recreate the cache instead of resetting it to retain the allocation.copy_from returns Result<_, BuildError> instead of panicking on invalid
configuration, and the Eq marker impls for UnboundCache and LruCache require V: Eq.get / set / remove / remove_entry / delete /
reset / contains / peek returning unwrapped values, so store.get(&k) is Option<V>
rather than Result<Option<V>, Infallible>. These take call-site priority over the
ConcurrentCached* trait methods, which return Result<_, Self::Error>; note that
s.set(k, v).unwrap() therefore compiles as Option::unwrap and panics on a first insert.
Use the cache_-prefixed trait methods, or UFCS, for the Result shape.capacity(n) is renamed initial_capacity(n) on UnboundCacheBuilder, TtlCacheBuilder,
TtlSortedCacheBuilder, and ExpiringCacheBuilder, where it pre-allocates without bounding
entry count. The name was ambiguous next to max_size(n) on the LRU builders.RedbCache::builder(name), RedisCache::builder(prefix), and AsyncRedisCache::builder(prefix)
take the primary required field as a positional argument. The 2.x ::new( entry points on
these three types are removed (they returned a builder, conflicting with the convention that
new() returns a ready store); the in-memory and sharded stores gained real
Type::new() / Type::new(required_field) constructors.LruTtlCacheBuilder and ShardedLruTtlCacheBuilder take the hasher in the third generic slot
and the eviction typestate marker last: LruTtlCacheBuilder<K, V, S = DefaultHashBuilder, E = NoEvict> and ShardedLruTtlCacheBuilder<K, V, H = DefaultShardHasher, E = NoEvict>.
LruTtlCacheBuilder had no hasher parameter in 2.x, so a 2.x annotation of
LruTtlCacheBuilder<K, V, HasEvict> names the hasher slot in 3.0 and must gain the hasher as
the third argument. Code naming only <K, V>, or reaching the hasher through .hasher(..),
is unaffected..ttl(...) stores keys without expiry. A TTL that is set
must be greater than zero, and RedisCacheBuildError::MissingRequired("ttl") is no longer
returned.RedisCacheBuilder::build() / AsyncRedisCacheBuilder::build() reject an empty prefix with
Build(BuildError::InvalidValue { field: "prefix", .. }). The prefix is what scopes
cache_clear to one logical cache; with an empty prefix, cache_clear matched
<namespace>:* and deleted the entries of every cache sharing the namespace.RedbCacheBuilder::build() validates cache_name as a filename component: empty, path
separators, path-traversal components, and any character invalid in a cross-platform filename
(: < > " | ? *, or an ASCII control byte) are rejected rather than silently
creating subdirectories or escaping the cache directory.refresh_on_hit: the refresh() alias is removed from
the in-memory TTL builders, and the redis/redb builders' refresh is renamed. The
#[cached(refresh = true)] attribute is unchanged.BuildError::InvalidTtl { ttl } is removed; a zero TTL at build time yields
BuildError::InvalidValue { field: "ttl", reason: "must be greater than zero" }.
RedisCacheBuildError::InvalidTtl and RedbCacheBuildError::InvalidTtl become
Build(BuildError), wrapping the inner error instead of duplicating it.Error suffix:
RedbCacheError::{StorageError, CacheDeserializationError, CacheSerializationError} became
{Storage, CacheDeserialization, CacheSerialization}; RedbCacheBuildError::ConnectionError
became Storage; RedisCacheError::{RedisCacheError, PoolError, CacheDeserializationError, CacheSerializationError} became {Redis, Pool, CacheDeserialization, CacheSerialization}.
RedbCacheError / RedbCacheBuildError are struct variants (named fields) matching the redis
enums, and CacheDeserialization carries a cached_value: Vec<u8> field.RedbCacheError, RedbCacheBuildError, RedisCacheError,
RedisCacheBuildError, BuildError, SetTtlError, SetMaxSizeError) are #[non_exhaustive],
so external matches need a wildcard arm.redis::, r2d2::, or redb:: types through
public fields or blanket From impls. Foreign causes are boxed behind
Box<dyn std::error::Error + Send + Sync> and read through source(), so a backing-crate
version bump is no longer a breaking change to these enums.Return<T>::value and Return<T>::was_cached are private fields
(cached_proc_macro_types). Use *r / r.into_inner() for the value and r.was_cached()
for the flag; struct pattern matches must switch to the accessors.CacheMetrics.size is renamed entry_count and is now Option<usize>, reporting None for
stores whose size is unknown (redis/redb) instead of a false 0. CacheMetrics is
#[non_exhaustive] and derives Default, so construct it by mutating
CacheMetrics::default() rather than with a struct literal.RedbCache::remove_expired_entries returns Result<usize, RedbCacheError> (the number
removed) instead of Result<(), RedbCacheError>, matching the evict traits.ttl attribute takes three mutually exclusive forms: ttl_secs = N (whole seconds,
replacing the 2.x bare-integer ttl = N), ttl_millis = N (milliseconds, new), and
ttl = "<Duration expr>" (a string-literal Duration expression). The old bare-integer form
produces an error directing you to ttl_secs. Builders gained matching .ttl_secs(n) /
.ttl_millis(n) methods (#149).size attribute is removed from #[cached] / #[concurrent_cached] (use
max_size = N; the macros detect size and emit a directed error), and the unbound
attribute is removed from #[cached] (a bare #[cached] already builds an UnboundCache).#[cached(refresh = true)] without a TTL is a compile error; it was previously ignored.
#[cached] also rejects result_fallback combined with with_cached_flag, and rejects an
explicit sync_writes_buckets when sync_writes is not "by_key" (the value was accepted
and silently ignored).#[cached] / #[once] reject the concurrent-store-only attributes (disk, redis,
map_error, shards, durable, disk_dir, cache_prefix_block) with a targeted redirect
to #[concurrent_cached], and #[once] rejects the #[cached]-only attributes
(result_fallback, refresh, max_size, ty, create, key, convert, sync_lock,
unsync_reads, sync_writes_buckets). #[concurrent_cached] rejects a custom ty without a
create block on the redis and disk paths, an async closure for map_error, and
cache_prefix_block on the disk path (it is redis-only).name starting with __cached (the prefix reserved for generated
bindings) and validate name as a Rust identifier.#[concurrent_cached]'s refresh attribute is a plain bool (was Option<bool>), so
refresh = false no longer conflicts with expires or a create block.redis_tokio and redis_smol enable the TLS-agnostic
connection path, so add redis_tokio_native_tls / redis_tokio_rustls (or the redis_smol
equivalents) to restore TLS. redis_connection_manager and redis_async_cache are
capability features depending only on redis/aio, so they are runtime-agnostic and must be
paired with a runtime feature; the connection manager is a per-cache .connection_manager(true)
opt-in rather than a feature that cfg-swapped every cache's connection type.disk_store feature is renamed redb_store. The wasm feature is removed (it gated
nothing; web-time provides wasm-compatible time types transparently), as are redis_ahash
and async_tokio_rt_multi_thread.async feature no longer implies tokio; it pulls only async-lock, and
cached::async_sync::{Mutex, RwLock, OnceCell} re-export from async-lock instead of
tokio::sync (OnceCell there has no const_new()). Async RedbCache runs blocking redb
work on the blocking crate's thread pool instead of tokio::spawn_blocking, and
RedbCacheError::BackgroundTaskFailed is removed. blocking is pulled by redb_store rather
than async, so redis-only and in-memory async builds do not pay for it.dep: syntax, so an optional dependency's name
is no longer silently usable as a feature; enable the named crate feature instead.rmp-serde) instead of JSON. Old 2.x JSON
entries are read transparently and rewritten as MessagePack on their next write. Redis TTLs
use PSETEX / PEXPIRE, so sub-second TTLs are honored to the millisecond (requires
Redis 2.6+).: -> %3A, % -> %25) and the key always has
three fields ({namespace}:{prefix}:{key}), so distinct triples always map to distinct keys;
an unescaped join previously let namespace="a:b", prefix="" collide with namespace="a", prefix="b". An empty prefix keeps its separator: ("ns", "", "k") encodes as ns::k. This
changes the on-wire key for any segment containing : or %; the value envelope's version
field does not cover key layout, so an old entry is not found after upgrading, is recomputed
and rewritten at the new key, and the old entry expires on its original TTL.RedisCache::connection_string() / AsyncRedisCache::connection_string() return a
ConnectionString newtype whose Display and Debug redact credentials; call .reveal()
for the raw URL.ahash feature enables ahash/runtime-rng on non-wasm targets, seeding hash maps from
the OS RNG instead of a compile-time seed (hash-flood resistance). wasm32 keeps the
compile-time seed. No source change required.cached::prelude re-exports the common traits plus the CacheMetrics struct for a single
glob import.UnboundCache, LruCache, TtlCache,
LruTtlCache, TtlSortedCache, ExpiringCache, and ExpiringLruCache gained a hasher type
parameter defaulted to DefaultHashBuilder and a .hasher(s) builder method, mirroring the
sharded stores. DefaultHashBuilder is re-exported from the crate root.Builder::new() on all 13 in-memory and sharded builders, matching the IO builders'
public constructors.CacheValue<V, M = ()>: the value-plus-metadata wrapper returned by the LRU-family order
methods, re-exported from the crate root. Deref<Target = V>, PartialEq<V> against bare
values, Display where V: Display, value() / into_value(), and expires_at() when
M = Option<Instant>. IntoValues::into_values() bulk-unwraps an iter_order() /
value_order() result back into a plain Vec<V>. The reverse comparison
bare_value == wrapped cannot be implemented: coherence forbids the blanket impl.retain(keep) across every in-memory store: UnboundCache, LruCache, TtlCache,
LruTtlCache, TtlSortedCache, ExpiringCache, ExpiringLruCache, and the six sharded
stores. Every removed entry fires on_evict; on the expiry-aware stores expired entries are
removed regardless of the predicate and every removal counts an eviction. The sharded form
locks one shard at a time (not atomic across shards), runs the predicate under the shard write
lock (so it must not re-enter the cache), fires on_evict after the lock is released, and
requires no K: Clone bound.ConcurrentCachePeek and ConcurrentCachePeekAsync: side-effect-free cache_peek /
async_cache_peek (plus peek / async_peek aliases) for concurrent stores, with no
recency promotion, no TTL refresh, no hit/miss metrics, and no lazy removal of expired
entries. Implemented by the six sharded stores, which also expose an inherent
peek(&self, &K) -> Option<V>. RedisCache, RedbCache, and AsyncRedisCache implement
neither: peek is an in-memory concept, and for an IO-backed store there is no client-side
state to skip while the operation remains a full round trip. Both traits are in the prelude.ConcurrentCachedAsyncExt, a blanket extension trait over ConcurrentCachedAsync with ten
async_-prefixed aliases (async_get, async_set, async_remove, async_remove_entry,
async_delete, async_contains, async_clear, async_reset, async_get_or_set_with,
async_try_get_or_set_with), mirroring ConcurrentCachedExt. In the prelude.ConcurrentCached::cache_try_get_or_set_with and its async counterpart (both provided):
fallible-init get-or-set returning Result<Result<V, E>, Self::Error>, store error outer,
closure error inner; nothing is stored on a closure Err.
ConcurrentCachedExt::try_get_or_set_with is the short alias. ConcurrentCached /
ConcurrentCachedAsync also gained defaulted cache_get_or_set_with (get-then-set,
non-atomic) and no-op-default cache_reset_metrics.ConcurrentCacheBase gained cache_hits / cache_misses /
cache_capacity / cache_evictions and a default metrics(), so a generic bound can read a
sharded store's metrics; CachedExt gained capacity / evictions / reset;
ConcurrentCachedExt gained len / is_empty / hits / misses / capacity /
evictions / clear / reset; CachedPeek::peek, CloneCached::peek_with_expiry_status,
and ConcurrentCloneCached::{get_with_expiry_status, peek_with_expiry_status} fill in the
remaining aliases.Cached::cache_contains (defaulted, get-based, overridden peek-based by the built-ins) and
inherent contains on the six sharded stores, giving contains both spellings on both trait
families. CachedExt::contains delegates to it, so contains no longer counts a hit/miss,
promotes recency, or refreshes TTL, and reports expired entries as absent.ExpiringLruCache::iter_order / key_order / value_order, completing LRU-family
introspection parity, and TtlSortedCache::capacity() -> Option<usize>.set_max_size(&self, usize) -> Option<usize> and try_set_max_size(&self, usize) -> Result<Option<usize>, SetMaxSizeError>.
Shrinking evicts LRU-excess entries per shard strictly by recency, fires on_evict, and
counts evictions; resize is not atomic across shards. LruCache, LruTtlCache, and
ExpiringLruCache gained the same pair (#180), and SetMaxSizeError (variants
ZeroMaxSize and CapacityOverflow) replaces the mix of BuildError and std::io::Error
the 2.x resize paths returned. CacheTtl::try_set_ttl is the matching strict TTL setter,
returning SetTtlError::ZeroTtl.per_shard_initial_capacity on the three unbounded sharded builders, the sharded counterpart
of initial_capacity.SerializeCached / SerializeCachedAsync with cache_set_ref / async_cache_set_ref,
implemented by RedisCache / AsyncRedisCache / RedbCache, letting serialize-backed stores
set an entry without taking ownership. #[concurrent_cached] calls the borrowed setter for
any store implementing them, avoiding a value clone per set (#196, #195).RedisCache / AsyncRedisCache implement cache_clear / async_cache_clear through a
namespace-scoped SCAN + batched DEL (O(n), scoped to the cache's prefix, not a server
flush), with glob metacharacters in the namespace/prefix escaped so they match literally
(#200). RedisCache and AsyncRedisCache also implement Clone; RedbCache does not.RedbCache::flush / async_flush force a durable commit, RedbCache::disk_path() returns
the backing file path, and RedbCache::async_remove_expired_entries runs the sweep on the
blocking thread pool so it is usable from async contexts.RedisCacheError / RedbCacheError and their build-error siblings expose
is_deserialization() -> bool, so callers can distinguish a codec failure from a storage or
network error without a full match. Debug is implemented for RedisCache,
AsyncRedisCache, and RedbCache, redacted to namespace/prefix/path/ttl/refresh.PartialEq / Eq for ExpiringCache and ExpiringLruCache, and PartialEq / Eq / Hash
for ConnectionString. NoEvict / HasEvict derive Clone, Copy, Debug, Default and
are documented at the crate root.Expires::expires_at(&self) -> Option<Instant> as a default method returning the value's
expiry instant when tracked. Advisory only: is_expired() remains the authoritative liveness
check, and existing impl Expires blocks get the default for free.force_refresh (a block expression over the arguments that bypasses the
cached value, #146), in_impl = true for methods inside impl blocks including self
receivers (#16, #140), companions_vis = "<vis>" to set the generated companions'
visibility, companions = false to suppress the {fn}_no_cache / {fn}_prime_cache
companions entirely, and ttl_millis (above). convert, create, force_refresh,
map_error, and cache_prefix_block accept unquoted Rust in addition to the quoted-string
form. map_error is optional on the disk and redis paths (the generated code uses
.map_err(Into::into)?, so E: From<RedbCacheError> / From<RedisCacheError>).
#[concurrent_cached] accepts result_fallback together with expires.#[cached] / #[concurrent_cached] accept reference arguments (&T,
Option<&T>) on the default-key path, deriving an owned key without a convert (#202,
#203); the crate root is resolved via proc-macro-crate, so a renamed or re-exported
cached dependency works (#157); macro-introduced bindings are hygienically named
__cached_*, so arguments named key, cache, or result no longer collide (#230,
#114); and a generic function without key + convert produces a clear error (#80).#[cached] / #[once] / #[concurrent_cached] on an
async fn without the async feature, a TTL attribute without time_stores, and
#[concurrent_cached(disk = true)] / (redis = true) without redb_store / a redis feature
all name the missing feature instead of surfacing errors from generated internals. A return
type that does not implement Clone produces exactly one error, spanned at the return type.#[doc(alias)] entries mapping the 2.x store names to their 3.0 types (SizedCache ->
LruCache, TimedCache -> TtlCache, TimedSizedCache -> LruTtlCache) for docs.rs search.bin/check-versions.sh
fails the release when a cached_proc_macro* dependency pin disagrees with that subcrate's
version, or when a stable cached would depend on a pre-release subcrate.sync_writes = "by_key" bucket selection seeds from a per-static RandomState instead of a
fixed-seed hasher, so an attacker who knows the key space cannot collapse the lock buckets to
force whole-cache serialization.cache_get path self-heal by default:
the entry is deleted and the call returns a miss so the cached function recomputes. Opt into
fail-closed behavior with .strict_deserialization(true).resolve_connection_string() returns a redacting
ConnectionString, the build path constructs sanitized synthetic errors (including the
r2d2 pool-build failure and the NotUnicode env-var value, which is the connection string
itself), and RedisCacheBuilder::connection_pool_connection_timeout bounds how long build
waits for a connection. The legacy-JSON backward read requires the exact version field value,
and client-side caching rejects a URL pinning RESP2 (which cannot deliver invalidation
messages, so accepting it would silently serve stale data).0700 and the database file
forced to 0600 on every open (not only at creation); a symlink at the resolved db path or
at a configured cache directory is rejected before opening; symlink and permission validation
runs for the XDG default candidates, not only the temp fallback; and a read-only or
group/world-writable candidate falls back to the temp directory.RedbCacheError::CacheDeserialization / RedisCacheError::CacheDeserialization render their
cached_value bytes as <N bytes redacted> in Debug, and are documented as potentially
sensitive.{fn}_prime_cache no longer deadlocks or blocks readers: it ran the function body while
holding the cache write lock, so a recursive prime re-locked the same static on the same
thread (parking_lot is non-reentrant) and any prime blocked every reader for the full
recompute. The body now runs before the lock is taken.#[cached(result_fallback = true)] no longer overwrites a newer cached value with a stale
one. The fallback was captured before the function body ran and written back unconditionally
on Err, so a slow failing call could clobber a value a concurrent call had refreshed, and on
a TTL store refresh its deadline. The fallback is now read under the same lock the write takes;
result_fallback rejects a non-disabled sync_writes, so no caller could serialize the
window themselves.TtlCache, LruTtlCache, and TtlSortedCache; several paths anchored before the factory, so
a factory slower than the TTL produced an already-stale entry. Refreshing an entry under an
overflowing TTL now clears the deadline, as a fresh insert already did.on_evict or counts an eviction
until the replacement factory succeeds; overwriting an expired entry fires on_evict and
counts uniformly across the timed and sharded stores; a panicking on_evict during capacity
eviction can no longer leave the cache over capacity; cache_clear_with_on_evict counts every
removed entry rather than degrading to a silent cache_clear without a callback; and
cache_remove samples expiry once, at removal, so a slow callback cannot turn a live entry
into a None return.retain / evict previously fired on_evict inside the selection scan
or removed entries eagerly while the user predicate ran, so a panicking predicate could remove
nothing while having already run cleanup callbacks, or silently drop every entry already
yielded. The sharded implementation records a Vec<bool> of decisions rather than cloned keys,
so no K: Clone bound is added.TtlSortedCache::set_and_get_mut no longer orphans a map row when the size trim it triggers
unwinds: the stamp was unlinked from the deadline index and re-inserted after the trim, so a
panic in between left the entry in the map but invisible to every index-driven sweep while
still counted by cache_size(). TtlSortedCache::set_with(..).evict() also performs the
expiry sweep when max_size is configured and the map is under the bound, where the opt-in
was previously discarded, and build reserves with try_reserve so a capacity-overflowing
max_size returns Err(BuildError) instead of aborting.RedisCache::cache_clear / async_cache_clear decode SCAN replies as bytes rather than
String. Redis keys are binary-safe, so a single non-UTF-8 key anywhere in the cache's scope
aborted the clear permanently: the offending key was never removed, so every retry failed
identically.RedbCache::cache_set no longer returns a displaced value that had already expired, and
RedbCacheBuilder::build() returns RedbCacheBuildError::Storage instead of panicking when
the backing file is damaged. A truncated tail is the ordinary result of a full disk or a
killed process, and the file is a disposable cache, so it must not take the application down.
(This cannot help under panic = "abort".)remove_expired_entries re-read and re-check inside the write transaction, and use a single
time snapshot for the scan and write passes; redb self-heal re-reads before deleting; and the
redis self-heal delete is conditional through a Lua script comparing stored bytes, so a
concurrent valid write racing the read is not discarded.RedbCache default-directory resolution self-heals a pre-existing cache directory created
with legacy permissions by an earlier version, which permanently failed the security
validation. The chmod only succeeds for the owner, so an attacker-owned or symlinked
directory still falls through to the next candidate.ShardedTtlCache,
ShardedLruTtlCache, ShardedExpiringCache, and ShardedExpiringLruCache previously
evaluated a displaced entry's expiry outside the lock or twice, so a value crossing the
threshold in that window fired on_evict without counting the eviction or produced a wrong
return value. deep_clone on the expiring sharded stores reads the hit/miss counters under
the shard read lock, so cloned metrics match cloned entries.LruCache::cache_reset uses a fallible allocation path (a grown max_size could request a
HashMap capacity past the allocation limit and panic), and internal LRU list pre-allocation
saturates instead of overflowing.#[once] generic-value-type guard compares whole idents, so
fn f<S: Into<String>>(..) -> String is no longer falsely rejected; a raw-identifier cache
name (e.g. r#type) builds a working static instead of panicking; attributes written
between the macro and the fn forward to every generated item, so #[cfg] gating stays in
lockstep; user lint attributes reach the generated *_prime_cache companion; and no generated
use places a name in a scope enclosing user code, so a user item named Cached or
CloneCached is no longer shadowed.key / convert / force_refresh values produce contextual errors explaining the
expected syntax instead of a bare syn "unexpected token".RedbCacheError, RedbCacheBuildError, RedisCacheError, and RedisCacheBuildError
Display output includes the underlying cause, which was previously reachable only through
Debug while the source type is documented as not public API.ConcurrentCacheTtl::refresh_on_hit reflects the configured flag: the concurrent stores
overrode only set_refresh_on_hit, so the getter always reported false through trait
dispatch.Cached for HashMap no longer requires S: Default, so HashMap<K, V, DefaultHashBuilder>
implements Cached on wasm.doc(cfg)) on the async_core-gated impls and on
AsyncRedisCacheBuilder::client_side_caching, which previously rendered as unconditionally
available.OnceLock) instead of probing it on every
construction. The LRU-family and expiry-aware stores sweep in one pass instead of collecting
keys first, the TTL stores sample the clock once per operation instead of once per entry
examined, and ExpiringCache is smaller per instance.get_or_set_with returning V directly, so the common case
needs no trait import or .unwrap().#[must_use] is applied across the pure-query trait methods (cache_size / len /
is_empty / metrics / hits / misses / ttl / refresh_on_hit / ...), the removal
methods on the concurrent traits, CacheEvict::evict / ConcurrentCacheEvict::evict,
CacheMetrics::hit_ratio, the order accessors, and the sharded builders. The short
remove / remove_entry aliases and the inherent sharded set / remove are deliberately
left un-annotated: on the inherent methods the attribute cannot fire on .unwrap() (which
consumes the value) and would fire on correct fire-and-forget calls.Return::set_was_cached is #[doc(hidden)] (macro plumbing); it remains pub and callable.KeyedCache moved under a #[doc(hidden)] pub mod __private, so it no longer appears as a
suggested import when a user references a removed legacy store name.hashbrown updated to 0.17 (internal). Dev-only: criterion 0.8, googletest 0.14.[lints] table, so a future-toolchain warning
firing in cached cannot break downstream builds, and specs/, local/, .cursorrules, and
Makefile are excluded from the published package.len / cache_size / iter / evict contract on lazy-eviction stores is documented in
one place: len returns the stored count without an expiry scan (so it may include expired
entries), iter omits expired entries from the view without removing them, and evict()
reclaims them and yields an accurate live count..unwrap() sharp edge.REDIS_VALUE_VERSION) and the redb
on-disk format (versioned file name, table name) are documented as stable for the 3.x series
on the store struct docs; changes bump the embedded version and are reserved for a major
release.Arc<T> return pattern for expensive-to-clone values is documented on the macros: the
cache stores the Arc, and hits clone only the pointer (#64).examples/resilience.rs covering sync_writes = "by_key",
result_fallback, and force_refresh, plus cache-invalidation (#21) and struct-method
(#236) examples.feat: bound trait Error types, add cache_contains , peek / reset aliases by @jaemk in #290
Error types, add cache_contains, peek/reset aliases by @jaemk in #290cache_contains, peek-based contains parity, remove inherent TtlSortedCache::set_ttl by @jaemk in #291CacheSetError, rename TtlSortedCache insert* to set*, wrap order-method values in CacheValue by @jaemk in #293peek, builder new(), ext alias parity, concurrent cache_try_get_or_set_with, retain on map stores by @jaemk in #294cache_contains implementor listing and docsrs gate, note Arc<T> return pattern, add peek recency test by @jaemk in #295Clone error for #[cached] return types, async_core docsrs badges, doc and spec corrections by @jaemk in #296TtlSortedCache::set_with builder, ConcurrentCachePeek, ConcurrentCachedExt::try_get_or_set_with by @jaemk in #297retain on TtlSortedCache and the six sharded stores by @jaemk in #298cache_set overwrite by @jaemk in #299max_size, make cache_contains dyn-callable by @jaemk in #300on_evict, clear Clone error for #[once], no-callback retain fast path by @jaemk in #301retain returns removed count, ConcurrentCachePeekAsync, MSRV 1.92 by @jaemk in #302Full Changelog: cached_proc_macro_types-v3.0.0-rc.8...v3.0.0-rc.10
docs: show expires = true with Result and Option returns in expires_per_key example by @jaemk in #269
CachedGetOrSetAsync by @jaemk in #276sync_writes_buckets, drop expiring-store struct bounds, derive ConnectionString eq, store format and doc fixes by @jaemk in #284set_max_size to sharded LRU stores, rename RedbCacheBuilder::disk_dir, add resilience example by @jaemk in #285ExpiringLruCache::retain, docs and spec fixes by @jaemk in #286SetMaxSizeError::ZeroSize to ZeroMaxSize, document redis/redb format stability, correct spec drift by @jaemk in #287Acquire load for sharded metrics().capacity, document concurrent set_max_size semantics, sync specs by @jaemk in #288Full Changelog: v2.0.2...v3.0.0-rc.8
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
> for a complete walkthrough of every breaking change (and an
Upgrading from 0.x? See the 1.0 migration guide for a complete walkthrough of every breaking change (and an agent-oriented version for automated tooling).
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →