NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
crates.io · #4461 most downloaded on crates.io
Maps where keys borrow from values, including bijective and trijective maps.
Last release 27 days ago
10 Sep 2026
Release timing varies
gaps range from 8 days to 5 months
Nearly every release is documented
notes for 32 of 32 stable releases
Nothing withdrawn
no release was ever pulled
1 years old
32 releases · first in 2025
One column per month.
This is a breaking change only for Serialize impls that exist solely for a 'static key type. In most cases, impls are generic over the key lifetime —…
This release fixes a number of soundness holes, mostly identified by Claude Fable 5.1 and GPT-6 Astra, plus Google's unsafe_rust_review_experimental agent skill. All identified soundness holes require significantly contrived code, e.g. a Hash impl that stashes the passed-in reference into a thread-local or internal Cell.
Overall, iddqd now has significantly less unsafe code than before, though due to Rust compiler limitations it asks slightly more of trait implementers (such as IdOrdItem::Key now requiring Hash for change detection). We hope to relax these requirements in the future as the Rust compiler improves.
Thanks to the authors of the Google agent skill.
debug_with_keys methods on IdOrdMap, IdHashMap, BiHashMap, and TriHashMap. These return a value whose Debug output is the previous {key: item, ...} form, and require the key types to be Debug for the lifetime of the borrow.
IdOrdMap's IntoIter now implements ExactSizeIterator and FusedIterator, matching the other maps' owning iterators.
Breaking: The mutable-borrow lookup methods now take the key by value, as T::Key<'_>, rather than any Q: Equivalent<T::Key<'_>> (or Comparable). This affects:
IdHashMap and IdOrdMap: get_mut and remove.BiHashMap: get1_mut, get2_mut, remove1, remove2, get_mut_unique, and remove_unique.TriHashMap: get1_mut, get2_mut, get3_mut, remove1, remove2, remove3, get_mut_unique, and remove_unique.The shared-borrow lookups (get, contains_key, and their numbered variants) still accept any Q.
See the Mutable lookups take owned keys section in the crate docs for more information, and the "Fixed" entry below for the soundness hole this closes.
The Debug impl for IdHashMap no longer requires S: Clone + BuildHasher, matching BiHashMap and TriHashMap.
The Debug impls for IdOrdMap, IdHashMap, BiHashMap, and TriHashMap now format items only, as a set ({item, ...}), and require just T: Debug. Previously they formatted {key: item, ...} and also required the key types to be Debug. Use debug_with_keys for the previous form. The Debug impls for the daft Diff and MapLeaf types likewise no longer require the key types to be Debug.
Breaking: IdOrdItem::Key now requires Hash in addition to Ord. Any Hash impl that is generic over the key lifetime, including derived ones, satisfies the new bound.
IdOrdMap's RefMut has always needed Hash to detect key changes, but the bound used to be on each method that hands out a RefMut. Moving it onto the trait removes those per-method bounds.
As a result, RefMut::reborrow no longer requires the item type to be 'static. (The 0.3.10 changelog claimed this already worked, but it did not.)
Fixed a soundness hole in the mutable-borrow lookup methods listed under "Changed". Their signatures, such as fn get_mut<'a, Q: Equivalent<T::Key<'a>>>(&'a mut self, key: &Q), let caller code copy a reference out of that key into a Cell<Option<&'a str>> and read it after the map had mutated or dropped the item.
These APIs have been changed to take T::Key<'_> directly, which closes this soundness hole.
The Iter, IterMut, and IntoIter types now report an exact size_hint. Previously, they returned (0, None). This violated the ExactSizeIterator contract, resulting in calls like .take(...).len() panicking on a non-empty map.
Fixed a soundness hole in IdOrdMap's RefMut. Within Entry::and_modify and IdOrdMap::retain, the item can be removed while the RefMut's borrow lifetime 'a is still live. In a contrived scenario where:
'static, e.g. with Box::leak; and,Hash impl was written only for Key<'static>,The Hash impl could observe a key that wasn't valid for 'static. The new Hash bound on IdOrdItem::Key rejects a 'static-only Hash impl at compile time.
Fixed a soundness hole in the Debug impls for IdOrdMap, IdHashMap, BiHashMap, and TriHashMap. A contrived scenario where a Debug impl was written only for Key<'static> could observe a 'static key that actually borrowed from the map. The impls no longer format keys, and the internal lifetime-extending transmute is gone. (This is why the Debug output changed; see above.)
Fixed a soundness hole in the serialize functions of IdOrdMapAsMap, IdHashMapAsMap, BiHashMapAsMap, and TriHashMapAsMap. The lifetime 'a in T::Key<'a>: Serialize was not tied to the borrow of the map, so a contrived scenario where a Serialize impl was written only for Key<'static> could observe a key that actually borrowed from the map.
This is a breaking change only for Serialize impls that exist solely for a 'static key type. In most cases, impls are generic over the key lifetime — those are unaffected.
Fixed a soundness hole in IdHashMap, BiHashMap, and TriHashMap with custom allocators (via the allocator-api2 feature). The maps now correctly call the allocator's grow, grow_zeroed, shrink, and allocate_zeroed methods.
Previously, only allocate and deallocate were called, and the others fell through to the trait's default implementations -- those implementations allocate a new block, copy the memory, and then call deallocate on the old one. In the unlikely case that the last deallocate freed the block and then panicked, a map resize could leave the map holding a freed pointer, and dropping the map would free it again.
Allocators that don't implement grow and shrink still get the trait's default grow and shrink. For those allocators, deallocate should not unwind after freeing. (This is a pre-existing limitation in the allocator-api2 crate.)
IdOrdMap does not support custom allocators and is not affected. Unsoundness on allocator panics is a widespread problem in the Rust ecosystem, which is why the soon-to-be-stabilized standard library allocator API bans panicking within the allocator.
from_iter_unique constructors on IdHashMap , BiHashMap , and TriHashMap , matching the existing IdOrdMap::from_iter_unique . These build a map from an
from_iter_unique constructors on IdHashMap, BiHashMap, and TriHashMap, matching the existing IdOrdMap::from_iter_unique. These build a map from an iterator and, rather than overwriting, return an error on the first item that conflicts with an already-inserted one.
Because a value in a BiHashMap or TriHashMap can conflict on more than one key at once, the error reports every distinct existing item it collides with (up to two for BiHashMap and up to three for TriHashMap).
Deserialization no longer preallocates based on an unbounded size hint. Length-prefixed formats such as bincode and postcard derive their size hint from the input, so a small hostile payload claiming a huge number of elements could previously cause an excessively large allocation before any element was read. Preallocation is now capped at 1 MiB worth of items, matching what serde does for the standard library's collections.
The insert_overwrite path on IdHashMap no longer aborts when an allocation fails, matching the existing guarantee on BiHashMap and TriHashMap. Instead, it results in a catchable panic. (The map is left unchanged, similar to BiHashMap and TriHashMap.)
Note that BTreeMap::insert_overwrite will abort on allocation failure, because it calls into std which doesn't have an equivalent to HashMap::try_reserve.
iddqd 's core invariants are now formally verified under adversarial Hash and Ord impls using the Soteria symbolic executor. No new bugs were found du
iddqd's core invariants are now formally verified under adversarial Hash and Ord impls using the Soteria symbolic executor. No new bugs were found during this process.
The formal verification is broad (covers all possible adversarial return values) but bounded-depth; it acts as a complement to the existing layers of randomized testing, which are less broad but generate much deeper operation sequences.
For more information on our validation philosophy, see this Oxide blog entry.
Expanded examples for BiHashMap's Entry.
The FromIterator implementations now reserve capacity at the start of the operation.
FromIterator implementations now reserve capacity at the start of the operation.The insert_overwrite paths on BiHashMap and TriHashMap are now atomic in case user code panics. Thanks to SG-devel for your first contribution!
insert_overwrite paths on BiHashMap and TriHashMap are now atomic in case user code panics. Thanks to SG-devel for your first contribution!The retain callbacks no longer permit the RefMut to be stashed outside them. This is technically a breaking change, but is being treated as a soundnes…
retain callbacks no longer permit the RefMut to be stashed outside them. This is technically a breaking change, but is being treated as a soundness bugfix.Fixed a logic bug in TriHashMap::remove_unique , when key1 matches, and one of key2 and key3 matches, but not the other.
TriHashMap::remove_unique, when key1 matches, and one of key2 and key3 matches, but not the other.The internal implementation for item storage has been changed to use a linear slot-based buffer, resulting in 2-3x performance improvements for most w
u32::MAX (4 294 967 295) elements at any given time. This limit is very unlikely to be reached in practice.IdHashMap and IdOrdMap are now panic-safe, in the sense that a panic in user code will not corrupt the map. This does not currently extend to BiHashMap and TriHashMapFixed a rehashing bug in hash map reserve and shrink-to-fit methods. (Due to an oversight, these methods were previously not part of our property-base
Capacity management methods for all map types:
reserve(&mut self, additional: usize) reserves capacity for at least additional more elements.shrink_to_fit(&mut self) shrinks capacity to fit the current length.shrink_to(&mut self, min_capacity: usize) shrinks capacity to at least min_capacity.try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError>: fallible capacity reservation for hash maps (IdHashMap, BiHashMap, TriHashMap).TryReserveError type in the errors module for reporting allocation failures.IdOrdMap, the reserve and shrink methods only affect item storage. The internal BTreeSet used for item ordering does not support capacity control.IdOrdMap does not provide try_reserve, since the underlying BTreeSet does not expose fallible reservation operations.retain.The Extend implementations now pre-reserve capacity based on the iterator's size_hint.
clear methods for all map types to remove all items from the map.
clear methods for all map types to remove all items from the map.IdHashMapAsMap, BiHashMapAsMap, TriHashMapAsMap, and IdOrdMapAsMap marker types to use with #[serde(with = ...)].retain methods that allow filtering items in place based on a predicate.
retain methods that allow filtering items in place based on a predicate.IdOrdMap::first, first_entry, last, last_entry, pop_first, and pop_last methods for accessing entries at the beginning and end of the map.BiHashMap::with_hasher and TriHashMap::with_hasher are now const fn.BiHashMap and TriHashMap.Replaced obsolete doc_auto_cfg with doc_cfg, to fix Rust nightly builds with the doc_cfg flag enabled.
Replaced obsolete doc_auto_cfg with doc_cfg, to fix Rust nightly builds with the doc_cfg flag enabled.
iddqd now depends on serde_core rather than serde. This allows iddqd's compilation to be parallelized with serde_derive.
serde_core rather than serde. This allows iddqd's compilation to be parallelized with serde_derive.foldhash updated to 0.2, and hashbrown updated to 0.16.The following methods are now const fn:
The following methods are now const fn:
IdOrdMap::newIdHashMap::with_hasherThe type definitions for IdHashMap, BiHashMap, TriHashMap, and IdOrdMap no longer require IdHashItem, BiHashItem, TriHashItem, and IdOrdItem, respecti
The type definitions for IdHashMap, BiHashMap, TriHashMap, and IdOrdMap no longer require IdHashItem, BiHashItem, TriHashItem, and IdOrdItem, respectively. This matches the standard library's HashMap and BTreeMap type definitions which don't require Hash + Eq or Ord bounds.
Thanks to aatifsyed for your first contribution!
id_ord_map::RefMut's reborrow method now works if the underlying IdOrdItem is non-'static.
id_ord_map::RefMut's reborrow method now works if the underlying IdOrdItem is non-'static.
For the optional daft feature, the map Diff types now implement fmt::Debug.
daft feature, the map Diff types now implement fmt::Debug.New proptest feature adds strategy and Arbitrary implementations for map types.
proptest feature adds strategy and Arbitrary implementations for map types.iddqd is now compatible with schemars's preserve_order feature. Thanks Sh3Rm4n for your first contribution!
preserve_order feature. Thanks Sh3Rm4n for your first contribution!Relaxed Debug requirement to only require that T::Key<'a>: fmt::Debug, not for<'k> T::Key<'k>: fmt::Debug. This allows items with borrowed data to com
Debug requirement to only require that T::Key<'a>: fmt::Debug, not for<'k> T::Key<'k>: fmt::Debug. This allows items with borrowed data to compile in more cases.Hash requirement for IdOrdMap get_mut and related APIs in a similar fashion.New feature schemars08 adds support for generating JSON schemas.
schemars08 adds support for generating JSON schemas.New macros id_hash_map, bi_hash_map, tri_hash_map, and id_ord_map allow easy construction of literal macros. These macros use insert_unique, so they p
id_hash_map, bi_hash_map, tri_hash_map, and id_ord_map allow easy construction of literal macros. These macros use insert_unique, so they panic if duplicate keys are encountered.id_upcast, bi_upcast and tri_upcast macros now have a Self: 'long bound, allowing them to be used for non-'static items.derive-where, debug-ignore, and serde's derive feature. iddqd no longer depends on any proc macros.A lot of new documentation. Most functions now have doctests.
The hash map types now support custom hashers.
allocator-api2 feature (enabled by default), the hash map types now support custom allocators, including on stable. See the bumpalo-alloc example.lib.rs that small copyable keys like integers are best returned as owned ones.Ord requirement for Comparable keys. (The Hash requirement for Equivalent continues to be required.)Re-export equivalent::Equivalent and equivalent::Comparable.
equivalent::Equivalent and equivalent::Comparable.Lookups now use [equivalent::Equivalent] or [equivalent::Comparable], which are strictly more general than Borrow.
equivalent::Equivalent or equivalent::Comparable, which are strictly more general than Borrow.get_mut and remove methods no longer require the key type; the borrow checker limitation has been worked around.MapLeaf<'a, T>'s Clone and Copy no longer require T to be Clone or Copy. (MapLeaf is just a couple of references, so this is never necessary.)
MapLeaf<'a, T>'s Clone and Copy no longer require T to be Clone or Copy. (MapLeaf is just a couple of references, so this is never necessary.)Daft implementations for BiHashMap and TriHashMap changed to also allow diffing by individual keys.
Extend implementations.BiHashMap and TriHashMap changed to also allow diffing by individual keys.BiHashMap and TriHashMap now have a remove_unique method which removes an item uniquely indexed by all keys.
BiHashMap and TriHashMap now have a remove_unique method which removes an item uniquely indexed by all keys.upcast macros are now annotated with #[inline], since they're trivial.Daft implementations with the new daft feature.
daft feature.BiHashItem implementations for reference types like &'a T and Box<T>.<!-- next-url --> [0.5.0]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.5.0 [0.4.6]: https://github.com/oxidecomputer/iddqd/releases/ta
Initial release.
<!-- next-url --> [0.5.0]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.5.0 [0.4.6]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.4.6 [0.4.5]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.4.5 [0.4.4]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.4.4 [0.4.3]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.4.3 [0.4.2]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.4.2 [0.4.1]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.4.1 [0.4.0]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.4.0 [0.3.18]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.18 [0.3.17]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.17 [0.3.16]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.16 [0.3.15]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.15 [0.3.14]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.14 [0.3.13]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.13 [0.3.12]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.12 [0.3.11]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.11 [0.3.10]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.10 [0.3.9]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.9 [0.3.8]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.8 [0.3.7]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.7 [0.3.6]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.6 [0.3.5]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.5 [0.3.4]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.4 [0.3.3]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.3 [0.3.2]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.2 [0.3.1]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.1 [0.3.0]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.3.0 [0.2.1]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.2.1 [0.2.0]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.2.0 [0.1.2]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.1.2 [0.1.1]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.1.1 [0.1.0]: https://github.com/oxidecomputer/iddqd/releases/tag/iddqd-0.1.0
Your coding agent can read these notes before it upgrades. Set up the MCP server →