cached 3.1.0

Generic cache implementations and simplified function memoization
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
# Changelog

## [Unreleased]

## [3.1.0] - 2026-08-24

### Added

- `CacheExpiry` and `ConcurrentCacheExpiry` traits, providing `cache_peek_expires_at()` /
  `peek_expires_at()`: a side-effect-free per-key read returning `(Option<V>, Option<Instant>)`
  instead of the `bool` `cache_peek_with_expiry_status` returns, so callers can implement a
  threshold-based refresh (refresh when the remaining TTL drops below N) directly against the
  deadline ([#91](https://github.com/jaemk/cached/issues/91)). Additive and non-breaking:
  standalone traits, not new required methods on `CloneCached` / `ConcurrentCloneCached`.
  Implemented by `TtlCache`, `LruTtlCache`, `TtlSortedCache`, `ExpiringCache`, `ExpiringLruCache`,
  `ShardedTtlCache`, `ShardedLruTtlCache`, `ShardedExpiringCache`, and `ShardedExpiringLruCache`.
  Both traits also provide a value-free `cache_expires_at()` / `expires_at()`, returning `(bool,
  Option<Instant>)` (presence, deadline) instead of cloning the value, for callers who only need
  the remaining time; it needs no `V: Clone` bound, since that bound moved off the impl blocks and
  onto the value-returning methods (`cache_peek_expires_at` / `peek_expires_at`).
  On `ExpiringCache`, `ExpiringLruCache`, `ShardedExpiringCache`, and `ShardedExpiringLruCache`
  the deadline comes from `Expires::expires_at()`, whose default body returns `None`: for an
  `Expires` impl that only implements `is_expired` (the crate's own documented recipe), the read
  reports `None` for both live and expired entries, and a threshold-refresh policy built on it
  silently never fires.

### Documentation

- New runnable example `examples/refresh_before_expiry.rs`: recompute an entry once its
  remaining ttl drops below a threshold, using `cache_peek_expires_at` (`peek_expires_at`) to
  read the deadline and `{fn}_prime_cache` to refresh outside the cache write lock, so the
  stored value is replaced while still live and no caller reads an expired entry. Companion to
  `examples/stale_while_revalidate.rs`, which handles the already-expired case. Covers the sync
  `#[cached]`, async `#[cached]`, and async `#[concurrent_cached]` static shapes. No API change.

- New runnable example `examples/stale_while_revalidate.rs`: serve an expired value
  immediately and refresh it off the critical path, composed from
  `cache_peek_with_expiry_status` (which returns an expired entry as
  `(Some(value), true)` without removing it) and `{fn}_prime_cache` (which runs the
  function body outside the cache write lock). Covers the sync `#[cached]`, async
  `#[cached]`, and async `#[concurrent_cached]` static shapes, and an in-flight guard
  that collapses concurrent refreshes for the same key, and a single-flight section
  adding `sync_writes = "by_key"` so the cold path deduplicates while stale reads still
  never block. No API change.

### Fixed

- `examples/stale_while_revalidate.rs` released its in-flight refresh claim only when the
  refresh returned normally, so a panicking or aborted refresh left the key claimed for the
  rest of the process and pinned it to a stale value with no way back. The claim is now
  released from `Drop`. Example code only; no API change.

## [3.0.0 / cached_proc_macro 3.0.0 / cached_proc_macro_types 3.0.0] - 2026-08-22

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](docs/migrations/2.0-to-3.0-human.md), with the mechanical breaking-change
list in the [agent-oriented guide](docs/migrations/2.0-to-3.0.md).

### Breaking Changes

#### Minimum supported Rust version

- MSRV raised from 1.85 to 1.92. `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`.

#### Store renames and the disk backend ([#237])

- `DiskCache` is renamed `RedbCache` (naming the backend, like `RedisCache`) and is backed by
  [`redb`](https://crates.io/crates/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.
- The six sharded stores are single types carrying a defaulted hasher parameter,
  `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.

#### Trait surface

- The short method aliases (`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.
- The concurrent trait surface is split. Introspection (`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.
- The 2.x `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).
- New required methods on custom impls: `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.

#### Store behavior

- `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.
- Every store counts an eviction before firing `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`.
- The default shard count of the LRU-bounded sharded stores (`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.
- TTL stores track per-entry expiry, so `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.
- Sharded `copy_from` returns `Result<_, BuildError>` instead of panicking on invalid
  configuration, and the `Eq` marker impls for `UnboundCache` and `LruCache` require `V: Eq`.
- The six sharded types expose inherent `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.

#### Builders

- `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.
- The redis TTL is optional: omitting `.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.
- Builder refresh naming is unified on `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 and metrics types

- Error enum variants dropped their redundant `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.
- The public store error enums (`RedbCacheError`, `RedbCacheBuildError`, `RedisCacheError`,
  `RedisCacheBuildError`, `BuildError`, `SetTtlError`, `SetMaxSizeError`) are `#[non_exhaustive]`,
  so external matches need a wildcard arm.
- The redis and redb error types no longer expose `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.

#### Macros

- The `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]).
- The deprecated `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).
- All three macros reject a `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.

#### Features and runtime

- Redis TLS is a separate axis ([#231]): `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.
- The `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`.
- The `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.
- Optional dependencies are gated with Cargo's `dep:` syntax, so an optional dependency's name
  is no longer silently usable as a feature; enable the named crate feature instead.
- Redis values are serialized with MessagePack (`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+).
- Redis key segments are percent-escaped (`:` -> `%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.
- The `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.

### Added

- `cached::prelude` re-exports the common traits plus the `CacheMetrics` struct for a single
  glob import.
- Custom hashers on the non-sharded in-memory stores: `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`.
- Metric and introspection parity: `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>`.
- Runtime capacity resizing on the sharded LRU stores: `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.
- Macro attributes: `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`.
- Macro ergonomics: `#[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]).
- Compile-time missing-feature guards: `#[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.
- The release workflow creates a git tag and GitHub release for each workspace crate that is
  newly published ([#245]), and refuses to publish a half-bumped workspace: `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.

### Security

- `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.
- Corrupt or undecodable cached values on the redis/redb `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)`.
- Redis credential handling is structural: `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).
- redb disk hardening on Unix: the cache directory is created `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.

### Fixed

- `{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.
- TTL expiry is anchored after the value factory resolves on every get-or-set path across
  `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.
- Eviction accounting: the try-path get-or-set no longer fires `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.
- Sweeps are panic-safe and two-phase (select, remove, count, then notify) across the in-memory
  and sharded stores. `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"`.)
- Read-then-write races closed on both IO stores: redb refresh-on-hit, expiry eviction, and
  `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.
- Sharded expiry evaluation happens once, under the shard write lock: `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.
- Macro correctness: the `#[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.
- Macro attribute errors span the offending attribute rather than the function name, and
  malformed `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.
- docs.rs feature annotations (`doc(cfg)`) on the `async_core`-gated impls and on
  `AsyncRedisCacheBuilder::client_side_caching`, which previously rendered as unconditionally
  available.

### Changed

- The in-memory and sharded stores are faster, with no contract change beyond the behavior
  changes listed above. The sharded stores resolve a read hit in one hash lookup instead of two,
  count evictions per shard rather than through a shared striped counter, and cache the host's
  CPU topology (sampled once per process in a `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.
- Sharded stores gained an inherent `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.
- The published crate manifests no longer carry a `[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.

### Documentation

- The `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.
- The sharded inherent-vs-trait return-shape split is documented on all six sharded store types,
  including the UFCS disambiguation and the `.unwrap()` sharp edge.
- The redis on-wire format (positional MessagePack array, `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.
- The `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]).
- New runnable example `examples/resilience.rs` covering `sync_writes = "by_key"`,
  `result_fallback`, and `force_refresh`, plus cache-invalidation ([#21]) and struct-method
  ([#236]) examples.

[#16]: https://github.com/jaemk/cached/issues/16
[#21]: https://github.com/jaemk/cached/issues/21
[#64]: https://github.com/jaemk/cached/issues/64
[#80]: https://github.com/jaemk/cached/issues/80
[#114]: https://github.com/jaemk/cached/issues/114
[#140]: https://github.com/jaemk/cached/issues/140
[#146]: https://github.com/jaemk/cached/issues/146
[#149]: https://github.com/jaemk/cached/issues/149
[#157]: https://github.com/jaemk/cached/issues/157
[#179]: https://github.com/jaemk/cached/issues/179
[#180]: https://github.com/jaemk/cached/issues/180
[#195]: https://github.com/jaemk/cached/issues/195
[#196]: https://github.com/jaemk/cached/issues/196
[#200]: https://github.com/jaemk/cached/issues/200
[#202]: https://github.com/jaemk/cached/issues/202
[#203]: https://github.com/jaemk/cached/issues/203
[#230]: https://github.com/jaemk/cached/issues/230
[#231]: https://github.com/jaemk/cached/issues/231
[#236]: https://github.com/jaemk/cached/pull/236
[#237]: https://github.com/jaemk/cached/issues/237
[#245]: https://github.com/jaemk/cached/issues/245
[rust-lang/rust#100013]: https://github.com/rust-lang/rust/issues/100013

## [2.0.2]
- Docs/tests only (no API change): document the `Expires` trait / `expires = true` as the idiomatic way to set a dynamic, per-entry TTL (a lifetime computed at call time rather than the uniform `ttl = N`), with a runnable example reference, and add a regression test for the runtime-argument-driven TTL case ([#246](https://github.com/jaemk/cached/issues/246)).

## [2.0.1]
- Fix `TtlSortedCacheBuilder`: an explicit `.capacity(n)` is now honored even when `.max_size(m)` is also set. Previously the `max_size`-derived `m + 1` preallocation ran first, and because `HashMap::reserve` never shrinks, a smaller `.capacity(n)` had no effect. The explicit capacity now takes precedence as the preallocation hint while `max_size` continues to bound entry count ([#266](https://github.com/jaemk/cached/issues/266)).

## [2.0.0 / cached_proc_macro 2.0.0]
> **Upgrading from 1.1?** See the [2.0 migration guide](docs/migrations/1.1-to-2.0-human.md).

### Breaking Changes

#### Minimum supported Rust version & edition
- **MSRV raised from 1.80 to 1.85, and the crates moved to the 2024 edition.** Edition 2024 was stabilized in Rust 1.85, so this is the new minimum a downstream project needs to build `cached`. Consumers already on Rust ≥ 1.85 are unaffected; those on 1.80–1.84 must update their toolchain. (The repository's `rust-toolchain.toml` pins the latest stable for local development and CI only — that pin does not propagate to consumers.)

#### Trait API changes
- `Cached::cache_remove_entry<Q>(&mut self, k: &Q) -> Option<(K, V)>`: new required method on the `Cached` trait that removes an entry and returns the stored key and value. Unlike `cache_remove`, this returns `Some` even when the deleted entry was already expired, making it possible to distinguish "key absent" from "key present but expired". Always fires the store's `on_evict` callback (if set).
- `ConcurrentCached::cache_remove_entry(&self, k: &K) -> Result<Option<(K, V)>, Self::Error>`: same semantics on the concurrent trait; implemented for all nine concurrent stores (six sharded plus `DiskCache` / `RedisCache` / `AsyncRedisCache`). The seven non-sharded stores (`UnboundCache`, `LruCache`, etc.) gain `cache_remove_entry` via the `Cached` trait above.
- `Cached::cache_delete<Q>(&mut self, k: &Q) -> bool`: new default method on `Cached` that deletes an entry without returning it; returns `true` if an entry was physically removed (including expired entries), `false` if the key was absent. Implemented via `cache_remove_entry`.
- `DiskCache` and `RedisCache` / `AsyncRedisCache` now require `K: Clone` (in addition to existing bounds) for their `ConcurrentCached` / `ConcurrentCachedAsync` impls, which is needed to return the stored key from `cache_remove_entry`.
- **`ConcurrentCached` / `ConcurrentCachedAsync` mutators now take `&self`** instead of `&mut self`: `set_refresh_on_hit`, `set_ttl`, and `unset_ttl` are defined with a shared receiver, matching the internally-synchronized `&self` contract of the rest of these traits (`cache_set`, `cache_remove`, …). This lets you flip the refresh flag or change the TTL on a shared store (e.g. one behind an `Arc` or a `static`) without exclusive access. Implementors must update their method signatures (`fn set_ttl(&self, …)` etc.); the bundled `DiskCache` / `RedisCache` / `AsyncRedisCache` stores do this via interior mutability (`parking_lot::Mutex` + `AtomicBool`). The single-owner `Cached` and `CacheTtl` traits are unaffected and keep their `&mut self` mutators.
- **`ConcurrentCached::cache_size` / `ConcurrentCachedAsync::cache_size`**: new method `fn cache_size(&self) -> Result<Option<usize>, Self::Error>` reporting the number of entries, with a default of `Ok(None)`. The default makes it non-breaking for existing external implementors and honest for stores that cannot cheaply produce a count: the six sharded stores override it to return `Ok(Some(len))`, while the external-store impls (`DiskCache`, `RedisCache`, `AsyncRedisCache`) keep the `Ok(None)` default because their backends (redb, Redis) expose no O(1) size. Sharded stores also retain their inherent `len()` / `is_empty()` for a non-`Result` count.

#### Macro attribute changes (`#[cached]`, `#[once]`, `#[concurrent_cached]`)
- **`result = true` removed from `#[cached]` and `#[once]`**: All `Result<T, E>` return types now automatically skip caching `Err` values. Remove `result = true` from all `#[cached]` and `#[once]` annotations — the behavior is now the default. To force-cache `Err` values, use the new `cache_err = true` opt-in.
- **`option = true` removed from `#[cached]` and `#[once]`**: All `Option<T>` return types now automatically skip caching `None` values. Remove `option = true` from all `#[cached]` and `#[once]` annotations — the behavior is now the default. To force-cache `None` values, use the new `cache_none = true` opt-in.
- **`#[concurrent_cached]` now supports `Option<T>` returns**: previously only `Result<T, E>` was accepted; `Option<T>` and plain `T: Clone` returns are now natively supported on the default in-memory sharded path. Note: `option = true` was never a recognized attribute on `#[concurrent_cached]` (it was silently ignored in 1.x); the new `cache_none = true` is the explicit opt-in to cache `None` values.
- **`#[cached]` / `#[once]` on `fn() -> Option<T>` without attributes**: previously cached `None` as-is; now skips caching `None`. Add `cache_none = true` to preserve the old behavior.
- **`#[cached]` / `#[once]` on `fn() -> Result<T,E>` without attributes**: previously cached the full `Result`; now skips caching `Err`. Add `cache_err = true` to preserve the old behavior.
- **`result_fallback = true` no longer requires `result = true`**: the explicit `result = true` companion is dropped; `result_fallback` now auto-detects `Result<T,E>` return types.
- **Custom-`ty` users storing `Option<T>` or `Result<T,E>` directly**: if your cache store type holds `Option<T>` or `Result<T,E>` as the value, you must now add `cache_none = true` or `cache_err = true` respectively so the macro uses the full wrapper type rather than extracting the inner `T`.
- **`map_error` on the default in-memory sharded path is now a compile error**: previously `map_error = "…"` was silently accepted and ignored when the store was the infallible default. If you had `map_error` on a `#[concurrent_cached]` that uses no `redis`/`disk`/`ty`/`create`, remove it. If you still need `map_error` (because you are switching to a `redis` or `disk` backend), add the corresponding backend attribute.
- **`result_fallback = true` and `with_cached_flag = true` are mutually exclusive** on `#[concurrent_cached]`: using both together is now a compile error. The combination was never valid — `result_fallback` stores the inner `Ok(T)` value while `with_cached_flag` wraps it in `Return<T>` — but the error was previously inscrutable. Remove one of the two attributes.
- **`cache_none = true` and `with_cached_flag = true` are mutually exclusive** on `#[cached]`, `#[once]`, and `#[concurrent_cached]`: using both together is now a compile error. The combination was never valid — `cache_none = true` stores `Option<T>` as the cached value type while `with_cached_flag = true` stores the inner `T` — but the error was previously a confusing downstream type mismatch. Remove one of the two attributes.

#### Store behavior changes
- **`cache_remove` on expiring stores** now returns `None` for expired-but-present entries. Previously `ExpiringCache`, `ExpiringLruCache`, and expiry-aware sharded stores returned `Some(value)` for an already-expired entry; now returns `None`. The entry is still removed and `on_evict` still fires.
- **`ConcurrentCached::cache_delete`** (and its `ConcurrentCachedAsync` equivalent) now returns `true` for expired-but-physically-present entries. In 1.x the method returned `false` for such entries. Use `cache_remove` if you need to distinguish a live removal from an expired one.
- **`LruCache::retain`** now fires `on_evict` and increments `cache_evictions()` for each removed entry, matching the semantics of `cache_remove`. Previously `retain` was side-effect-free. Internal TTL and expiring wrapper stores (`LruTtlCache`, `ExpiringLruCache`) use a new crate-internal `retain_silent` for their eviction sweeps, so those stores continue to count evictions exactly once.
- **`DiskCacheBuildError` gains a new `InvalidTtl(BuildError)` variant**: any exhaustive `match` on `DiskCacheBuildError` must add an arm for `InvalidTtl`. This variant is returned when a `DiskCacheBuilder` is given a zero-duration TTL.
- **`RedisCacheBuildError` gains a new `InvalidTtl(BuildError)` variant**: same as above for `RedisCacheBuildError`. Returned when a `RedisCacheBuilder` is given a zero-duration TTL.

#### Builder-only construction — `build()` returns `Result`, all store constructors removed
- **Every store is now built exactly one way: `X::builder().…setters….build()?`.** All direct, store-returning constructors are removed — `new`, `with_capacity`, `with_max_size`, `with_ttl`, `with_ttl_and_capacity`, `with_ttl_and_refresh`, `with_max_size_and_ttl`, `with_max_size_and_ttl_and_refresh`, every `try_with_*`, and the sharded `new` / `with_shards` / `with_max_size[_and_shards]` / `with_ttl[_and_shards]` / `with_max_size_and_ttl[_and_shards]` variants — across `UnboundCache`, `LruCache`, `TtlCache`, `LruTtlCache`, `TtlSortedCache`, `ExpiringCache`, `ExpiringLruCache`, and all six sharded stores. (`DiskCache` / `RedisCache` / `AsyncRedisCache` are unchanged: their `new(...)` / `builder(...)` already return a builder.) This removes the second, panic-prone construction path that duplicated the builder.
- **`Builder::build` now returns `Result<Store, BuildError>` for every in-memory and sharded store.** It previously returned the store directly and panicked on invalid configuration. Add `?` or `.unwrap()`. (Disk/Redis `build()` already returned `Result`; unchanged.)
- **`try_build()` is removed from all builders.** Now that `build()` is the single fallible constructor the alias is redundant — replace every `.try_build()` with `.build()`.
- **`TtlSortedCacheBuilder` gains `.capacity(n)`** — the preallocation hint formerly supplied via `TtlSortedCache::with_ttl_and_capacity`. It is distinct from `.max_size(n)`, which is the eviction bound.
- **Zero TTL is now always rejected.** Because every store is built through its (validating) builder, a zero `Duration` yields `BuildError::InvalidTtl`. The previously-permissive direct constructors (e.g. `TtlCache::with_ttl(Duration::ZERO)`) that accepted a zero TTL no longer exist.

#### `size` → `max_size` naming (builder setter, macro attribute, runtime setters)
- Builder setter `.size(n)` → `.max_size(n)` (LRU-family stores and `TtlSortedCache`). The sharded builders' per-shard cap setter is `per_shard_max_size`.
- The `#[cached]` / `#[concurrent_cached]` **macro attribute `size = N` → `max_size = N`**. The old `size = N` spelling keeps working as a **deprecated alias** that emits a deprecation warning (anchored at the `size` token). Setting both on one annotation is a compile error. See "New macro attributes" under Added below.
- **`TtlSortedCache` runtime max-size setters**: `size_limit(n)` → `set_max_size(n)` and `try_size_limit(n)` → `try_set_max_size(n)` (matching the `set_ttl` runtime-mutator convention). The error type also changed: `try_set_max_size` now returns `Result<Option<usize>, cached::SetMaxSizeError>` instead of `std::io::Result<Option<usize>>`; if you propagate the error with `?` into an `io::Error` context, update the enclosing function's error type or convert explicitly.

### Added

#### New macro attributes
- `max_size = N` attribute for `#[cached]` and `#[concurrent_cached]`: the preferred spelling of the LRU-bound attribute, mirroring the renamed `max_size` builder setter. The original `size = N` attribute continues to work as a **deprecated alias** — using it emits a deprecation warning (anchored at the `size` token) steering you to `max_size`. Specifying both `size` and `max_size` on the same annotation is a compile error.
- `cache_err = true` attribute for `#[cached]`, `#[once]`, and `#[concurrent_cached]`: opt-in to also cache `Err` values from `Result<T, E>` returns (requires a `Result<T, E>` return type; mutually exclusive with `result_fallback`).
- `cache_none = true` attribute for `#[cached]`, `#[once]`, and `#[concurrent_cached]`: opt-in to also cache `None` values from `Option<T>` returns (requires an `Option<T>` return type).
- `result_fallback = true` support for `#[concurrent_cached]`: on an `Err` return, the last cached `Ok` value for the same key is returned instead. The stale value is kept in the primary cache slot (via `ConcurrentCloneCached::cache_get_with_expiry_status`) and re-cached with a fresh TTL window on `Err`; no separate fallback store is created. Requires a TTL (`ttl`/`ttl_secs`/`ttl_millis`) (a compile error is emitted otherwise). Restricted to the default in-memory sharded path (not redis/disk). Mutually exclusive with `cache_err` and `with_cached_flag`.

#### New sharded in-memory cache stores
- Add six fully-concurrent, sharded in-memory cache stores: `ShardedCache<K,V>` (unbounded), `ShardedLruCache<K,V>` (LRU), `ShardedTtlCache<K,V>` (TTL, requires `time_stores`), `ShardedLruTtlCache<K,V>` (LRU + TTL, requires `time_stores`), `ShardedExpiringCache<K,V>` (per-value expiry, unbounded), and `ShardedExpiringLruCache<K,V>` (per-value expiry, LRU-bounded). All six wrap an `Arc` (cheap clone, `Send + Sync`), use power-of-two per-shard `parking_lot::RwLock`s with cache-line-padded shard structs to eliminate false sharing, and support builder APIs with `on_evict` callbacks, `copy_from` for live resharding, and `metrics()` / `shard_sizes()` for observability. Shard routing uses the `ShardHasher<K>` trait (default: `DefaultShardHasher` backed by ahash) as a zero-overhead type parameter, allowing custom partition logic without runtime overhead.
- `#[concurrent_cached]` now defaults to an in-memory sharded store when `redis = true` and `disk = true` are both absent and no custom `ty`/`create` is provided. Macro attributes `max_size = N`, `ttl = T`, `shards = S`, and `expires = true` select the matching variant. `map_error` must not be specified on this path — the stores are `Infallible` and have no errors to map (supply `redis = true`, `disk = true`, or a custom `ty`/`create` to use a fallible store).
- `#[concurrent_cached]` on the default in-memory sharded stores now accepts plain return types — any `T: Clone`, `Option<T>`, or `Result<T, E>`. `redis`, `disk`, and custom `ty`/`create` stores still require `Result<T, E>`.
- Add `expires = true` attribute support to `#[concurrent_cached]` macro to automatically select `ShardedExpiringCache` (unbounded) or `ShardedExpiringLruCache` (LRU-bounded when `max_size` is also set).
- `ShardedExpiringCache` and `ShardedExpiringLruCache` require cached values to implement the `Expires` trait; `copy_from` skips entries already reporting `is_expired() == true`. Both expose `deep_clone` for snapshot copies.

#### Other additions
- Add `cache_clear_with_on_evict()` to all six sharded stores (`ShardedCache`, `ShardedLruCache`, `ShardedTtlCache`, `ShardedLruTtlCache`, `ShardedExpiringCache`, `ShardedExpiringLruCache`): fires the `on_evict` callback for every removed entry when a callback is configured, and (where applicable) increments the evictions counter (`ShardedCache` is unbounded and has no evictions counter). The plain `clear()` inherent method remains fast and side-effect-free; `cache_clear_with_on_evict()` is the opt-in alternative.
- Add `cache_clear_with_on_evict()` to all seven non-sharded stores (`UnboundCache`, `LruCache`, `TtlCache`, `LruTtlCache`, `ExpiringCache`, `ExpiringLruCache`, `TtlSortedCache`): fires the `on_evict` callback for every removed entry and (where applicable) increments the evictions counter. The plain `cache_clear()` method remains fast and side-effect-free; `cache_clear_with_on_evict()` is the opt-in alternative.
- Add `StripedCounter` — a 16-slot cache-line-padded atomic counter — for hit/miss metrics on `UnboundCache` and `TtlSortedCache` to reduce false sharing under concurrent `cache_get_read`. All other stores continue to use plain `AtomicU64`.
- Add `ConcurrentCloneCached<K, V>` trait: concurrent analogue of `CloneCached` for the four expiry-capable sharded stores (`ShardedTtlCache`, `ShardedLruTtlCache`, `ShardedExpiringCache`, `ShardedExpiringLruCache`). Provides `cache_get_with_expiry_status(&self, key: &K) -> (Option<V>, bool)` — returns the value without removing expired entries, enabling `result_fallback` to fall back to stale values in-place. Takes `&self` (not `&mut self`) since sharded stores are internally synchronized.
- Add API consistency aliases: `Cached::{get,set,remove,remove_entry,delete}` and `ConcurrentCached::{get,set,remove,remove_entry,delete}` delegate to the existing `cache_*` methods (the sync `Cached` trait gains `remove_entry` / `delete` to match `ConcurrentCached`); both the sharded and non-sharded TTL builders expose `.refresh_on_hit(...)` as the primary setter with `.refresh(...)` retained as an alias; `DiskCache`, `RedisCache`, and `AsyncRedisCache` expose `::builder(...)` aliases (alongside their existing `::new(...)` builder entry points). Note: `DiskCache::new(...)` / `RedisCache::new(...)` / `AsyncRedisCache::new(...)` are **builder** entry points -- they return a builder, not a ready-to-use store -- and are intentionally retained; only the in-memory and sharded store constructors that returned stores directly were removed.
- Add an inherent `capacity()` getter to `LruCache`, `LruTtlCache`, and `ExpiringLruCache` — and to their sharded counterparts `ShardedLruCache`, `ShardedLruTtlCache`, and `ShardedExpiringLruCache` — that returns the configured max-entry bound (distinct from `cache_size()`, which returns the current live entry count).
- Add `BuildError::InvalidTtl { ttl }` variant for a single consistently-worded zero-TTL rejection path across all builders.
- Document on `ConcurrentCachedAsync` that `get`/`set`/`remove`/`delete` short aliases are intentionally absent to avoid worsening method-resolution ambiguity.

### Fixed
- Unify zero-TTL validation across all TTL-capable store builders: `TtlCache`, `LruTtlCache`, `TtlSortedCache`, `ShardedTtlCache`, `ShardedLruTtlCache`, `DiskCache`, `RedisCache`, and `AsyncRedisCache` builders now all call the shared `validate_ttl` helper and return `BuildError::InvalidTtl { ttl }`. With construction now builder-only, a zero TTL is uniformly rejected at build time (there is no longer a permissive direct-constructor path).
- Make the generated `#[concurrent_cached]` in-memory `Infallible` error shim map into the function's declared `Result<_, E>` error type, reject invalid store-selection attributes, and use UFCS for generated `ConcurrentCached` calls so sync functions compile even when both concurrent traits are in scope.
- Implement `CacheEvict` for `ShardedTtlCacheBase` and `ShardedLruTtlCacheBase`, make sharded builders return `BuildError` instead of panicking on capacity/shard overflows, avoid unnecessary `'static` bounds when building `ShardedLruTtlCache` without `on_evict`, optimize `ShardedTtlCacheBase` hits under `refresh_on_hit` by bypassing read-locks, and correct the sharded LRU capacity documentation.
- Fix timed-store eviction sweeps to use the crate's configured `Instant` type.
- Optimize `TtlSortedCache::cache_get` and `cache_get_mut` live hits to use a single hash-map lookup.
- Unify `cache_remove` semantics: removing any present entry now fires the store's `on_evict` callback (if set) and increments `evictions`.
- Tighten `#[concurrent_cached]` return-type classification so generic plain return types like `HashMap<K, V>` are not mistaken for `Result` aliases.
- Tighten `Result`-return detection in all three macros to require the exact identifier `Result` rather than matching any identifier that ends with `"Result"`. Type aliases such as `type MyResult<T> = Result<T, E>` are now treated as plain values (their `Err` variant is cached). Only the literal `Result<T, E>` and its fully-qualified forms (e.g. `std::result::Result<T, E>`) continue to trigger skip-on-`Err` / `result_fallback` semantics. This aligns with the existing `Option`-detection behavior and makes the macro surface consistent.
- Pass the stored key (via `remove_entry`) rather than the lookup key to `on_evict` in `ShardedTtlCache::cache_remove` and `ShardedExpiringCache::cache_get` / `cache_remove`.
- `#[concurrent_cached]` now rejects `map_error` on the default in-memory sharded path with a compile error — the stores are `Infallible` and accepting `map_error` while silently ignoring it was misleading. Previously `map_error` on this path was accepted and the infallible path emitted `.expect(…)` regardless.
- Remove redundant `.clone()` on the `#[concurrent_cached]` cache-hit return path for all three return-type variants.
- Fix `#[concurrent_cached(with_cached_flag = true)]` on the default in-memory path for plain `cached::Return<T>` returns.
- Extend `build()` panic messages on all sharded stores to include the underlying `BuildError` detail.
- Fix `ShardedLruTtlCacheBase::evict()` to remove expired inner entries without calling `cache_remove`, preventing double-counting of evictions and double-firing of `on_evict`.
- Fix `Cached::cache_delete` (now on `Cached` via `cache_remove_entry`) correctly returns `true` for entries that were present but already expired; previously `cache_delete` on `ConcurrentCached` returned `false` for expired entries.

## [1.1.0 / cached_proc_macro 1.1.0]

### Added
- Add `ExpiringCache` (and `ExpiringCacheBuilder`) as a size-unbounded store where each value implements the `Expires` trait and determines its own expiration.
- Add `expires = true` attribute to the `#[cached]` procedural macro: automatically selects `ExpiringCache` (unbounded) or `ExpiringLruCache` (LRU-bounded when `size` is also set), so the return type controls its own expiry via `Expires`. Compatible with `result`, `option`, `result_fallback`, `sync_writes`, `key`/`convert`, and `size`. Mutually exclusive with `ttl`, `ty`, `create`, `with_cached_flag`, `unsync_reads`, `refresh`, and `unbound`.
- Add support for the `expires = true` attribute in the `#[once]` procedural macro to allow single-value functions to utilize value-defined expiration (`Expires` trait).
- Add comprehensive unit tests in `src/stores/expiring_lru.rs` covering the `Expires` trait and `ExpiringLruCache`'s `CachedIter::iter` expired-filtering, `Clone`, `std::fmt::Debug`, `cache_remove`, and `cache_clear`.
- Implement `std::fmt::Debug` and `Clone` for `TtlSortedCache` (and its internal `Entry` type) and `ExpiringCache` to ensure full `Debug`/`Clone` trait parity across all 7 core in-memory store types.
- Add robust unit tests across all remaining core cache stores (`UnboundCache`, `LruCache`, `TtlCache`, `LruTtlCache`, `TtlSortedCache`) verifying `Debug` and `Clone` trait behaviors; `UnboundCache` and `LruCache` also verify `PartialEq` and `Eq`.
- Add comprehensive validation unit tests for each store builder's fallible `try_build()` methods (asserting expected `BuildError` outcomes for invalid capacities, sizes, or missing required attributes like `ttl`).
- Add unit tests validating the `std::fmt::Display` representation for all `BuildError` variants in `src/stores/mod.rs`.
- Add standardized micro-benchmarks (`benches/cache_benches.rs`) for cache hits across all 7 core in-memory stores (`UnboundCache`, `LruCache`, `TtlCache`, `LruTtlCache`, `ExpiringLruCache`, `ExpiringCache`, `TtlSortedCache`), cache misses & inserts, eviction capacity overhead, and `RwLock` lock-synchronization (with and without `CachedRead::cache_get_read` unsynchronized reads).
- Add new `bench` target to the `Makefile` to run the benchmark suite.
- Add standard, runnable example `examples/expires_per_key.rs` demonstrating how to use the `Expires` trait with `ExpiringLruCache` and `ExpiringCache` for per-value expiration, including keyed caching via `#[cached(expires = true)]` and single-value caching via `#[once(expires = true)]`.
- Add detailed library-level documentation and quickstart example for `Expires`, `ExpiringCache`, and `ExpiringLruCache` to `src/lib.rs` (automatically synced to `README.md`).

## [1.0.0 / cached_proc_macro 1.0.0 / cached_proc_macro_types 1.0.0]
> **Upgrading from 0.x?** See the [1.0 migration guide](docs/migrations/0.x-to-1.0-human.md)
> for a complete walkthrough of every breaking change (and an
> [agent-oriented version](docs/migrations/0.x-to-1.0.md) for automated tooling).
## Added
- Add comprehensive async integration tests in `tests/cached.rs` for `CachedAsync` methods on `TtlCache`, `LruTtlCache`, `TtlSortedCache`, `ExpiringLruCache`, and `UnboundCache` to assert correct `on_evict` invocation on expired lookups.
- Add `make help` and `make check/help` targets for documenting and validating
  supported Makefile commands.
- Add fallible `try_build` methods to `TtlCacheBuilder` and `ExpiringLruCacheBuilder`.
- Re-export `TtlSortedCacheError` at the crate root (and via `cached::stores`) so users can
  name and match on the error returned by `TtlSortedCache::cache_try_set`.
- `ExpiringLruCache::store()` accessor (mirroring `LruTtlCache::store()`) for advanced
  introspection of the inner `LruCache`.
- Add `ConcurrentCached::cache_delete` and `ConcurrentCachedAsync::cache_delete` for deleting
  entries without decoding or returning the previous value.
- `CachedPeek` trait: non-mutating cache lookups that skip recency updates, TTL refresh, and hit/miss metrics
- `CachedRead` trait: shared-reference reads for stores with no read-side mutation; used by `unsync_reads`
- `CacheEvict` trait: explicit `evict()` method to sweep expired entries from all timed/expiring stores
- `unsync_reads = true` option for `#[cached]`: uses a read lock on the cache-hit path instead of a write lock; requires the store to implement `CachedRead` (supported by `UnboundCache`, `TtlSortedCache`, `HashMap`, and custom stores that implement `CachedRead`)
- `on_evict(|k, v| { ... })` eviction callbacks on all in-memory stores (`LruCache`, `TtlCache`, `LruTtlCache`, `ExpiringLruCache`, `TtlSortedCache`)
- `::builder()` constructor APIs for all in-memory stores
- `cache_evictions()` metric on all stores that support eviction
- `ConcurrentCachedAsync` is now implemented for `DiskCache`; `#[concurrent_cached(disk = true)]`
  on an `async fn` runs all `sled` I/O on `tokio`'s blocking pool via `spawn_blocking` instead
  of blocking the async runtime. Adds the `DiskCacheError::BackgroundTaskFailed` variant
  returned if that blocking task is cancelled or panics.
- `#[cached]`, `#[once]`, and `#[concurrent_cached]` are now re-exported at the crate root
  (`use cached::cached;` works), alongside the existing `cached::macros::*` path.
- `DiskCacheBuildError`, `DiskCacheBuilder`, `RedisCacheBuildError`, `RedisCacheBuilder`, and
  `AsyncRedisCacheBuilder` are now re-exported at the crate root, matching the in-memory
  `*Builder` re-exports — the error type returned by `DiskCache`/`RedisCache` `build()` is now
  nameable via the same path the cache type came from.
## Changed
- Make LRU-backed `try_build` paths consistently use fallible allocation helpers
  instead of panicking constructors.
- Optimize `TtlCache`, `LruTtlCache`, and `ExpiringLruCache` to perform exactly one lookup (O(1)) on hit paths for `cache_get`, `cache_get_mut`, and `cache_get_with_expiry_status` by inlining expiration status checks.
- **Breaking:** `LruCache::try_with_size` and `LruTtlCache::try_with_size_and_ttl` now return `Result<_, BuildError>` directly instead of `std::io::Result` as a hard breaking change, aligning them with modern Builder pattern construction.
- `TtlSortedCache::set_ttl` now returns `Option<Duration>` (previously `Duration`) to match
  `CacheTtl::set_ttl` and the `set_ttl` of every other timed store.
- `LruCache`, `LruTtlCache`, and `ExpiringLruCache` `cache_reset` implementations now
  rebuild their backing stores instead of only clearing entries.
- `DiskCache::cache_get` now returns deserialization errors for corrupted entries instead of
  treating them as cache misses.
- `DiskCache::remove_expired_entries` now reports storage and deserialization errors encountered
  while sweeping instead of ignoring them.
- Fix timed `#[once]` caches so TTL starts after the function body finishes executing.
- Improve macro diagnostics for `result_fallback` without `result = true` and for
  `with_cached_flag` return types whose names merely contain `Return`.
- Fix `ExpiringLruCache::cache_capacity` to report `Some(capacity)` (was falling
  through to the `Cached` default `None`, so `metrics().capacity` was inaccurate
  for the only size-bounded store that didn't override it).
- `RedisCache`, `RedisCacheBuilder`, `AsyncRedisCache`, and `AsyncRedisCacheBuilder`
  now use a fn-pointer `PhantomData<fn() -> (K, V)>` so the cache type is
  unconditionally `Send + Sync` regardless of whether `K`/`V` are. Dropped the
  `V: Sync` bound from `impl AsyncRedisCache` and `impl ConcurrentCachedAsync
  for AsyncRedisCache` (values cross the async boundary by value, never by
  shared reference). A value that is `Send` but `!Sync` (e.g. one containing a
  `Cell`) — previously rejected because the macro-emitted
  `LazyLock<RedisCache<_, V>>` / `OnceCell<AsyncRedisCache<_, V>>` static
  required the cache type to be `Sync` (`PhantomData<(K, V)>` propagated
  `V: Sync`), and the async path additionally had `V: Send + Sync` on the
  trait/inherent impls — is now accepted. Mirrors the async `DiskCache`
  relaxation.
- `#[concurrent_cached]` now structurally requires the function return to be a
  `Result` (last path segment named `Result`). Previously `Option<T>` / `Vec<T>`
  / bare `T` returns passed the attribute check and produced a confusing error
  inside the generated body; they now fail with a clean spanned diagnostic
  pointing at the return type. Proc-macro token-only limitation: a `Result`
  *type alias* renamed away from `Result` is not recognized (same as
  `with_cached_flag`/`Return`).
- **Breaking:** `#[concurrent_cached]` now rejects every store-builder attribute
  (`ttl`, `refresh`, `cache_prefix_block`, `disk_dir`, `connection_config`,
  `sync_to_disk_on_cache_change`) when a `create` block is supplied, with a
  single unified message naming each offender. Previously only `ttl`/`refresh`
  (and `cache_prefix_block` for the redis/custom branches) were rejected, so
  `disk_dir`/`connection_config`/`sync_to_disk_on_cache_change` paired with
  `create` were silently ignored — a real footgun (the user thought their disk
  path / durability was applied when it was not). Move the dropped attrs into
  your `create` block, or remove them.
- **Breaking:** `#[cached]` likewise rejects its store-builder attributes
  (`ttl`, `ttl_millis`, `max_size`, `unbound`, `refresh`) when a `create` block
  is supplied, with the same unified message, mirroring `#[concurrent_cached]`.
  Previously `refresh` paired with `create` was silently ignored. Move the
  dropped attrs into your `create` block, or remove them.
- `CacheEvict::evict` now returns the number of expired entries removed, matching the existing
  `TtlSortedCache` behavior.
- Fix `DiskCache::cache_get` refreshes to return serialization errors instead of panicking when
  refreshed values cannot be serialized.
- Fix `DiskCache::cache_set` to return the raw previous value at a key, matching the
  `ConcurrentCached` trait contract and Redis behavior.
- Fix `LruTtlCache` expired lookups so they do not promote expired entries or inflate the
  inner `LruCache` hit/miss metrics.
- Fix `ExpiringLruCache::cache_get` and `cache_get_mut` to use `peek_by_key` +
  `move_to_front_by_key` instead of routing through `LruCache::cache_get`, which was
  inflating the inner store's hit counter on every successful lookup.
- Fix `ExpiringLruCache::cache_get_mut` to fire `on_evict` callbacks and increment eviction
  metrics when an expired entry is removed.
- Redis TTL handling now rejects only zero durations, rounds sub-second non-zero TTLs up to one
  second, and avoids overflowing refresh expirations.
- **Breaking:** Redis cache key format changed from raw concatenation (`{namespace}{prefix}{key}`)
  to colon-delimited joining with empty-segment skipping (`{namespace}:{prefix}:{key}`).
  Existing Redis caches built against pre-1.0 versions will see cache misses on upgrade because
  stored keys will no longer match. The default namespace (`cached-redis-store:`) is trimmed of
  its trailing colon and re-joined, so the effective change for default-namespace users is that
  the prefix and key are now separated by `:` (e.g. `cached-redis-store:my_prefixmy_key` →
  `cached-redis-store:my_prefix:my_key`).
- `LruTtlCache` validation errors now use `ErrorKind::InvalidInput` instead of raw OS error
  codes.
- Improve `#[cached(unsync_reads = true)]` diagnostics for generated sized/timed stores and
  convert several `#[concurrent_cached]` macro panics into spanned compile errors.
- Fix `LruTtlCache` and `ExpiringLruCache`: `on_evict` callbacks and eviction counts now correctly fire when `cache_get_or_set_with` replaces an expired entry (previously the displaced value was silently discarded)
- Fix `ExpiringLruCache::cache_get`: expired entries are now removed on access instead of being promoted to most-recent in the LRU, which was causing live entries to be evicted ahead of expired ones
- Fix `TtlSortedCache`: size-limit validation now returns `ErrorKind::InvalidInput` instead of `from_raw_os_error(22)`
- Fix `HashMap` `CachedPeek`/`CachedRead` impls: removed spurious `S: Default` bound (only the `Cached` impl requires it)
- Expanded `make tests` matrix with explicit `no-default`, `proc_macro`-only, `time_stores`, `async`, `disk_store`, and `redis` feature combinations
- **Breaking:** `redis_connection_manager` no longer implies `redis_tokio`. It now implies `async`
  and `redis_store` plus the `redis/tokio-comp` and `redis/connection-manager` redis features —
  giving you the Tokio async runtime and the connection manager without pulling in TLS. Users who
  need TLS should add `redis_tokio` (native-tls) or configure TLS via the `redis` crate directly.
## Removed
- **Breaking:** Completely removed the unused internal `Status` enum from `cached::stores` (it was previously returned by an internal helper which has been inlined/eliminated).
- **Breaking:** Removed declarative macros (`cached!`, `cached_key!`, `cached_result!`,
  `cached_key_result!`, `cached_control!`) and the `macros` module that contained them.
  Use the `#[cached]`, `#[once]`, and `#[concurrent_cached]` procedural macros instead.
- **Breaking:** The procedural macro re-export module has been renamed from `proc_macro` to
  `macros`. Update `use cached::proc_macro::cached` to `use cached::macros::cached`
  (and similarly for `once`; the `io_cached` macro was additionally renamed — see below).
- **Breaking:** Renamed the `IOCached`/`IOCachedAsync` traits to
  `ConcurrentCached`/`ConcurrentCachedAsync`, and the `#[io_cached]` proc macro to
  `#[concurrent_cached]` (`cached::macros::io_cached` → `cached::macros::concurrent_cached`).
  The contract is unchanged — the names no longer imply "IO", since a self-synchronizing
  in-memory store is equally valid. Update `impl IOCached for`/`use cached::IOCached` and
  every `#[io_cached(...)]` attribute accordingly.
- **Breaking:** Removed `InMemoryAdapter<K, V, C>`. It only wrapped a `Cached` store in a
  single `parking_lot::Mutex`, which is strictly worse than `#[cached]` for the macro path
  (double locking) and trivially hand-rolled for the rare generic-bridge case. Use
  `#[cached]`/`#[once]` for in-memory memoization, or implement `ConcurrentCached` directly.
- The example files `basic_proc_macro` and `kitchen_sink_proc_macro` have been renamed to
  `basic` and `kitchen_sink` respectively.
- **Breaking:** Renamed `CanExpire` trait to `Expires`. Update `use cached::CanExpire` to
  `use cached::Expires` and all `V: CanExpire` bounds to `V: Expires`.
- **Breaking:** IO store builder methods drop the `set_` prefix to match in-memory builder style:
  - `DiskCacheBuilder`: `set_ttl` → `ttl`, `set_refresh` → `refresh`,
    `set_disk_directory` → `disk_directory`,
    `set_sync_to_disk_on_cache_change` → `sync_to_disk_on_cache_change`,
    `set_connection_config` → `connection_config`
  - `RedisCacheBuilder` / `AsyncRedisCacheBuilder`: `set_lifespan` → `ttl`,
    `set_refresh` → `refresh`, `set_namespace` → `namespace`, `set_prefix` → `prefix`,
    `set_connection_string` → `connection_string`,
    `set_connection_pool_max_size` → `connection_pool_max_size`,
    `set_connection_pool_min_idle` → `connection_pool_min_idle`,
    `set_connection_pool_max_lifetime` → `connection_pool_max_lifetime`,
    `set_connection_pool_idle_timeout` → `connection_pool_idle_timeout`,
    `set_client_side_caching` → `client_side_caching` (async only);
    the internal resolver `connection_string` → `resolve_connection_string`
    (the setter now owns the bare name).
- **Breaking:** Removed all `#[deprecated]` shim methods: `LruCache::with_capacity`,
  `TtlSortedCache::ttl_millis`, `DiskCacheBuilder::set_lifespan`.
- **Breaking:** Removed `cache_ttl`, `cache_set_ttl`, and `cache_unset_ttl` from
  the `Cached` trait. Use `CacheTtl::ttl`, `set_ttl`, and `unset_ttl` on timed
  stores instead.
- **Breaking:** Renamed IO-backed TTL/refresh methods to match `CacheTtl`:
  `cache_ttl` → `ttl`, `cache_set_ttl` → `set_ttl`, `cache_unset_ttl` → `unset_ttl`,
  `cache_set_refresh` → `set_refresh_on_hit`.
- **Breaking:** Renamed inherent timed-store refresh accessors:
  `TtlCache::refresh` → `refresh_on_hit`, `TtlCache::set_refresh` → `set_refresh_on_hit`,
  `LruTtlCache::refresh` → `refresh_on_hit`, `LruTtlCache::set_refresh` → `set_refresh_on_hit`.
- **Breaking:** `get_store()` → `store()` on `TtlCache`, `LruTtlCache`, and `UnboundCache`
  (follows Rust API Guidelines C-GETTER).
- **Breaking:** `TtlSortedCache::get_borrowed` removed; `get` is now generic
  (`get<Q>(&self, key: &Q) where K: Borrow<Q>`) so `cache.get("key")` and
  `cache.get(slice)` work directly.
- **Breaking:** `TtlSortedCache`'s inherent `remove(&K)` / `clear()` / `len()`
  / `is_empty()` / `get<Q>(&self, ...)` methods removed — they shadowed the
  same-named `Cached` short aliases without adding behavior. Bring `Cached`
  into scope and use the trait short aliases (`cache.remove(&k)` etc.) or the
  canonical `cache_*` forms. The inherent `get` was the only one with a
  semantic difference: it was `&self` and **did not** evict expired entries on
  access (the trait `Cached::get` requires `&mut self` and *does* — it
  delegates to `cache_get`, which removes expired entries on access in this
  store). To preserve the previous `&self` non-evicting read behavior, use
  [`CachedRead::cache_get_read`](https://docs.rs/cached/latest/cached/trait.CachedRead.html)
  or `CachedPeek::cache_peek`. Both already implemented by `TtlSortedCache`.
- **Breaking:** Renamed `CachedAsync::get_or_set_with` → `async_get_or_set_with` and
  `CachedAsync::try_get_or_set_with` → `async_try_get_or_set_with`. The old names collided
  with the same-named `Cached` convenience methods (the in-memory stores implement both
  traits), so any call with both traits in scope (e.g. `use cached::*;`) failed to compile
  with `E0034`. The `#[cached]`/`#[once]` macros are unaffected — they call the canonical
  `cache_*` methods.
- Fix rustdoc links so documentation builds cleanly with warnings denied across
  feature combinations.

## [0.59.0 / [cached_proc_macro[0.27.0]]]
## Added
## Changed
- Fix `examples/wasm` build: add `time_stores` feature to the `cached` dependency (required when using `default-features = false` with `TimedCache`)
## Removed

## [0.58.0]
## Added
- Add `redis_async_cache` feature for Redis client-side caching support via the RESP3 protocol
## Changed
- Update `redis` to 1.0
## Removed

## [0.57.0 / [cached_proc_macro[0.26.0]]]
## Added
- Add `parking_lot` dependency
## Changed
- Switch to `parking_lot`'s `Mutex` and `RwLock` in all macros.
- Remove `unwrap()` calls from lock operations.
## Removed

## [0.56.0 / [cached_proc_macro[0.25.0]]]
## Added
## Changed
- *BREAKING* All timed/expiring caches now use std::time::Duration values instead of raw seconds/millis.
- Update `redis` to 0.32
- Update `hashbrown` to 0.15
## Removed

## [0.55.1 / [cached_proc_macro[0.24.0]]]
## Added
- Add `sync_writes = "by_key"` support to `#[cached]`
## Changed
- Update `redis` to 0.29.0
- Update `directories` to 6.0
- Update `thiserror` to 2.0
- With the `sync_writes = "by_key"` addition, the argument values changed from a boolean
  to strings. The equivalent of `sync_writes = true` is now `sync_writes = "default"`
## Removed

## [0.54.0]
## Added
- Add `Cached::cache_try_get_or_set_with` for parity with async trait
## Changed
- Remove unnecessary string clones in redis cache store
- Update cargo default features manifest key
## Removed

## [0.53.1 / [cached_proc_macro[0.23.0]]]
## Added
## Changed
- Replace `instant` with `web_time` in proc macro, update cached_proc_macro version
## Removed

## [0.53.0]
## Added
## Changed
- Replace unmaintained `instant` crate with `web_time`
## Removed

## [0.52.0 / [cached_proc_macro[0.22.0]] ]
## Added
## Changed
- Propagate function generics to generated inner cache function 
## Removed


## [0.51.4]
## Added
## Changed
- Update `DiskCache` to require `ToString` instead of `Display`
## Removed

## [0.51.3]
## Added
- `ExpiringSizedCache`: Allow specifying explicit TTL when inserting
## Changed
- Refactor `ExpiringSizedCache` internals to not require tombstones
- `ExpiringSizedCache` keys must impl `Ord`
- `ExpiringSizedCache` `remove` and `insert` updated to return only unexpired values
## Removed

## [0.51.2]
## Added
- Add `get_borrowed` methods to `ExpiringSizedCache` to support cache retrieval using `&str` / `&[T]`
  when the key types are `String` / `Vec<T>`. This is a workaround for issues implementing `Borrow`
  for a generic wrapper type.
## Changed
## Removed

## [0.51.1]
## Added
- Update documentation and add missing methods to `ExpiringSizedCache` (clear, configuration methods)
## Changed
- `ExpiringSizedCache`: When allocating using `with_capacity`, allocate enough space to account for
  the default max number of tombstone entries
## Removed

## [0.51.0]
## Added
- Add `ExpiringSizedCache` intended for high read scenarios. Currently incompatible with the cached trait and macros.
## Changed
## Removed

## [0.50.0 / [cached_proc_macro[0.21.0]] ]
## Added
- Add `DiskCacheBuilder::set_sync_to_disk_on_cache_change` to specify that the cache changes should be written to disk on every cache change.
- Add `sync_to_disk_on_cache_change` to `#[io_cached]` to allow setting `DiskCacheBuilder::set_sync_to_disk_on_cache_change` from the proc macro.
- Add `DiskCacheBuilder::set_connection_config` to give more control over the sled connection.
- Add `connection_config` to `#[io_cached]` to allow setting `DiskCacheBuilder::set_connection_config` from the proc macro.
- Add `DiskCache::connection()` and `DiskCache::connection_mut()` to give access to the underlying sled connection.
- Add `cache_unset_lifespan` to cached traits for un-setting expiration on types that support it
## Changed
- [Breaking] `type` attribute is now `ty`
- Upgrade to syn2 
- Corrected a typo in DiskCacheError (de)serialization variants
- Signature or `DiskCache::remove_expired_entries`: this now returns `Result<(), DiskCacheError>` instead of `()`, returning an `Err(sled::Error)` on removing and flushing from the connection.
## Removed

## [0.49.3]
## Added
## Changed
- Fix `DiskCache` expired value logic
## Removed

## [0.49.2]
## Added
## Changed
- While handling cache refreshes in `DiskCache::cache_get`, treat deserialization failures as non-existent values
## Removed

## [0.49.1]
## Added
## Changed
- Fix `DiskCache::remove_expired_entries` signature
## Removed

## [0.49.0 / [cached_proc_macro[0.20.0]] ]
## Added
- Add DiskCache store
- Add `disk=true` (and company) flags to `#[io_cached]`
## Changed
## Removed

## [0.48.1 / [cached_proc_macro[0.19.1]] / [cached_proc_macro_types[0.1.1]]]
## Added
- Include LICENSE file in `cached_proc_macro` and `cached_proc_macro_types`
## Changed
## Removed

## [0.48.0 / [cached_proc_macro[0.19.0]]]
## Added
- Add `CloneCached` trait with additional methods when the cache value type implements `Clone`
- Add `result_fallback` option to `cached` proc_macro to support re-using expired cache values
  when utilizing an expiring cache store and a fallible function.
## Changed
## Removed

## [0.47.0]
## Added
## Changed
- Update redis `0.23.0` -> `0.24.0`
## Removed

## [0.46.1 / [cached_proc_macro[0.18.1]]
## Added
## Changed
- Fix #once sync_writes bug causing a deadlock after ttl expiry, https://github.com/jaemk/cached/issues/174
## Removed

## [0.46.0]
## Added
- Add `ahash` feature to use the faster [ahash](https://github.com/tkaitchuck/aHash) algorithm.
- Set `ahash` as a default feature.
- Update hashbrown `0.13.0` -> `0.14.0`
## Changed
## Removed

## [0.45.1] / [cached_proc_macro[0.18.0]]
## Added
## Changed
- Release `*_no_cache` changes from `0.45.0`. The change is in the proc macro crate which
  I forgot to release a new version of.
## Removed

## [0.45.0]
## Added
- Generate `*_no_cache` function for every cached function to allow calling the original function
  without caching. **This is backwards incompatible if you have a function with the same name**.
## Changed
- `tokio` dependency has been removed from `proc_macro` feature (originally unecessarily included).
- `async` feature has been removed from the `default` feature. **This is a backwards incompatible change.**
  If you want to use `async` features, you need to enable `async` explicitly.
- remove accidental `#[doc(hidden)]` on the `stores` module
## Removed

## [0.44.0] / [cached_proc_macro[0.17.0]]
## Added
- Option to enable redis multiplex-connection manager on `AsyncRedisCache`
## Changed
- Show proc-macro documentation on docs.rs
- Document needed feature flags
- Hide implementation details in documentation
- Relax `Cached` trait's `cache_get`, `cache_get_mut` and `cache_remove` key parameter. Allow `K: Borrow<Q>`
  like `std::collections::HashMap` and friends. Avoids copies particularly on `Cached<String, _>` where now
  you can do `cache.cache_get("key")` and before you had to `cache.cache_get("key".to_string())`.

  Note: This is a minor breaking change for anyone manually implementing the `Cached` trait.
  The signatures of `cache_get`, `cache_get_mut`, and `cache_remove` must be updated to include the
  additional trait bound on the `key` type:
  ```rust
    fn cache_get<Q>(&mut self, key: &Q) -> Option<&V>
    where
        K: std::borrow::Borrow<Q>,
        Q: std::hash::Hash + Eq + ?Sized,
    {
  ```
## Removed
- Dependency to `lazy_static` and `async_once` are removed.

## [0.43.0]
## Added
## Changed
- Update redis `0.22.0` -> `0.23.0`
- Update serial_test `0.10.0` -> `2.0.0`
## Removed

## [0.42.0] / [cached_proc_macro[0.16.0]]
## Added
## Changed
- Better code generation for `#[cached]` when the `sync_writes` flag is true.
## Removed

## [0.41.0]
## Added
## Changed
- Fix "sized" cache types (`SizedCache`, `TimedSizedCache`) to check capacity and evict members after insertion.
- Fixes bug where continuously inserting a key present in the cache would incorrectly evict the oldest cache member
  even though the cache size was not increasing.
## Removed

## [0.40.0]
## Added
- Add optional feature flag `redis_ahash` to enable `redis`'s optional `ahash` feature
## Changed
- Update `redis` to `0.22.0`
- Move `tokio`'s `rt-multi-thread` feature from being a default to being optionally enabled by `async_tokio_rt_multi_thread`
- Fix makefile's doc target to match documentation, changed from `make sync` to `make docs`
## Removed

## [0.39.0]
## Added
- Add flush method to ExpiringValueCache
## Changed
## Removed

## [0.38.0] / [cached_proc_macro[0.15.0]]
## Added
## Changed
- Fix proc macro argument documentation
- Disable futures `default-features`
- Add cache-remove to redis example
## Removed

## [0.37.0] / [cached_proc_macro[0.14.0]]
## Added
## Changed
- Mark the auto-generated "priming" functions with `#[allow(dead_code)]`
- Fix documentation typos
- Replace dev/build scripts with a Makefile
## Removed

## [0.36.0] / [cached_proc_macro[0.13.0]]
## Added
- wasm support for non-io macros and stores
## Changed
- Use `instant` crate for wasm compatible time
## Removed

## [0.35.0]
## Added
- Added `ExpiringValueCache` for caching values that can themselves expire.
- Added COPYRIGHT file
## Changed
## Removed

## [0.34.1]
## Added
- Make sure `AsyncRedisCacheBuilder`, `RedisCacheBuilder`, and `RedisCacheBuildError` publicly visible
## Changed
## Removed

## [0.34.0] / [cached_proc_macro[0.12.0]]
## Added
## Changed
- Replace `async-mutex` and `async-rwlock` used by proc-macros with `tokio::sync` versions
- Add optional `version` field to `CachedRedisValue` struct
- Cleanup feature flags so async redis features include `redis_store` and `async` features automatically
## Removed

## [0.33.0]
## Added
- Allow specifying the namespace added to cache keys generated by redis stores
## Changed
- Bump hashbrown 0.11.2 -> 0.12: https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md#v0120---2022-01-17
- Bump smartstring 0.2 -> 1: https://github.com/bodil/smartstring/blob/master/CHANGELOG.md#100---2022-02-24
## Removed

## [0.32.1]
## Added
## Changed
- Fix redis features so `redis/aio` is only included when async redis
  features (`redis_tokio` / `redis_async_std`) are enabled
## Removed

## [0.32.0] / [cached_proc_macro[0.11.0]]
## Added
- Fix how doc strings are handled by proc-macros. Capture all documentation on the
  cached function definitions and add them to the function definitions generated
  by the proc-macros. Add doc strings to generated static caches. Link to relevant static
  caches in generated function definitions. Add documentation to the generated
  cache-priming function.
## Changed
## Removed

## [0.31.0] / [cached_proc_macro[0.10.0]]
## Added
- `IOCached` and `IOCachedAsync` traits
- `RedisCache` and `AsyncRedisCache` store types
- Add `#[io_cached]` proc macro for defining cached functions backed
  by stores that implement `IOCached`/`IOCachedAsync`
## Changed
- Convert from travis-ci to github actions
- Update build status badge to link to github actions
## Removed

## [0.30.0]
## Added
- Add flush method to TimedSize and TimedSized caches
## Changed
- Fix timed/timed-sized cache-get/insert/remove to remove and not
  return expired values
## Removed

## [0.29.0] / [cached_proc_macro[0.9.0]]
## Added
- proc-macro: support arguments of the wrapped function being prefixed with `mut`
## Changed
## Removed

## [0.28.0]
## Added
- Add failable TimedSize and SizeCached constructors
## Changed
## Removed

## [0.27.0] / [cached_proc_macro[0.8.0]]
## Added
- Add `time_refresh` option to `#[cached]` to refresh TTLs on cache hits
- Generate `*_prime_cache` functions for every `#[cached]` and `#[once]` function
  to allow priming caches.
## Changed
## Removed

## [0.26.1] / [cached_proc_macro[0.7.1]]
## Added
- Add `sync_writes` option to `#[cached]` macro to synchronize
  concurrent function calls of duplicate arguments. For ex, if
  a long running `#[cached(sync_writes = true)]` function is called
  several times concurrently, the actual function is only executed
  once while all other calls block and return the newly cached value.
## Changed
## Removed

## [0.26.0] / [cached_proc_macro[0.7.0]]
## Added
- Add `#[once]` macro for create a `RwLock` cache wrapping a single value
- For all caches, add a function to get an immutable reference to their
  contents. This makes it possible to manually dump a cache, so its contents
  can be saved and restored later.
## Changed
## Removed

## [0.25.1]
## Added
## Changed
- Update deps hashbrown and darling, remove async-mutex from cached-proc-macro crate
## Removed

## [0.25.0]
## Added
- Add option to "timed" caches to refresh the ttl of entries on cache hits
## Changed
## Removed

## [0.24.1] / [cached_proc_macro[0.6.1]]
## Added
- Add docs strings to the items generated by the `#cached` proc macro
## Changed
## Removed

## [0.24.0]
## Added
- `cache_reset_metrics` trait method to reset hits/misses
## Changed
## Removed

## [0.23.0]
## Added
## Changed
- Refactor cache store types to separate modules
## Removed

## cached[0.22.0] / cached_proc_macro[0.6.0] / cached_proc_macro_types[0.1.0]
## Added
- Add support for returning a `cached::Return` wrapper type that
  indicates whether the result came from the function's cache.
## Changed
## Removed

## [0.21.1] / [0.5.0]
## Added
- Support mutual `size` & `time` args in the cached proc macro.
  Added when TimedSizedCache was added, but forgot to release
  the cached_proc_macro crate update.
## Changed
## Removed

## [0.21.0]
## Added
- Add a TimedSizedCache combining LRU and timed/ttl logic
## Changed
## Removed

## [0.20.0]
## Added
- Add new CachedAsync trait. Only present with async feature. Adds two async function in the entry API style of HashMap
## Changed
## Removed

## [0.19.0] / [0.4.0]
## Added
## Changed
- Add type hint `_result!` macros
- remove unnecessary transmute in cache reset
- remove unnecessary clones in proc macro
## Removed

## [0.18.0] / [0.3.0]
## Added
## Changed
- use `async-mutex` instead of full `async-std`
## Removed

## [0.17.0]
## Added
## Changed
- Store inner values when `result=true` or `option=true`. The `Error` type in the
`Result` now no longer needs to implement `Clone`.
## Removed

## [0.16.0]
## Added
- add `cache_set_lifespan` to change the cache lifespace, old value returned.
## Changed
## Removed

## [0.15.1]
## Added
## Changed
- fix proc macro when result=true, regression from changing `cache_set` to return the previous value
## Removed

## [0.15.0]
## Added
- add `Cached` implementation for std `HashMap`
## Changed
- trait `Cached` has a new method `cache_get_or_set_with`
- `cache_set` now returns the previous value if any
## Removed

## [0.14.0]
## Added
- add Clone, Debug trait derives on pub types

## Changed

## Removed

## [0.13.1]
## Added

## Changed
- fix proc macro documentation

## Removed

## [0.13.0]
## Added
- proc macro version
- async support when using the new proc macro version

## Changed

## Removed

## [0.12.0]
## Added
- Add `cache_get_mut` to `Cached` trait, to allow mutable access for values in the cache.
- Change the type of `hits` and `misses` to be `u64`.

## Changed

## Removed

## [0.11.0]
## Added
- Add `value_order` method to SizedCache, similar to `key_order`

## Changed

## Removed

## [0.10.0]
## Added
- add `cache_reset` trait method for resetting cache collections to
  their initial state

## Changed
- Update `once_cell` to 1.x

## Removed

## [0.9.0]
## Added

## Changed
- Replace SizedCache implementation to avoid O(n) lookup on cache-get
- Update to Rust-2018 edition
- cargo fmt everything

## Removed


## [0.8.1]
## Added

## Changed
- Replace inner cache when "clearing" unbounded cache

## Removed


## [0.8.0]
## Added

## Changed
- Switch to `once_cell`. Library users no longer need to import `lazy_static`

## Removed

## [0.7.0]
## Added
- Add `cache_clear` and `cache_result` to `Cached` trait
  - Allows for defeating cache entries if desired

## Changed

## Removed

## [0.6.2]
## Added

## Changed
- Update documentation
  - Note the in-memory nature of cache stores
  - Note the behavior of memoized functions under concurrent access

## Removed

## [0.6.1]
## Added

## Changed
- Fixed duplicate key eviction in `SizedCache::cache_set`. This would manifest when
  `cached` functions called with duplicate keys would race set an uncached key,
  or if `SizedCache` was used directly.

## Removed

## [0.6.0]
## Added
- Add `cached_result` and `cached_key_result` to allow the caching of success for a function that returns `Result`.
- Add `cached_control` macro to allow specifying functionality
  at key points of the macro

## [0.5.0]
## Added
- Add `cached_key` macro to allow defining the caching key

## Changed
- Tweak `cached` macro syntax
- Update readme

## Removed


## [0.4.4]
## Added

## Changed
- Update trait docs

## Removed


## [0.4.3]
## Added

## Changed
- Update readme
- Update examples
- Update crate documentation and examples

## Removed