feat: macros, stores/traits, redis TLS split, runtime decoupling, constructor and ttl consistency#272
Merged
Merged
Conversation
e7d16f6 to
eee9213
Compare
9ea2c64 to
9d1ea5d
Compare
…sistency Make constructors and the ttl attribute consistent across the public surface, on top of the redb DiskCache rewrite. Breaking and additive changes with doc and test follow-ups: Macros: - `ttl` attribute now takes a `Duration` expression (`ttl = "core::time::Duration::from_secs(60)"`); the old whole-seconds integer form (`ttl = 60`) is removed and emits a migration error pointing at `ttl_secs`/`ttl_millis` - `ttl_secs` attribute for whole-second TTLs and `ttl_millis` for sub-second TTLs; `ttl` / `ttl_secs` / `ttl_millis` are three-way mutually exclusive, and each is mutually exclusive with `expires` (#149) - `force_refresh` attribute to bypass the cache per call, on all three macros (`#[cached]` / `#[concurrent_cached]` / `#[once]`); on `#[once]` it overwrites the single shared value (no per-call key, so no key-exclusion caveat). Combined with `result_fallback`, a force-refreshed `Err` still serves the previously cached `Ok`, and capturing that fallback leaves no read side effects on the bypassed entry (no TTL renewal, recency update, or hit-counter change) on both `#[cached]` and `#[concurrent_cached]` (#146) - `in_impl` attribute to cache methods inside `impl` blocks; `self`-receiver methods require `in_impl` (a `convert` block alone cannot rescue them, since the cache static cannot live at `impl` scope) and emit a `{fn}_no_cache` cache-bypass sibling, hidden from rustdoc via `#[doc(hidden)]` (#16, #140) - reference arguments (`&T`, `Option<&T>`) form the default key without `convert` (#202, #203) - generated code resolves the crate path via `proc-macro-crate`, so a renamed or re-exported `cached` dependency works (#157) - macro-introduced bindings are hygienically named, so arguments named `key` / `cache` / `result` no longer collide with generated code (#230, #114) - clear compile error for generic functions used without `key` + `convert` (#80) - `refresh` is rejected alongside the other store-builder attributes when a `create` block is supplied, mirroring `#[concurrent_cached]` - the shared `force_refresh` guard builder is factored into one helper Traits and stores: - every in-memory and sharded store gains a `new()` that returns a ready-to-use cache (zero-config stores take no args; required-field stores take them positionally - `LruCache::new(max_size)`, `TtlCache::new(ttl)`, `LruTtlCache::new(max_size, ttl)`, the `Sharded*` variants, etc.), all `#[must_use]`. `RedbCache` / `RedisCache` / `AsyncRedisCache` drop `new()` (it returned a builder, not a cache); use `builder(...)`. `new()` now consistently returns a usable cache everywhere it exists - `new()` / `builder()` on each sharded `*Base` type are now defined only on the default-hasher specialization (`*Base<K, V, DefaultShardHasher>`, the named alias). They previously sat on the generic `*Base<K, V, H>` impl but always returned a `DefaultShardHasher` builder, so a `*Base::<_, _, CustomHasher>:: builder()` / `::new()` turbofish compiled yet silently dropped the custom hasher; that turbofish now fails to compile (E0599). A custom hasher is specified via `ShardedX::builder().hasher(h)`, which switches the builder's hasher type; alias-based construction and the `.hasher()` path are unchanged - every TTL builder (non-sharded, sharded, Redis, Redb) gains `ttl_secs` / `ttl_millis` convenience setters alongside `ttl(Duration)`; last-writer-wins - `get_or_set_with` / `try_get_or_set_with` (and the async variants) now return `&V` instead of `&mut V`, with new `*_mut` variants preserving the old behavior (#179) - new additive `SerializeCached` / `SerializeCachedAsync` traits (`cache_set_ref`) for serialize-backed stores; `#[concurrent_cached]` routes its set through an autoref shim that uses the borrowed setter for any store implementing the trait (built-in redis/disk or a custom `ty`/`create` store), avoiding a value clone (#196, #195) - `RedisCache` / `AsyncRedisCache` gain `cache_clear` / `async_cache_clear`, with `cache_reset` delegating to `cache_clear`; `RedisCacheBuilder` / `AsyncRedisCacheBuilder` `build()` reject an empty namespace+prefix scope (`EmptyScope`) so `cache_clear` cannot `SCAN MATCH *` the whole database (#200) - `RedbCacheBuilder::build()` validates `cache_name` as a filename component, returning `InvalidCacheName` for a path separator (`/` or `\`) or a path-traversal component (`.` / `..`) - `LruCache::set_max_size` / `try_set_max_size` for resizing a live cache, with matching methods on `LruTtlCache` and `ExpiringLruCache` (#180) - `ConcurrentCloneCached` gains a non-renewing `cache_peek_with_expiry_status` (used to capture a `result_fallback` stale value without read side effects) Redis: - TLS features split so rustls is selectable; `redis_tokio` / `redis_smol` no longer imply native-tls (add `*_native_tls` or `*_rustls`) (#231) Docs and process: - docs and examples now prefer the short alias method API (`get`/`set`/`remove`/`clear`/`len`/...) over the `cache_*`-prefixed forms, with a note (in the crate docs and on the `Cached` trait) on when to use the `cache_*` names - a collision with another in-scope trait's method. Both forms remain; no method was removed - document floats as the canonical `convert` case (#78); add cache-invalidation and struct-method examples (#21, #236), the latter demonstrating `in_impl` and noting the in_impl cache is not externally invalidatable and its shared-id staleness footgun - release workflow tags and creates a GitHub release for each published workspace crate on publish, via `bin/tag-release.sh` (root `cached` -> `vX.Y.Z`, subcrates -> `<crate-name>-vX.Y.Z`) (#245); it retries a missing release when the tag already exists on the remote; the CI tag-release git identity is scoped to `--local` - fix a doctest under `--no-default-features` (#260) - exclude internal dev tooling (.agents, .claude, .github, bin, docs/dev, AGENTS.md, CLAUDE.md) from the published crate via Cargo `exclude` - README is generated from src/lib.rs via cargo-readme (regenerated here); dev tooling: pr-review shards reviewers across model-sized randomized chunks; pr-cycle fans fix application across disjoint sub-agents - documentation accuracy pass: `ttl_millis` / `ttl_secs` are valid with `result_fallback` and (on the in-memory path) need `time_stores`; on `#[cached]` the "honored exactly" claim is scoped to the default in-memory store; Redis rounds `ttl_millis` up to the next whole second (500ms -> 1s, 1500ms -> 2s); `in_impl` emits a `#[doc(hidden)]` `{fn}_no_cache` sibling (all three macros); `#[concurrent_cached]` `force_refresh` documents the `result_fallback` interaction; the `#[concurrent_cached]` attribute table reaches parity with `#[cached]`; wasm is incompatible with `redis_connection_manager` / `redis_async_cache`; the async `V: Sync` clone-elision asymmetry and how a `Send + !Sync + !Clone` value surfaces at the generated set site; `CachedAsync` shared-ref default Send bounds; `Cached::cache_get_or_set_with` / `cache_try_get_or_set_with` flagged as provided defaults; migration-guide `&mut V` -> `&V` coercion caveat; redis `build()` lists `InvalidTtl` before `EmptyScope` to match validation order - macro-output polish: ASCII-only macro diagnostics; no dead `#[doc]` on the function-local `in_impl` cache static; documented rationale for `#[allow(dead_code)]` on the generated prime companion and for the `crate_path()` `::cached` fallback - elevate all compiler warnings to errors workspace-wide via the Cargo `[lints]` table (`[workspace.lints.rust] warnings = "deny"`, opted into by the `cached` and `cached_proc_macro` packages), so `cargo build`/`test`/`clippy` deny warnings rather than only the Makefile clippy target; gate `validate_ttl` to `any(time_stores, disk_store, redis_store)` and an unused test `AtomicUsize` import to `proc_macro` to keep `--no-default-features` warning-clean Tests: - positive macro coverage for all three ttl spellings (`ttl` Duration expr / `ttl_secs` / `ttl_millis`) across `#[cached]` / `#[once]` / `#[concurrent_cached]`, and compile-fail UI goldens for the three-way exclusivity and the `ttl = <int>` migration error - inline store coverage for every new `new()` constructor and the `ttl_secs` / `ttl_millis` builder setters (including override semantics) - compile-fail UI golden asserting `*Base::<_, _, CustomHasher>::{new,builder}()` no longer compiles (the custom-hasher `.hasher()` path stays covered by the existing sharded `custom_hasher` unit test) - async clone-elision clone-count assertions; `#[concurrent_cached]` `result_fallback` + `force_refresh` (including the no-read-side-effects bypass); `#[once]` `force_refresh`; `force_refresh` + `in_impl`; generic-rejection goldens; `_mut` trait coverage on `ExpiringCache` / `TtlSortedCache`; redis `cache_clear` / `cache_set_ref` and redb `cache_set_ref` round-trips; deferred attribute/store combinations (max_size+ttl_millis, in_impl+ttl_millis, ttl_millis+force_refresh, result_fallback+ttl_millis, async once in_impl) and store(0) counter resets; sharded peek does not renew TTL under refresh_on_hit See docs/migrations/2.0-to-unreleased.md and the CHANGELOG for migration details.
… sharded must_use, redis tests Docs: - CHANGELOG: stop claiming RedbCache/RedisCache/AsyncRedisCache ::new are retained (the type-level ::new were removed; only *Builder::new remain); mark ttl_millis as new this release, not pre-existing; result_fallback requires ttl/ttl_secs/ttl_millis. - migration guide: RedbCache::new -> builder in the cache-name example; ttl_millis marked new. - proc-macro docs: ttl now takes a Duration-expression string, document ttl_secs, fix 3-way exclusivity wording; once.rs comments name the generated <fn>_no_cache sibling (was __cached_inner). - README (via src/lib.rs): time_stores also required for #[once] TTL forms; result_fallback ttl wording. - AGENTS.md: CacheMetrics field entry_count (was size); _prime_cache not emitted for in_impl; document force_refresh/in_impl; store table notes ttl_millis / ttl=expr select the TTL stores. Code/tests: - redb: reject NUL byte in cache_name (InvalidCacheName) + test. - LruTtlCache::set_max_size: own the documented zero-size panic. - sharded ExpiringLru/LruTtl/Ttl builder(): add #[must_use]. - redis: fix AsyncRedisCacheBuilder::new doc; test namespace-only clear pattern. - tests: UnboundCache cache_try_get_or_set_with_mut Err path; redis set_ref_round_trip clears first for idempotency.
…rename next_major_* tests to v3_* Macro codegen: - cached/concurrent_cached: reject const-generic functions without `convert`, extending the existing type-generic guard (const params have the same monomorphic-static naming problem). Lifetime-only generics still compile. - cached: hoist the force_refresh predicate into a single binding on the `unsync_reads` + `SyncWriteMode::Default` path. It was previously expanded in both the optimistic read-lock block and the write-lock re-check, so a side-effecting predicate evaluated twice per call. Stores: - sharded ttl/lru_ttl: deflake the expiry timing tests (shorter TTLs, wider sleep-to-ttl ratio). Release infra: - tag-release.sh: fixed-string tab-anchored tag matching, deduped remote checks, corrected backfill-semantics comments. - release.yml/build.yml: corrected comments, bump actions/checkout to v6. Tests: - new UI goldens: const-generic-without-convert and unparseable-ttl-duration for the relevant macros. - new coverage: lifetime-only generics compile and cache; unsync_reads with and without force_refresh; force_refresh single-eval; in_impl `_no_cache` sibling bypass (given its own struct/counter to avoid a parallel-run race). - rename tests/next_major_* to tests/v3_* (the next major is 3.0).
…uble-eval; default-key Option<&mut T> - `#[cached(result_fallback, force_refresh)]`: an expired entry is now still served as the stale fallback when a bypassed recompute returns `Err`. The bypass path captured the fallback with `cache_peek`, which drops expired entries; it now uses a new non-renewing `CloneCached::cache_peek_with_expiry_status` that surfaces expired entries with no read side effects. Defaulted trait method (non-breaking), overridden in the single-owner TTL stores (`ttl`, `ttl_sorted`, `expiring`, `expiring_lru`, `lru_ttl`). - `#[once(sync_writes, force_refresh)]`: hoist the predicate into a single binding so it is evaluated once per call instead of twice on the write path, matching `#[cached]`. - default-key `Option<&mut T>`: use `as_deref()` so the non-`Copy` option is not moved before the generated `_no_cache` call. - docs: revert a stray `::new`-removal note out of the frozen 2.0.0 changelog section, name the `DiskCache` alias in the Unreleased breaking entry, document the unused force_refresh flag in the README, and list the NUL byte in the `InvalidCacheName` doc.
Docs and comments only, no behavior change: - result_fallback rustdoc enumerated only `ttl`/`ttl_millis` but the gate is `has_ttl`, which `ttl_secs` also satisfies; normalize the four sites in cached_proc_macro to `ttl`/`ttl_secs`/`ttl_millis`. - force_refresh note was self-contradictory (claimed the body never sees `refresh`, then told the user to silence its unused-variable warning); the generated body does receive `refresh`. Reworded in src/lib.rs. - Method-naming docs wrongly attributed `async_cache_*` to CachedAsync; only ConcurrentCachedAsync uses that spelling. CachedAsync uses the async_get_or_set_with family. - Example run-command headers named `redis_tokio`/`redis_smol` but the examples' required-features demand the `_native_tls` variants; copy-paste now works. - CHANGELOG: drop stale "(seconds)" on ttl, add ttl_secs to the ttl_millis exclusion set, fix broken `buildd` migration anchor. - AGENTS.md: scope `max_size` to #[cached]/#[concurrent_cached] (not #[once]). - Store doc nits: document LruTtlCache::iter_order, point lru.rs build() links at the BuildError variants, correct the redb cache_name traversal comment. - Fix a misleading peek-expiry test comment; ASCII-ify em-dashes in changed examples and one ui fixture. Regenerated README.md from src/lib.rs.
4ce955b to
1363073
Compare
Breaking: `redis_async_cache` no longer implies native-tls. It now pulls `redis_tokio` (TLS-agnostic) + `redis/cache-aio`; add `redis_tokio_native_tls` or `redis_tokio_rustls` alongside if TLS is required. This folds the feature into the same uniform rule as `redis_tokio`/`redis_smol` (issue #231) instead of leaving it as the lone TLS-bundling exception. Updated the example's required-features and run-command, the crate feature-list rustdoc (and the generated README), the CHANGELOG TLS-split entry, and migration section 8. The Makefile CI target `redis-async-cache-smol` (an invalid Tokio-runtime + smol-TLS combo once the feature went agnostic) becomes `redis-async-cache-rustls`, checking `redis_tokio_rustls,redis_async_cache` and adding the rustls CI coverage that was previously missing. Macro codegen nits: - Gate the unsync_reads (cached) and once force_refresh binding so no constant `let __cached_force_refreshing = if true { false } else { true }` is emitted when force_refresh is absent; the force_refresh-present path is unchanged. - Reorder the `expires`-vs-`ttl` mutual-exclusion checks ahead of ttl parsing in all three macros, so `expires = true` with a malformed `ttl` surfaces the exclusion error rather than a parse error. Test coverage: - Add the missing async owned-store cache-hit clone assertion in serialize_set_dispatch, mirroring the sync sibling. - Add a `concurrent_cached` refresh-in-create conflict trybuild fixture (parity with the `cached` one). - Add expires+malformed-ttl trybuild fixtures for all three macros to lock in the reorder (existing exclusive fixtures used a valid ttl and passed either way).
…:Error` to `TtlSortedCacheError` Breaking changes for the v3 release: - Rename the unbounded sharded store `ShardedCache` (and its `ShardedCacheBase`/ `ShardedCacheBuilder`) to `ShardedUnboundCache`. The old name read as the umbrella for the whole sharded family while it was only the unbounded variant; the new name is parallel with `ShardedLruCache`, `ShardedTtlCache`, etc. Clean break, no alias. - Rename `ttl_sorted::Error` to `TtlSortedCacheError` and drop the old public alias. Also remove the dead `From<ttl_sorted::Error> for std::io::Error` impl (unused; the store never surfaces errors through `io::Error`). Updates the `#[concurrent_cached]` codegen, re-exports, doc tables, examples, tests, benches, and trybuild fixtures.
…dant re-export Breaking and additive trait-surface changes for the v3 release: - Make `cache_peek_with_expiry_status` a required method on `CloneCached` and `ConcurrentCloneCached`. The old provided defaults returned a wrong/no-op result that silently broke `force_refresh` + `result_fallback` for external stores; every built-in store already overrides it, so only out-of-tree impls are affected. - Make `cache_clear` and `cache_reset` required on `ConcurrentCached` and `ConcurrentCachedAsync`. Their no-op `Ok(())` defaults silently did nothing; all built-in stores already override both. `cache_reset_metrics` keeps its default (a legitimate no-op for the metric-less redis/redb stores). - Add `CacheTtl::try_set_ttl`, a validated variant of `set_ttl` that returns the new `SetTtlError::ZeroTtl` instead of storing a zero ttl (which silently makes every inserted entry expire on insertion). Additive: provided default, no impl changes. Also correct the `set_ttl` docs, which wrongly claimed it panics on a zero ttl. - Add ergonomic `len`/`is_empty` aliases on `ConcurrentCached`/`ConcurrentCachedAsync`, mirroring the sync `Cached` trait. - Remove the redundant `#[doc(hidden)] pub use web_time;` re-export (the `pub use web_time as time;` alias is unaffected) and a redundant intra-doc link target.
…sh` a plain bool, improve macro parse errors Proc-macro surface changes for the v3 release: - Remove the `unbound` attribute from `#[cached]`. It was redundant: the default store (no `max_size`, `ttl`, or `expires`) is already an `UnboundCache`, so `#[cached(unbound)]` built an identical store to a bare `#[cached]`. The attribute is intercepted with a migration error pointing at the bare-`#[cached]` replacement rather than a generic "unknown field". Breaking: drop `unbound` from your attributes. - Collapse `#[concurrent_cached]`'s `refresh` from `Option<bool>` to `bool`, matching `#[cached]`. `refresh = false` is now the default and no longer conflicts with `expires` or a `create` block (only `refresh = true` does); the redis/disk codegen always emits `refresh_on_hit(refresh)`, which is a no-op for the false default. - Add context to the `key` and `convert` parse errors: a malformed `key`/`convert` now explains what the attribute expects with an example instead of surfacing a bare `syn` "unexpected token".
…d Debug/PartialEq, simplify redis features Store and Cargo changes for the v3 release. Breaking: - Remove the inherent `refresh_on_hit`/`set_refresh_on_hit` methods on `TtlCache` and `LruTtlCache`. They shadowed the `CacheTtl` trait methods, and the inherent setter returned `()` instead of the previous value. Bring `CacheTtl` into scope to call them; `set_refresh_on_hit` now returns the previous `bool`. The builder `refresh_on_hit` is unchanged. - Remove the `wasm` cargo feature. It gated nothing - `web-time` provides wasm-compatible time types transparently with no opt-in feature, so the flag was a no-op. Drop it from your feature list; nothing else is needed for wasm targets. Additive: - Implement `Debug` for `RedisCache`, `AsyncRedisCache`, and `RedbCache` (redacted: only namespace/prefix/path/ttl/refresh, never connection strings or credentials). - Implement `PartialEq`/`Eq` for `ExpiringCache` and `ExpiringLruCache` (equal when their stored entries are equal). - Add `#[must_use]` parity and spread the `with_hasher` doc alias across the sharded builders. Other: - Simplify `redis_connection_manager` to build on `redis_tokio` instead of re-listing its redis sub-features (resolved feature set unchanged). - Bump `cached_proc_macro_types` to edition 2024 / rust 1.89, matching the workspace. - Fix the `ConcurrentCached` trait doc example to implement the now-required `cache_clear` and `cache_reset`.
…lease Document the breaking and additive changes and reconcile the existing Unreleased entries they invalidated: - Add changelog entries for the `ShardedUnboundCache` / `TtlSortedCacheError` renames, the now-required `cache_clear`/`cache_reset`/`cache_peek_with_expiry_status`, the removed `unbound` attribute and inherent ttl refresh setters, the `refresh` bool, the removed `wasm` feature, and the additive `try_set_ttl`/`SetTtlError`, concurrent `len`/`is_empty`, `Debug`, and `PartialEq`/`Eq` impls. - Reconcile the changelog/migration notes that described `cache_clear`/`cache_reset` and `cache_peek_with_expiry_status` as no-op/non-meeting defaults: they are now required. - Add migration sections 15-21 with detection/action and update the VERIFY checklist. - Rename `ShardedCache` references in the migration guide and AGENTS.md store table. - Regenerate README.md from the updated lib.rs docs.
- Remove the stale `unbound` attribute from the `#[cached]` macro rustdoc; the attribute was removed this release, so the docs no longer advertise it. - Correct the `ExpiringLruCache` `PartialEq` doc: equality is membership-based and does not compare LRU recency order (the "same LRU order" claim was wrong). - Document that the sharded stores' inherent `len`/`is_empty` shadow the new `ConcurrentCached`/`ConcurrentCachedAsync` trait aliases, and how to call the trait methods via fully-qualified syntax; note the `is_empty` `Ok(Some(false))` case. - Warn on the concurrent `set_ttl` that a zero ttl is stored unchecked and that the validated `try_set_ttl` lives on the single-owner `CacheTtl` trait. - Note in the migration guide that the TTL stores intentionally omit `PartialEq`/`Eq`.
…or sources, add Clone
Self-contained additive changes:
- Validate the `name` attribute on `#[cached]`/`#[once]`/`#[concurrent_cached]` via
`syn::parse_str::<Ident>` and emit a spanned "`name` must be a valid Rust identifier"
error instead of panicking in `Ident::new` on a bad name.
- Reject the `#[cached]`-only sync attributes on the other macros with a clear
"`<attr>` is not supported on `#[<macro>]`" message: `sync_lock`/`unsync_reads` on
`#[once]` and `#[concurrent_cached]`, and `sync_writes_buckets` on `#[concurrent_cached]`.
`sync_writes`/`sync_writes_buckets` remain valid on `#[once]` (they drive its codegen).
- Wire `#[source]` on `RedisCacheBuildError::MissingConnectionString` and
`RedisCacheError::CacheDeserialization`/`CacheSerialization`, and switch their Display
from `{error:?}` to `{error}` so error chains are reachable and render cleanly.
- Implement `Clone` for `RedisCache` and `AsyncRedisCache` (Arc-backed pool / cloneable
connection); `RedbCache` stays non-Clone. Make `Clone` a supertrait of `ShardHasher`,
matching the de-facto bound on `deep_clone`/`copy_from`.
… `DiskCache`/`store()`, must_use
Coupled main-crate trait cluster:
- Replace `Box<dyn Error>` on `Cached::cache_try_set`/`try_set` with a concrete
`#[non_exhaustive] CacheSetError` (variant `TimeBounds`), re-exported from the crate
root. `TtlSortedCache` maps its expiry-overflow error onto it. Callers can now match.
- Add defaulted `cache_get_or_set_with`/`get_or_set_with` to `ConcurrentCached` and
`async_cache_get_or_set_with` to `ConcurrentCachedAsync` (get-then-set, documented
non-atomic), closing the central-primitive gap versus the single-owner traits.
- Default `set_refresh_on_hit` to `{ false }` on both concurrent traits (was required
boilerplate for every custom impl) and add a defaulted `refresh_on_hit` getter.
- Remove the `DiskCache`/`DiskCacheBuilder`/`DiskCacheError`/`DiskCacheBuildError`
aliases; use the `Redb*` names.
- Remove the `store()` accessors from `UnboundCache`/`TtlCache`/`LruTtlCache`/
`ExpiringLruCache`; they leaked the internal `TimedEntry` wrapper and pinned the
representation.
- Add `#[must_use]` to the pure-query trait methods (`cache_size`/`len`/`is_empty`/
`metrics`/`hits`/`misses`/`ttl`/...), to `cache_remove`/`cache_remove_entry`, and to
`ConcurrentCacheEvict::evict`. The short `remove`/`remove_entry` aliases stay
un-annotated for ergonomic for-effect removal.
Docs, toolchain, and feature-gate updates: - Raise `cached_proc_macro`'s rust-version to 1.89, matching the workspace. Collapse a now-lintable nested `if let` in the macro into a let-chain (the MSRV bump enables clippy's let-chain suggestion). - Document all batch items in the CHANGELOG: breaking (typed `cache_try_set`, `DiskCache` alias removal, `store()` removal, `ShardHasher: Clone`, must_use) and additive (concurrent `get_or_set_with`, `refresh_on_hit` getter / defaulted setter, redis `Clone`, `name` validation, attr rejection, error sources, `evict` must_use). - Add migration-guide sections 22-25 and the matching VERIFY entries. - Drop the stale `DiskCache` alias note from the disk example comment. - Regenerate README.md from the updated lib.rs docs.
…d errors, macro guards
Breaking (v3):
- builders: every store uses a no-arg `::builder()`; required fields move to setters validated in `build()` (`RedbCache::builder().name(..)`, `RedisCache`/`AsyncRedisCache` `.prefix(..).ttl(..)`)
- rename the four `CachedAsync` methods to `async_cache_*` (`ConcurrentCachedAsync` unchanged)
- remove `BuildError::InvalidTtl`; a zero ttl is now `InvalidValue { field: "ttl", .. }`; rename `RedisCacheBuildError`/`RedbCacheBuildError` `InvalidTtl` to `Build(BuildError)`
- sharded `set_ttl(0)` no longer panics
- reject `#[cached(refresh = true)]` without a ttl
Non-breaking:
- `#[once]` rejects the `#[cached]`-only attrs (`result_fallback`, `refresh`, `max_size`, `ty`, `create`, `key`, `convert`)
- add `#[must_use]` to `CacheEvict::evict` and the single-owner inherent `evict`
- emit a helpful error for a non-string `force_refresh`
- fix the `Duration` import under no default features
- update changelog, migration guide, AGENTS.md, README
Breaking (v3): a zero `Duration` passed to `set_ttl` now disables expiry, identical to `unset_ttl()`, uniformly across `ShardedTtlCache`, `ShardedLruTtlCache`, `TtlCache`, `LruTtlCache`, and `RedisCache`/`AsyncRedisCache`. Previously the sharded stores panicked and the others errored on the next write. - sharded stores: collapse the `ttl_set` bool and `ttl_nanos` into a single `AtomicU64` (0 = disabled), removing the prior torn-read window - single-owner stores: route liveness through an `entry_live` helper (zero ttl = always live) - redis: a disabled ttl writes keys without expiry (plain `SET`, no `EX`); refresh-on-hit leaves a pre-existing key TTL intact rather than persisting it - `build()` and `try_set_ttl(0)` still reject zero; disable via `unset_ttl`/`set_ttl(0)` - `TtlSortedCache` semantics unchanged (out of scope) - update set_ttl docs, changelog, migration guide, README; add single-owner, redis, and sharded zero-ttl coverage
… links - redb: `set_ttl(0)` now disables expiry (== `unset_ttl`) like the other stores, instead of expiring entries immediately - the zero-ttl=disabled work had missed the disk store. Sync and async impls, with a regression test. - document that `TtlSortedCache` cannot disable expiry (a zero ttl expires entries immediately there) and soften the `CacheTtl::set_ttl` contract doc accordingly - fix the crate-doc `AsyncRedisCache::builder` example to the no-arg builder form (it still used the removed positional arguments) - derive `Clone, PartialEq, Eq` on `CacheSetError` and `TtlSortedCacheError` for parity with `SetMaxSizeError`/`SetTtlError` - document the `MissingRequired` build error at the redis/redb `builder()` entry points, add a match snippet to the `*BuildError` types, and note the redis namespace vs prefix layout in the crate docs - repair broken (`Self::unset_ttl`) and redundant intra-doc links so `cargo doc` is clean
…iguity Breaking (v3): `ConcurrentCached` and `ConcurrentCachedAsync` each declared the same eight synchronous helpers (`cache_size`/`len`/`is_empty` and `ttl`/`set_ttl`/`unset_ttl`/ `refresh_on_hit`/`set_refresh_on_hit`), so any type implementing both (`RedbCache`, every sharded store) hit `error[E0034]: multiple applicable items in scope` when both traits were in scope - which the prelude does. The single-owner side avoids this because `Cached` is a shared core that async stores also implement; the concurrent side had no such base. - add `ConcurrentCacheBase` (associated `Error` + `cache_size`/`len`/`is_empty`) as a supertrait of both `ConcurrentCached` and `ConcurrentCachedAsync`; the helpers are now declared once - add `ConcurrentCacheTtl` (`ttl`/`set_ttl`/`unset_ttl`, a default `try_set_ttl` that rejects a zero `Duration` with `SetTtlError::ZeroTtl`, and the refresh getters), implemented only by TTL-capable concurrent stores; non-TTL stores (`ShardedUnbound`/`ShardedLru`/`ShardedExpiring*`) no longer expose `set_ttl` - mirrors the single-owner `Cached`/`CacheTtl`/`CachedAsync` split - export both new traits at the crate root and in `cached::prelude` - adds a validated concurrent `try_set_ttl` that the concurrent surface previously lacked
…etter truthful Breaking (v3): `refresh_on_hit` and `set_refresh_on_hit` are now required (no default bodies) on `CacheTtl` and `ConcurrentCacheTtl`. Custom impls of either trait must provide both. This fixes a latent bug the defaults hid: the concurrent stores (sharded TTL stores, `RedisCache`, `AsyncRedisCache`, `RedbCache`) overrode `set_refresh_on_hit` (writing the real flag) but not the `refresh_on_hit` getter, so through trait dispatch the getter returned a stale `false` even after `set_refresh_on_hit(true)`. Requiring both forces a truthful getter by construction; each store now reads its real refresh flag. `TtlSortedCache` declares its no-refresh stance explicitly (both return `false`). - flip the tests that pinned the buggy default-false getter to assert it reflects the setter - changelog + migration note for the required methods and the getter fix
…RedisCache` and `RedbCache`
…onal map_error, add companions_vis
bare `#[cached]` defaults to `sync_writes = "by_key"` instead of Disabled.
The cache static becomes `(lock, Vec<Arc<Mutex<...>>>)` in ByKey mode; existing
code that accesses the lock directly via `.read()`/`.write()` now requires `.0.read()`.
`result_fallback = true` without an explicit `sync_writes` silently selects Disabled
(since ByKey and result_fallback are incompatible). Explicit `sync_writes` + result_fallback
still errors. Updated all tests, examples, and UI golden files to reflect the new type.
`convert`, `create`, `force_refresh`, `map_error`, `cache_prefix_block` changed from
`Option<String>` to `Option<syn::Expr>`. darling 0.20.11 handles both unquoted (`convert =
{ ... }`) and legacy quoted (`convert = "{ ... }"`) forms transparently. Removed
`validate_force_refresh_is_string` and all call sites. Added `expr_to_block` helper in
`helpers.rs`. Updated `make_cache_key_type` and `parse_force_refresh_block` accordingly.
`map_error` is now optional on fallible `#[concurrent_cached]` paths (redis/disk/custom).
When absent, the macro generates `?` on the cache operation result, which requires the
function's error type to implement `From<StoreError>` via the standard `?` operator. When
present, the closure form `.map_err(closure)?` is still used.
added `companions_vis: Option<String>` to all three macros. `None` inherits the cached
function's visibility (current behavior). A non-empty string is parsed as `syn::Visibility`
and applied to all companion functions (`_no_cache`, `_prime_cache`, `__cached_inner`).
Tests added in `tests/v3_macros.rs`:
- `test_cached_by_key_default_deduplicates`: four concurrent threads for key=1 produce
exactly one body execution under the new ByKey default.
- `test_cached_sync_writes_false_double_compute`: `sync_writes = false` gives a bare-lock
static accessible via `.read()` directly.
- `test_cached_result_fallback_no_explicit_sync_writes_compiles`: result_fallback + bare
#[cached] compiles and caches correctly.
- `test_cached_unquoted_convert_compiles_and_caches`: unquoted `convert = { ... }` works.
- `test_cached_legacy_quoted_convert_compiles_and_caches`: legacy `convert = "{ ... }"` works.
- `test_cached_unquoted_force_refresh_compiles_and_works`: unquoted `force_refresh = { ... }`.
- `disk_no_map_error_tests::test_disk_concurrent_without_map_error_compiles_and_caches`:
disk path without `map_error` compiles when the error type implements `From<RedbCacheError>`.
- `companions_vis_tests::test_companions_vis_pub_crate_produces_pub_crate_companions`: pub fn
with `companions_vis = "pub(crate)"` has `pub(crate)` companion.
- `companions_vis_tests::test_companions_vis_default_inherits_fn_visibility`: default inherits.
UI tests added in `tests/ui/`:
- `cached_result_fallback_sync_writes_by_key`: explicit `sync_writes = "by_key"` + result_fallback errors.
- `cached_convert_malformed_unquoted`: malformed `convert = { let x = }` rejected by darling.
- `concurrent_cached_map_error_non_closure`: `map_error = 5` (non-closure) rejected with clear message.
…only Replace the insertion-time `instant: Instant` field in `TimedEntry` with `expires_at: Option<Instant>` computed at insert time. `None` means the entry never expires (TTL was disabled at insert). `Some(t)` means the entry is live while `Instant::now() < t`. This changes the semantics of `set_ttl`: it now only affects future inserts. Existing entries keep their original `expires_at` and are not retroactively re-evaluated. `refresh_on_hit` recomputes `expires_at = now + current_ttl` on each live hit; when the current TTL is zero it preserves the existing `expires_at` rather than setting None. Affected stores: `TtlCache`, `LruTtlCache`, `ShardedTtlCache`, `ShardedLruTtlCache`. Helper functions `entry_live(expires_at)` and `compute_expires_at(ttl, now)` are shared per-module. `cache_try_set` returns `CacheSetError::TimeBounds` on overflow. `TimedEntry` is now `pub(crate)` (previously `pub`). No re-export existed in `src/lib.rs`; this is purely a visibility tightening. Updated `tests/v3_single_owner_zero_ttl.rs`, `tests/v3_sharded_zero_ttl.rs`, and `tests/v3_traits.rs` to assert the new future-only semantics. Added `tests/v3_per_entry_expiry.rs` with 10 new tests gated `#[cfg(feature = "time_stores")]` covering `set_ttl` future-only contract and `refresh_on_hit` deadline extension on `TtlCache`, `LruTtlCache`, and `ShardedTtlCache`. Verified: `cargo build --all-features`, `cargo clippy --all-features --all-targets -- -D warnings`, `make tests/no-default tests/default tests/time-stores tests/proc-macro` all pass.
…arded stores
Each of the six sharded in-memory stores (ShardedUnboundCache, ShardedLruCache,
ShardedTtlCache, ShardedLruTtlCache, ShardedExpiringCache, ShardedExpiringLruCache)
now exposes inherent methods that return unwrapped values directly:
store.get(&k) -> Option<V>
store.set(k, v) -> Option<V>
store.remove(&k) -> Option<V>
store.remove_entry(&k) -> Option<(K, V)>
store.delete(&k) -> bool
store.reset()
These shadow the trait's short-alias methods (get/set/remove/remove_entry/delete)
which returned Result<_, Infallible>, eliminating the .expect("infallible") noise
at every call site. The cache_* prefixed trait methods and fully-qualified trait
path (ConcurrentCached::cache_get) remain the Result-returning form for generic code.
Call-site sweep:
- Updated examples/sharded.rs and examples/sharded_expiring.rs (6 .expect removed).
- Updated the deep_clone doctest in sharded/unbound.rs (3 .expect removed).
- Updated doctests in src/lib.rs ConcurrentCached::cache_remove_entry and
ConcurrentCloneCached (2 .expect removed).
- Updated tests/cached.rs concurrent_cached_trait_short_aliases_work to use
inherent methods (unwrapped) and trait path (Result) side by side.
Tests added per store (each asserting round-trip set/get, remove returns prior
value, delete bool, and clear/reset empties + resets metrics):
- sharded/unbound.rs: inherent_get_returns_option_not_result,
inherent_set_returns_previous_value, inherent_remove_returns_prior_value,
inherent_remove_entry_returns_key_and_value, inherent_delete_returns_bool,
inherent_reset_clears_and_resets_metrics,
inherent_and_trait_methods_coexist_via_fully_qualified_path
- sharded/lru.rs: same 7 tests
- sharded/ttl.rs: same 7 tests (gated by time_stores module feature)
- sharded/lru_ttl.rs: same 7 tests (gated by time_stores module feature)
- sharded/expiring.rs: inherent_get/set/remove/remove_entry/delete tests plus
inherent_get_returns_none_for_expired and trait coexistence
- sharded/expiring_lru.rs: same as expiring.rs
…c_sync uses async-lock
- async feature drops tokio dep; uses dep:async-lock + dep:blocking instead
- redb async ops use blocking::unblock instead of tokio::task::spawn_blocking
- remove RedbCacheError::BackgroundTaskFailed (blocking::unblock propagates panics directly)
- async_sync module re-exports async_lock::{Mutex, RwLock, OnceCell} instead of tokio::sync
- proc macro concurrent_cached: OnceCell::const_new() -> OnceCell::new() (async-lock API)
- remove async_tokio_rt_multi_thread feature; tokio moved to dev-dep with rt-multi-thread
- redis_smol* no longer pulls tokio through cached's async feature
- update Makefile tests/async, tests/disk-store, tests/redis-tokio to drop removed feature
- update all example required-features and doc comments referencing removed feature
- add futures::executor::block_on test to prove async RedbCache is runtime-agnostic
…untime decoupling, sync_writes default, unquoted attrs, companions_vis, per-entry TTL, Cached::Error, sharded inherent methods, TimedEntry pub(crate), LRU write-lock note)
…thout redundant braces
The unquoted-attr work parsed `create`/`cache_prefix_block` through `expr_to_block`
and emitted a block in value position (`Lock::new({ expr })` / `.prefix({ expr })`),
so a single-expression block tripped `unused_braces` under `-D warnings` and a bare
expression panicked the macro. Add `expr_value_tokens`, which unwraps a single-expression
block to its inner expression and emits bare expressions directly, keeping multi-statement
blocks intact.
…API, optional map_error - `struct_method`/`expires_per_key`: unquoted `convert` blocks; `struct_method` adds `companions_vis` - `kitchen_sink`: unquoted `create` expressions - `disk`: unquoted `map_error` closure plus a `From<RedbCacheError>` path with no `map_error` - `sharded`: correct the mislabeled `.expect` on the `load_record` Result
1363073 to
b99113c
Compare
Extends the Expires trait with a default-impl expires_at -> Option<Instant> so types that track a concrete deadline can expose it for observability without breaking existing impls. is_expired remains the authoritative liveness check.
… unify TtlSortedCacheError into CacheSetError; make set_ttl(0) mean never-expires in TtlSortedCache rename four default methods on the CachedAsync trait from get_async/set_async/ remove_async/clear_async to async_cache_get/async_cache_set/async_cache_remove/ async_cache_clear. The docs already advertised the new spelling; this makes the declarations match. No call sites existed in the codebase. delete TtlSortedCacheError (a duplicate of CacheSetError) and replace every use with super::CacheSetError. TtlSortedCache now shares the canonical error type with TtlCache and LruTtlCache. Removes TtlSortedCacheError from the public re-exports. change TtlSortedCache per-entry expiry from Instant to Option<Instant> where None = never expires. Stamped and Entry both get Option<Instant> expiry fields. The Stamped ordering is custom: None sorts GREATEST so never-expiring entries are the last candidates for size-limit eviction and are not swept by evict(). set_ttl(Duration::ZERO) and unset_ttl() now both disable expiry for future inserts, consistent with TtlCache / LruTtlCache. Entries inserted with a non-zero TTL are unaffected. Builder still requires a non-zero initial TTL. Tests added: async_cache_get_set_remove_clear_behavioral (lib.rs), ttl_sorted_cache_set_error_is_clone_eq, ttl_sorted_cache_try_set_returns_cache_set_error_on_overflow, set_ttl_zero_entries_never_expire, set_ttl_zero_only_affects_future_inserts, set_ttl_zero_never_expire_entries_evicted_last_under_size_pressure, unset_ttl_makes_future_inserts_never_expire, evict_does_not_remove_never_expiring_entries (ttl_sorted.rs). Existing v3_traits.rs tests updated to CacheSetError.
Breaking changes for the major release: - `disk_store` feature renamed to `redb_store` across Cargo.toml, src/lib.rs, src/stores/mod.rs, examples/disk.rs, examples/disk_async.rs, and all test files. - `redis_ahash` feature deleted entirely (no Rust cfg gates existed; only the Cargo.toml declaration and the doc bullet in src/lib.rs are removed).
…ache never-expires, and Expires::expires_at Independent coverage for the async_cache_* rename, the unified CacheSetError, TtlSortedCache zero-ttl never-expires semantics, and Expires::expires_at: - async_cache_get/set/remove/clear exercised on a real store (TtlSortedCache). - compile_fail doctests guard against reintroducing TtlSortedCacheError. - explicit Duration::ZERO insert stores expiry = None; retain_latest and max-size eviction keep never-expiring entries last. - get_or_set_with / try_get_or_set_with under zero ttl persist. - Expires::expires_at default vs override, and is_expired stays authoritative. - correct the stale zero-ttl assertions in tests/v3_single_owner_zero_ttl.rs.
…trait rename and feature changes Update the migration guide, CHANGELOG, README, and AGENTS for the async_cache_* rename, the CacheSetError unification, the TtlSortedCache zero-ttl change, and the redb_store/redis_ahash feature changes: - extend the CachedAsync naming docs to cover the renamed get_async/set_async/remove_async/clear_async shorthand methods. - fix the migration entries and VERIFY list, which previously described TtlSortedCache as still using TtlSortedCacheError and zero-ttl as immediate expiry (both now reversed). - document the TtlSortedCacheError removal, the disk_store -> redb_store rename, the redis_ahash removal, and Expires::expires_at. - rename disk_store -> redb_store in the Makefile test targets. - regenerate README from lib.rs.
b99113c to
a1ed7eb
Compare
Brings `BuildError` to parity with the sibling error enums `SetMaxSizeError`, `SetTtlError`, and `CacheSetError`, which already derive these. Its variants carry only `&'static str`, so the derive is sound. Callers can now compare and clone `build()` errors in tests. The `RedisCacheBuildError` and `RedbCacheBuildError` wrappers keep `Debug` only; their other variants wrap `redis::RedisError`, `r2d2::Error`, `redb::Error`, and `io::Error`, none of which implement these traits.
… redact connection string - serialize redis values with `rmp-serde` (MessagePack) instead of `serde_json`; `redis_store` now pulls `rmp-serde`. Existing redis entries are recomputed on miss. - set redis expiry with `PSETEX`/`PEXPIRE` so sub-second ttl is honored to the millisecond instead of rounded up to a whole second. - `RedisCache`/`AsyncRedisCache` `connection_string()` returns the redacted form; add `connection_string_unredacted()` for the raw url. - rename `RedbCacheBuildError::Connection` to `Storage`; convert the redb error enums to struct variants matching redis. serialize/deserialize variants on both backends carry `rmp_serde` errors, and `CacheDeserialization` carries the raw `cached_value` bytes.
…nce]` - emit a guiding error for `disk`, `redis`, and `map_error` on `#[cached]`/`#[once]` pointing to `#[concurrent_cached]`, instead of darling's generic unknown-field message. - generated `#[cached]` code calls the core `cache_set` method directly.
…ses to extension traits, expose sharded metrics via trait - add a hasher type parameter (`S = DefaultHashBuilder`) and a `.hasher()` builder method to `UnboundCache`, `LruCache`, `TtlCache`, `LruTtlCache`, `TtlSortedCache`, `ExpiringCache`, and `ExpiringLruCache`; export `DefaultHashBuilder`. - move the short method aliases (`get`/`set`/`remove`/`len`/...) off `Cached`/`ConcurrentCached` onto blanket `CachedExt`/`ConcurrentCachedExt` traits, re-exported from the prelude; the core traits keep only the `cache_`-prefixed methods. - add `cache_hits`/`cache_misses`/`cache_capacity`/`cache_evictions` and a default `metrics()` to `ConcurrentCacheBase` so sharded metrics are reachable through a trait bound. - document the `len`/`iter`/`evict` contract on lazy-eviction stores.
…t changes; add specs - reconcile the migration guide and changelog with the redis msgpack/millisecond-ttl/redaction changes, the struct-variant store errors, the `CachedExt`/`ConcurrentCachedExt` split, the non-sharded custom hasher, and the concurrent metric accessors. - regenerate the readme. - add a `specs/` directory documenting current and proposed 3.0 work, each item marked implemented, not implemented, or needs research.
…honor millisecond redis ttl - read legacy redis entries transparently: deserialize MessagePack first, then fall back to the pre-3.0 serde_json format identified by its `version` key. `serde_json` is re-added as a read-only fallback enabled by `redis_store`. - `RedisCache`/`AsyncRedisCache` `connection_string()` returns a `ConnectionString` whose `Display` and `Debug` redact; call `.reveal()` for the raw url. `connection_string_unredacted()` is removed. `ConnectionString` is re-exported from the crate root and `cached::stores`. - clamp a non-zero sub-millisecond ttl to 1ms so `PSETEX`/`PEXPIRE` never receive 0, and correct the docs that claimed redis rounds ttl up to whole seconds. - point the readme upgrade banner at the migration guide instead of listing breaking changes inline. - fix doc references to the extension traits: short aliases live on `CachedExt`/`ConcurrentCachedExt`, concurrent `len`/`is_empty` on `ConcurrentCacheBase`, and repair the `CachedExt::get_or_set_with` link.
- drop the migration entry that pointed at the removed `TtlSortedCacheError`; the unified type is `CacheSetError`. - document the redis backward-read fallback (pre-3.0 entries are read transparently, not recomputed) and the `ConnectionString`/`.reveal()` api. - note that `cache_reset` no longer restores preallocated capacity. - correct comments: the single generic `cache_reset` applies to all hashers, the sharded ttl expiry predicate, the `compute_expires_at` overflow-to-never-expires case, and the single-key refresh doctest.
…rrent metrics - add server-gated redis tests for reading pre-3.0 json entries, round-tripping a struct value through msgpack, and millisecond ttl precision via `PTTL`. - add tests threading a custom `BuildHasher` through `UnboundCache`/`LruCache`. - add tests asserting aggregated `cache_evictions`/`cache_hits`/`cache_misses`/`cache_capacity` through `ConcurrentCacheBase`. - update `connection_string()` call sites to `.reveal()`, fix the stale peek-status docstring, de-flake the per-entry-expiry timing tests, and relax the hit-path clone-count assertions.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Breaking and additive changes across the macros, stores, and traits for the next major release: constructor and
ttl-attribute consistency, async_writes = "by_key"default, async-runtime decoupling, trait/store ergonomics, redis serialization and connection-string changes, custom hashers on the in-memory stores, and the supporting docs and tests, on top of the redbDiskCacherewrite.See
docs/migrations/2.0-to-unreleased.mdand the CHANGELOG for full migration details.Breaking changes
ttlattribute now takes aDurationexpression (ttl = "core::time::Duration::from_secs(60)"). The old whole-seconds integer form (ttl = 60) is removed and emits a migration error pointing atttl_secs/ttl_millis.RedbCache/RedisCache/AsyncRedisCachedropnew()(it returned a builder, not a cache); usebuilder(...).get_or_set_with/try_get_or_set_with(and async variants) return&Vinstead of&mut V; new*_mutvariants preserve the old behavior (Unnecessary&mut Vwithget_or_set_withandtry_get_or_set_with(CachedAsync) #179).redis_tokio/redis_smolno longer imply native-tls (add*_native_tlsor*_rustls) (Rustls support for Redis #231).RedisCacheBuilder/AsyncRedisCacheBuilderbuild()reject an empty namespace+prefix scope (EmptyScope);RedbCacheBuilder::build()rejects an invalidcache_name(InvalidCacheName).#[cached]defaultssync_writes = "by_key": concurrent first-calls for the same key dedup through bucketed per-key locks, instead of the previous no-synchronization double-compute (which mirrored Pythonlru_cache). Opt out withsync_writes = false.#[once]/#[concurrent_cached]defaults unchanged.Cachedgains an associatedtype Error;cache_try_set/try_setreturnResult<_, Self::Error>. Built-ins useInfallible(non-fallible stores) andCacheSetError(TtlCache/LruTtlCache/TtlSortedCache); custom impls must declare it.CachedAsyncshorthand methodsget_async/set_async/remove_async/clear_asyncare renamed toasync_cache_get/async_cache_set/async_cache_remove/async_cache_clear, so everyCachedAsyncmethod uses theasync_cache_*namespace (ConcurrentCachedAsyncalready did).get/set/remove/clear/len/is_empty/ the shortget_or_set_withfamily, etc.) move off the coreCached/ConcurrentCachedtraits onto blanket extension traitsCachedExt/ConcurrentCachedExt(re-exported from the crate root and the prelude). The core traits keep only thecache_-prefixed methods, so a custom store implements a smaller surface. Callers usingcached::prelude::*need no change; others adduse cached::CachedExt;/use cached::ConcurrentCachedExt;, or use thecache_-prefixed form. Customimpl Cached/impl ConcurrentCachedblocks must drop any short-alias methods.ShardedCacherenamed toShardedUnboundCache(withShardedCacheBase/ShardedCacheBuilderrenamed to match); the old name only described the unbounded variant. No deprecated alias; rename at the call site.TtlSortedCacheErroris removed;TtlSortedCacheshares the unifiedCacheSetErrorwithTtlCache/LruTtlCache.set_ttlnow applies to future inserts only (it no longer re-dates existing entries), andset_ttl(0)/unset_ttl()disable expiry for future inserts only.TtlSortedCachenow treats a zerottlas never-expires for future inserts as well (per-entry expiry isOption<Instant>, ordered so never-expiring entries evict last); it previously meant immediate expiry.asyncfeature no longer pullstokio; smol / async-std async users no longer compile it. Theasync_tokio_rt_multi_threadfeature and theRedbCacheError::BackgroundTaskFailedvariant are removed. AsyncRedbCacheruns blocking redb work on theblockingcrate (runtime-agnostic), andcached::async_syncre-exportsasync-lockinstead oftokio::sync.disk_storecargo feature is renamed toredb_store, andredis_ahashis removed.cache_reset(and the concurrent / async variants) no longer preserves the preallocated backing capacity: it now doesclear()+shrink_to(initial_capacity), which the allocator may satisfy with a smaller allocation. Recreate the cache instead of resetting it to retain the allocation.TimedEntryis nowpub(crate)(it was an unused public type after thestore()accessors were removed).Redis store
rmp-serde) instead of JSON; theredis_storefeature pullsrmp-serde(and keepsserde_jsononly as a read-only fallback). Pre-3.0 JSON entries are read transparently: the store tries MessagePack first, then falls back toserde_jsonfor entries carrying aversionkey, serving the value without recompute. New writes use MessagePack; old entries are rewritten as MessagePack on their next write.RedisCacheError's serialize/deserialize variants carryrmp_serdeerror types instead ofserde_json::Error.PSETEX/PEXPIRE; sub-second TTLs are honored to the millisecond instead of rounded up to the next whole second. Whole-second TTLs are unchanged. Requires Redis 2.6+.RedisCache::connection_string()/AsyncRedisCache::connection_string()return aConnectionStringnewtype whoseDisplayandDebugboth redact credentials; call.reveal()for the raw URL.connection_string_unredacted()is removed.RedbCacheBuildError::Connectionis renamedStorage, the serialize/deserialize variants carry MessagePack error types, andCacheDeserializationgains acached_value: Vec<u8>field. Tuple patterns likeCacheSerialization(e)becomeCacheSerialization { source }.disk/redis/map_erroron#[cached]/#[once]now emit a guiding error pointing at#[concurrent_cached](they were never supported on the single-owner macros).Additive
new()returning a ready-to-use cache on every in-memory and sharded store, all#[must_use](LruCache::new(max_size),TtlCache::new(ttl),LruTtlCache::new(max_size, ttl), theSharded*variants, etc.).new()now consistently returns a usable cache everywhere it exists.S = DefaultHashBuildertype parameter and a.hasher(...)builder method onUnboundCache/LruCache/TtlCache/LruTtlCache/TtlSortedCache/ExpiringCache/ExpiringLruCache. The default keeps existing call sites source-compatible;DefaultHashBuilderis re-exported.cache_hits/cache_misses/cache_capacity/cache_evictions, plus a defaultmetrics()) onConcurrentCacheBase, so the sharded stores' aggregated metrics are reachable through a trait bound.ttl_secs(whole seconds) andttl_millis(sub-second) macro attributes;ttl/ttl_secs/ttl_millisare three-way mutually exclusive, and each is mutually exclusive withexpires(Feature Request: Floating-Point ttl #149).ttl_secs/ttl_millisconvenience setters on every TTL builder (non-sharded, sharded, Redis, Redb); last-writer-wins withttl(Duration).force_refresh(per-call cache bypass) on all three macros, withresult_fallbackinteraction and no read side effects on the bypassed entry (Feature Request: bool function argument that forces a cache refresh. #146).in_implto cache methods insideimplblocks;self-receiver methods require it and get a{fn}_no_cachesibling (macro: Not working inside impl blocks #16, Feature request: Skip self field to allow caching methods #140).&T,Option<&T>) form the default key withoutconvert(proc_macro: support args which are &T and Option<&T> #202, Support &T and Option<&T> in input #203).SerializeCached/SerializeCachedAsync(cache_set_ref) for serialize-backed stores;#[concurrent_cached]routes its set through them to avoid a value clone (Borrowed keys and values for IOCached::set_cache #196, Borrowed keys and values forIOCached::set_cache#195).RedisCache/AsyncRedisCachegaincache_clear/async_cache_clear(Addcache_clearoperation #200);LruCache::set_max_size/try_set_max_sizewith matchingLruTtlCache/ExpiringLruCachemethods (Feature: Ability to configure (or reconfigure) a SizedCachesizebased on runtime data #180);ConcurrentCloneCached::cache_peek_with_expiry_status.proc-macro-crate(renamed dependency works, Allow reexport #157); macro bindings are hygienic (keyargument name collision #230, Cannot use key as a function argument #114); clear error for generic functions withoutkey+convert(How to aproach generics ? #80).convert/create/force_refresh/map_error/cache_prefix_blockaccept bare Rust (convert = { format!("{a}") },map_error = |e| ...); the quoted-string forms still work.ty/keystay quoted (they hold types).map_erroroptional on disk / redis#[concurrent_cached]when the error type implementsFrom<store error>(the macro generates.map_err(Into::into)?).get/set/remove/remove_entry/delete/reseton the six sharded stores, returning unwrapped values (Option<V>,bool,()), removing the.expect("...infallible")ceremony. Thecache_*trait methods still returnResultfor generic code.companions_vismacro attribute sets the visibility of the generated{fn}_no_cache/{fn}_prime_cachecompanions independently of the cached fn.Expires::expires_at(&self) -> Option<Instant>default method (advisory observability;is_expired()stays the authoritative liveness check).Documentation
get/set/remove/clear/len/ ...) over thecache_*-prefixed forms, with a note on when to reach for thecache_*names (collision with another in-scope trait's method). The aliases now live onCachedExt/ConcurrentCachedExt(see Breaking changes); the prelude brings them in.specs/directory documenting the current state and proposed work for the 3.0 effort, each item marked implemented, not implemented, or needs research.convertguidance (Document how this should work on floats? #78), cache-invalidation and struct-method examples (Examples: cache invalidation #21, Add example of passing dyn trait instance #236), the redis backward-read / connection-string / millisecond-TTL behavior, and a broad accuracy pass across the macro/store docs and the migration guide.map_errorpath.Tests
#[cached]/#[once]/#[concurrent_cached], and compile-fail UI goldens for the three-way exclusivity, thettl = <int>migration error, and thedisk/redis/map_errorrejection on the single-owner macros.new()constructor and thettl_secs/ttl_millisbuilder setters (including override semantics).PTTL(server-gated); a customBuildHasherthreaded through the non-sharded stores; aggregatedcache_hits/cache_misses/cache_capacity/cache_evictionsthroughConcurrentCacheBase.force_refresh+result_fallback/in_impl,_muttrait coverage, redis/redbcache_set_refround-trips, additional attribute/store combinations, and sharded peek-does-not-renew-TTL.sync_writes = "by_key"default dedup, per-entry expiry (set_ttlfuture-only), theCached::Errorassociated type, the infallible sharded inherent methods, unquoted-attribute parsing (positive and UI goldens), theasync_cache_*rename and unifiedCacheSetError,TtlSortedCachenever-expires-on-zero-ttl,Expires::expires_at, and asyncRedbCacheunder a non-tokio executor (futures::executor::block_on).Verification
cargo build/clippy --all-features --all-targetsclean; fullmake cigreen (check + tests + examples), including redis-backed tests and examples against a local server and the trybuild UI goldens; doctests pass;cargo fmt --checkclean; README regenerated fromsrc/lib.rsand in sync.cargo treeconfirmsredis_smoland theasyncfeature no longer pulltokio(it remains a dev-dependency only).