├── README.md ├── .gitignore ├── crates ├── iddqd │ ├── LICENSE-MIT │ ├── LICENSE-APACHE │ ├── examples │ │ ├── README.md │ │ ├── tri-complex.rs │ │ ├── schemars-examples.rs │ │ ├── bi-complex.rs │ │ └── id-complex.rs │ ├── tests │ │ ├── snapshots │ │ │ └── map_sizes.txt │ │ ├── integration │ │ │ ├── main.rs │ │ │ ├── size_tests.rs │ │ │ └── schemars_tests.rs │ │ └── output │ │ │ ├── bi_hash_map_as_map_schema.json │ │ │ ├── bi_hash_map_schema.json │ │ │ ├── id_hash_map_as_map_schema.json │ │ │ ├── id_hash_map_schema.json │ │ │ ├── id_ord_map_as_map_schema.json │ │ │ ├── id_ord_map_schema.json │ │ │ ├── tri_hash_map_as_map_schema.json │ │ │ ├── tri_hash_map_schema.json │ │ │ ├── simple_container_schema.json │ │ │ ├── container_as_map_schema.json │ │ │ └── container_schema.json │ ├── src │ │ ├── support │ │ │ ├── mod.rs │ │ │ ├── fmt_utils.rs │ │ │ ├── map_hash.rs │ │ │ ├── hash_builder.rs │ │ │ ├── borrow.rs │ │ │ ├── alloc.rs │ │ │ ├── daft_utils.rs │ │ │ ├── schemars_utils.rs │ │ │ └── hash_table.rs │ │ ├── id_ord_map │ │ │ ├── mod.rs │ │ │ ├── tables.rs │ │ │ ├── schemars_impls.rs │ │ │ ├── trait_defs.rs │ │ │ ├── proptest_impls.rs │ │ │ ├── ref_mut.rs │ │ │ ├── iter.rs │ │ │ ├── daft_impls.rs │ │ │ └── serde_impls.rs │ │ ├── tri_hash_map │ │ │ ├── mod.rs │ │ │ ├── schemars_impls.rs │ │ │ ├── tables.rs │ │ │ ├── iter.rs │ │ │ ├── ref_mut.rs │ │ │ └── trait_defs.rs │ │ ├── id_hash_map │ │ │ ├── mod.rs │ │ │ ├── schemars_impls.rs │ │ │ ├── tables.rs │ │ │ ├── trait_defs.rs │ │ │ ├── iter.rs │ │ │ └── ref_mut.rs │ │ ├── bi_hash_map │ │ │ ├── mod.rs │ │ │ ├── schemars_impls.rs │ │ │ ├── entry_indexes.rs │ │ │ ├── tables.rs │ │ │ ├── iter.rs │ │ │ ├── trait_defs.rs │ │ │ └── ref_mut.rs │ │ ├── internal.rs │ │ └── errors.rs │ └── Cargo.toml ├── iddqd-benches │ ├── README.md │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── benches │ │ └── benches.rs ├── iddqd-extended-examples │ ├── src │ │ └── lib.rs │ ├── README.md │ ├── Cargo.toml │ └── examples │ │ ├── typify-types.rs │ │ └── bumpalo-alloc.rs └── iddqd-test-utils │ ├── README.md │ ├── src │ ├── lib.rs │ ├── unwind.rs │ ├── eq_props.rs │ ├── borrowed_item.rs │ ├── serde_utils.rs │ └── naive_map.rs │ └── Cargo.toml ├── .config └── nextest.toml ├── .cargo ├── config.toml └── mutants.toml ├── .github ├── renovate.json └── workflows │ ├── release.yml │ └── ci.yml ├── rustfmt.toml ├── release.toml ├── .claude └── settings.json ├── Justfile ├── LICENSE-MIT └── Cargo.toml /README.md: -------------------------------------------------------------------------------- 1 | crates/iddqd/README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /mutants.out* 3 | -------------------------------------------------------------------------------- /crates/iddqd/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../../LICENSE-MIT -------------------------------------------------------------------------------- /crates/iddqd/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../../LICENSE-APACHE -------------------------------------------------------------------------------- /crates/iddqd-benches/README.md: -------------------------------------------------------------------------------- 1 | # iddqd-benches 2 | 3 | Benchmarks for iddqd. 4 | -------------------------------------------------------------------------------- /crates/iddqd-extended-examples/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Dummy lib.rs to satisfy `cargo test --doc`. 2 | -------------------------------------------------------------------------------- /crates/iddqd-test-utils/README.md: -------------------------------------------------------------------------------- 1 | # iddqd-test-utils 2 | 3 | Test helpers for the iddqd crate. 4 | -------------------------------------------------------------------------------- /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | [profile.default-miri] 2 | # proptests are too slow to be run through miri 3 | default-filter = "not test(proptest)" 4 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xfmt = "fmt -- --config imports_granularity=Crate --config group_imports=One --config format_code_in_doc_comments=true" 3 | -------------------------------------------------------------------------------- /crates/iddqd-extended-examples/README.md: -------------------------------------------------------------------------------- 1 | # iddqd-extended-examples 2 | 3 | These examples demonstrate iddqd integrations with other crates. They live in a 4 | separate crate due to Cargo limitations. 5 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>oxidecomputer/renovate-config", 5 | "local>oxidecomputer/renovate-config//rust/autocreate", 6 | "local>oxidecomputer/renovate-config//actions/pin" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /crates/iddqd/examples/README.md: -------------------------------------------------------------------------------- 1 | # iddqd examples 2 | 3 | This directory contains some basic examples of iddqd use. 4 | 5 | For more examples including integrations with other crates, see the [extended 6 | examples](https://github.com/oxidecomputer/iddqd/tree/main/crates/iddqd-extended-examples/examples) 7 | directory. 8 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------- 2 | # Stable features that we customize locally 3 | # --------------------------------------------------------------------------- 4 | max_width = 80 5 | use_small_heuristics = "max" 6 | edition = "2021" 7 | style_edition = "2024" 8 | -------------------------------------------------------------------------------- /crates/iddqd/tests/snapshots/map_sizes.txt: -------------------------------------------------------------------------------- 1 | IdHashMap: 80 2 | IdHashMap: 88 3 | 4 | BiHashMap: 112 5 | BiHashMap: 120 6 | 7 | TriHashMap: 144 8 | TriHashMap: 152 9 | 10 | IdOrdMap: 72 11 | -------------------------------------------------------------------------------- /crates/iddqd/tests/integration/main.rs: -------------------------------------------------------------------------------- 1 | mod bi_hash_map; 2 | mod id_hash_map; 3 | #[cfg(feature = "std")] 4 | mod id_ord_map; 5 | #[cfg(feature = "schemars08")] 6 | mod schemars_tests; 7 | #[cfg(all( 8 | feature = "std", 9 | feature = "default-hasher", 10 | target_pointer_width = "64", 11 | not(miri) 12 | ))] 13 | mod size_tests; 14 | mod tri_hash_map; 15 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod alloc; 2 | pub(crate) mod borrow; 3 | #[cfg(feature = "std")] 4 | pub(crate) mod btree_table; 5 | #[cfg(feature = "daft")] 6 | pub(crate) mod daft_utils; 7 | pub(crate) mod fmt_utils; 8 | pub(crate) mod hash_builder; 9 | pub(crate) mod hash_table; 10 | pub(crate) mod item_set; 11 | pub(crate) mod map_hash; 12 | #[cfg(feature = "schemars08")] 13 | pub(crate) mod schemars_utils; 14 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/fmt_utils.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | /// Debug impl for a static string without quotes. 4 | pub(crate) struct StrDisplayAsDebug(pub(crate) &'static str); 5 | 6 | impl fmt::Debug for StrDisplayAsDebug { 7 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 8 | // Use the Display formatter to write the string without quotes. 9 | fmt::Display::fmt(&self.0, f) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | sign-tag = true 2 | shared-version = true 3 | # Required for templates below to work 4 | consolidate-commits = false 5 | pre-release-commit-message = "[{{crate_name}}] version {{version}}" 6 | tag-message = "[{{crate_name}}] version {{version}}" 7 | tag-name = "{{crate_name}}-{{version}}" 8 | publish = false 9 | dependent-version = "upgrade" 10 | pre-release-hook = ["just", "generate-readmes"] 11 | pre-release-replacements = [ 12 | {file="CHANGELOG.md", search="Unreleased", replace="{{version}}"}, 13 | ] -------------------------------------------------------------------------------- /crates/iddqd-test-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod borrowed_item; 2 | pub mod eq_props; 3 | pub mod naive_map; 4 | #[cfg(feature = "serde")] 5 | pub mod serde_utils; 6 | pub mod test_item; 7 | pub mod unwind; 8 | 9 | /// Re-exports the `bumpalo` crate if the `allocator-api2` feature is enabled -- 10 | /// used by doctests. 11 | #[cfg(feature = "allocator-api2")] 12 | pub use bumpalo; 13 | /// Re-exports `serde_json` if the `serde` feature is enabled -- used by 14 | /// doctests. 15 | #[cfg(feature = "serde")] 16 | pub use serde_json; 17 | -------------------------------------------------------------------------------- /.cargo/mutants.toml: -------------------------------------------------------------------------------- 1 | exclude_re = [ 2 | ">::len", 3 | ">::len", 4 | ">::len", 5 | "::fmt", 6 | "BiHashMapTables::with_capacity", 7 | "IdHashMap::is_empty", 8 | "IdHashMapTables::with_capacity", 9 | "Iter<'a>::len", 10 | "MapHashTable::validate", 11 | "OccupiedEntryMut<'a, T>::is_non_unique", 12 | "TriHashMapTables::with_capacity", 13 | ] 14 | -------------------------------------------------------------------------------- /.claude/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "allow": [ 4 | "Bash(git log:*)", 5 | "Bash(jj diff:*)", 6 | "Bash(jj s:*)", 7 | "Bash(cargo nextest run:*)", 8 | "Bash(cargo build:*)", 9 | "Bash(cargo clippy:*)", 10 | "Bash(cargo test:*)", 11 | "Bash(cargo fmt:*)", 12 | "Bash(rustc:*)", 13 | "Bash(cargo --version:*)", 14 | "Bash(cargo check:*)", 15 | "Bash(grep:*)", 16 | "Bash(cargo xfmt:*)" 17 | ], 18 | "deny": [], 19 | "ask": [] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crates/iddqd-test-utils/src/unwind.rs: -------------------------------------------------------------------------------- 1 | use std::panic::AssertUnwindSafe; 2 | 3 | pub fn catch_panic(f: impl FnOnce() -> T) -> Option { 4 | let result = std::panic::catch_unwind(AssertUnwindSafe(f)); 5 | match result { 6 | Ok(value) => Some(value), 7 | Err(err) => { 8 | if let Some(err) = err.downcast_ref::<&str>() { 9 | eprintln!("caught panic: {err}"); 10 | } else { 11 | eprintln!("caught unknown panic"); 12 | } 13 | None 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /crates/iddqd-benches/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iddqd-benches" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | license.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [dependencies] 10 | criterion.workspace = true 11 | foldhash.workspace = true 12 | iddqd = { workspace = true, features = ["default-hasher", "std"] } 13 | iddqd-test-utils = { workspace = true, features = ["std"] } 14 | 15 | [lints] 16 | workspace = true 17 | 18 | [[bench]] 19 | name = "benches" 20 | harness = false 21 | 22 | [package.metadata.release] 23 | release = false 24 | -------------------------------------------------------------------------------- /crates/iddqd-extended-examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iddqd-extended-examples" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | license.workspace = true 6 | rust-version.workspace = true 7 | publish = false 8 | 9 | [lints] 10 | workspace = true 11 | 12 | [dev-dependencies] 13 | bumpalo.workspace = true 14 | iddqd = { workspace = true, features = ["allocator-api2", "default-hasher", "serde"] } 15 | # Not actually required, but works around a Cargo bug that repros with (Rust 16 | # 1.87): 17 | # 18 | # % git checkout caf605b74da0ea8641fff06bce143c591a578ad4 19 | # (repo root)% cargo test 20 | iddqd-test-utils = { workspace = true, features = ["serde"] } 21 | serde.workspace = true 22 | serde_json.workspace = true 23 | typify.workspace = true 24 | 25 | [package.metadata.release] 26 | release = false 27 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/map_hash.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | fmt, 3 | hash::{BuildHasher, Hash}, 4 | }; 5 | 6 | /// Packages up a hash for later validation. 7 | #[derive(Clone)] 8 | pub(crate) struct MapHash { 9 | pub(super) hash: u64, 10 | } 11 | 12 | impl MapHash { 13 | pub(crate) fn new(hash: u64) -> Self { 14 | Self { hash } 15 | } 16 | 17 | pub(crate) fn hash(&self) -> u64 { 18 | self.hash 19 | } 20 | 21 | pub(crate) fn is_same_hash( 22 | &self, 23 | state: &S, 24 | key: K, 25 | ) -> bool { 26 | self.hash == state.hash_one(key) 27 | } 28 | } 29 | 30 | impl fmt::Debug for MapHash { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | f.debug_struct("MapHash") 33 | .field("hash", &self.hash) 34 | .finish_non_exhaustive() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/mod.rs: -------------------------------------------------------------------------------- 1 | //! An ordered map where the keys are part of the values, based on a B-Tree. 2 | //! 3 | //! For more information, see [`IdOrdMap`]. 4 | 5 | #[cfg(feature = "daft")] 6 | mod daft_impls; 7 | mod entry; 8 | pub(crate) mod imp; 9 | mod iter; 10 | #[cfg(feature = "proptest")] 11 | mod proptest_impls; 12 | mod ref_mut; 13 | #[cfg(feature = "schemars08")] 14 | mod schemars_impls; 15 | #[cfg(feature = "serde")] 16 | mod serde_impls; 17 | mod tables; 18 | pub(crate) mod trait_defs; 19 | 20 | #[cfg(feature = "daft")] 21 | pub use daft_impls::Diff; 22 | pub use entry::{Entry, OccupiedEntry, VacantEntry}; 23 | pub use imp::IdOrdMap; 24 | pub use iter::{IntoIter, Iter, IterMut}; 25 | #[cfg(feature = "proptest")] 26 | pub use proptest_impls::{IdOrdMapStrategy, IdOrdMapValueTree, prop_strategy}; 27 | pub use ref_mut::RefMut; 28 | #[cfg(feature = "serde")] 29 | pub use serde_impls::IdOrdMapAsMap; 30 | pub use trait_defs::IdOrdItem; 31 | -------------------------------------------------------------------------------- /crates/iddqd-test-utils/src/eq_props.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// Assert equality properties. 4 | /// 5 | /// The PartialEq algorithms under test are not obviously symmetric or 6 | /// reflexive, so we must ensure in our tests that they are. 7 | #[allow(clippy::eq_op)] 8 | pub fn assert_eq_props(a: T, b: T) { 9 | assert_eq!(a, a, "a == a"); 10 | assert_eq!(b, b, "b == b"); 11 | assert_eq!(a, b, "a == b"); 12 | assert_eq!(b, a, "b == a"); 13 | } 14 | 15 | /// Assert inequality properties. 16 | /// 17 | /// The PartialEq algorithms in this crate are not obviously symmetric or 18 | /// reflexive, so we must ensure in our tests that they are. 19 | #[allow(clippy::eq_op)] 20 | pub fn assert_ne_props(a: T, b: T) { 21 | // Also check reflexivity while we're here. 22 | assert_eq!(a, a, "a == a"); 23 | assert_eq!(b, b, "b == b"); 24 | assert_ne!(a, b, "a != b"); 25 | assert_ne!(b, a, "b != a"); 26 | } 27 | -------------------------------------------------------------------------------- /crates/iddqd-test-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iddqd-test-utils" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | license.workspace = true 6 | publish = false 7 | 8 | [lints] 9 | workspace = true 10 | 11 | [dependencies] 12 | bumpalo = { workspace = true, optional = true } 13 | hugealloc = { workspace = true, optional = true } 14 | iddqd.workspace = true 15 | proptest.workspace = true 16 | serde = { workspace = true, features = ["derive"], optional = true } 17 | serde_json = { workspace = true, optional = true } 18 | test-strategy.workspace = true 19 | 20 | [features] 21 | # We test once with allocator-api2 + hugealloc, and once without it and with the 22 | # default allocator. 23 | allocator-api2 = ["iddqd/allocator-api2", "dep:bumpalo", "dep:hugealloc"] 24 | # We test once with the default hasher, and once with std's RandomState. 25 | default-hasher = ["iddqd/default-hasher"] 26 | std = ["iddqd/std"] 27 | serde = ["dep:serde", "dep:serde_json", "iddqd/serde"] 28 | 29 | [package.metadata.release] 30 | release = false 31 | -------------------------------------------------------------------------------- /crates/iddqd-benches/src/lib.rs: -------------------------------------------------------------------------------- 1 | use iddqd::{IdHashItem, IdOrdItem, id_upcast}; 2 | 3 | pub struct RecordOwnedU32 { 4 | pub index: u32, 5 | pub data: String, 6 | } 7 | 8 | impl IdHashItem for RecordOwnedU32 { 9 | type Key<'a> = u32; 10 | 11 | fn key(&self) -> Self::Key<'_> { 12 | self.index 13 | } 14 | 15 | id_upcast!(); 16 | } 17 | 18 | impl IdOrdItem for RecordOwnedU32 { 19 | type Key<'a> = u32; 20 | 21 | fn key(&self) -> Self::Key<'_> { 22 | self.index 23 | } 24 | 25 | id_upcast!(); 26 | } 27 | 28 | pub struct RecordBorrowedU32 { 29 | pub index: u32, 30 | pub data: String, 31 | } 32 | 33 | impl IdHashItem for RecordBorrowedU32 { 34 | type Key<'a> = &'a u32; 35 | 36 | fn key(&self) -> Self::Key<'_> { 37 | &self.index 38 | } 39 | 40 | id_upcast!(); 41 | } 42 | 43 | impl IdOrdItem for RecordBorrowedU32 { 44 | type Key<'a> = &'a u32; 45 | 46 | fn key(&self) -> Self::Key<'_> { 47 | &self.index 48 | } 49 | 50 | id_upcast!(); 51 | } 52 | -------------------------------------------------------------------------------- /crates/iddqd/src/tri_hash_map/mod.rs: -------------------------------------------------------------------------------- 1 | //! A hash map where values are uniquely indexed by three keys. 2 | //! 3 | //! For more information, see [`TriHashMap`]. 4 | 5 | #[cfg(feature = "daft")] 6 | mod daft_impls; 7 | pub(crate) mod imp; 8 | mod iter; 9 | #[cfg(feature = "proptest")] 10 | mod proptest_impls; 11 | mod ref_mut; 12 | #[cfg(feature = "schemars08")] 13 | mod schemars_impls; 14 | #[cfg(feature = "serde")] 15 | mod serde_impls; 16 | mod tables; 17 | pub(crate) mod trait_defs; 18 | 19 | #[cfg(feature = "daft")] 20 | pub use daft_impls::{ByK1, ByK2, ByK3, Diff, MapLeaf}; 21 | pub use imp::TriHashMap; 22 | pub use iter::{IntoIter, Iter, IterMut}; 23 | #[cfg(all(feature = "proptest", feature = "default-hasher"))] 24 | pub use proptest_impls::prop_strategy; 25 | #[cfg(feature = "proptest")] 26 | pub use proptest_impls::{ 27 | TriHashMapStrategy, TriHashMapValueTree, prop_strategy_with_hasher, 28 | prop_strategy_with_hasher_in, 29 | }; 30 | pub use ref_mut::RefMut; 31 | #[cfg(feature = "serde")] 32 | pub use serde_impls::TriHashMapAsMap; 33 | pub use trait_defs::TriHashItem; 34 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_hash_map/mod.rs: -------------------------------------------------------------------------------- 1 | //! A hash map where keys are part of the values. 2 | //! 3 | //! For more information, see [`IdHashMap`]. 4 | 5 | #[cfg(feature = "daft")] 6 | mod daft_impls; 7 | mod entry; 8 | pub(crate) mod imp; 9 | mod iter; 10 | #[cfg(feature = "proptest")] 11 | mod proptest_impls; 12 | mod ref_mut; 13 | #[cfg(feature = "schemars08")] 14 | mod schemars_impls; 15 | #[cfg(feature = "serde")] 16 | mod serde_impls; 17 | mod tables; 18 | pub(crate) mod trait_defs; 19 | 20 | #[cfg(feature = "daft")] 21 | pub use daft_impls::Diff; 22 | pub use entry::{Entry, OccupiedEntry, VacantEntry}; 23 | pub use imp::IdHashMap; 24 | pub use iter::{IntoIter, Iter, IterMut}; 25 | #[cfg(all(feature = "proptest", feature = "default-hasher"))] 26 | pub use proptest_impls::prop_strategy; 27 | #[cfg(feature = "proptest")] 28 | pub use proptest_impls::{ 29 | IdHashMapStrategy, IdHashMapValueTree, prop_strategy_with_hasher, 30 | prop_strategy_with_hasher_in, 31 | }; 32 | pub use ref_mut::RefMut; 33 | #[cfg(feature = "serde")] 34 | pub use serde_impls::IdHashMapAsMap; 35 | pub use trait_defs::IdHashItem; 36 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/bi_hash_map_as_map_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "BiHashMapAsMap", 4 | "type": "object", 5 | "additionalProperties": { 6 | "$ref": "#/definitions/TestUser" 7 | }, 8 | "x-rust-type": { 9 | "crate": "iddqd", 10 | "parameters": [ 11 | { 12 | "$ref": "#/definitions/TestUser" 13 | } 14 | ], 15 | "path": "iddqd::BiHashMap", 16 | "version": "*" 17 | }, 18 | "definitions": { 19 | "TestUser": { 20 | "type": "object", 21 | "required": [ 22 | "age", 23 | "email", 24 | "id", 25 | "name" 26 | ], 27 | "properties": { 28 | "age": { 29 | "type": "integer", 30 | "format": "uint32", 31 | "minimum": 0.0 32 | }, 33 | "email": { 34 | "type": "string" 35 | }, 36 | "id": { 37 | "type": "integer", 38 | "format": "uint32", 39 | "minimum": 0.0 40 | }, 41 | "name": { 42 | "type": "string" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/bi_hash_map_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "BiHashMap", 4 | "type": "array", 5 | "items": { 6 | "$ref": "#/definitions/TestUser" 7 | }, 8 | "uniqueItems": true, 9 | "x-rust-type": { 10 | "crate": "iddqd", 11 | "parameters": [ 12 | { 13 | "$ref": "#/definitions/TestUser" 14 | } 15 | ], 16 | "path": "iddqd::BiHashMap", 17 | "version": "*" 18 | }, 19 | "definitions": { 20 | "TestUser": { 21 | "type": "object", 22 | "required": [ 23 | "age", 24 | "email", 25 | "id", 26 | "name" 27 | ], 28 | "properties": { 29 | "age": { 30 | "type": "integer", 31 | "format": "uint32", 32 | "minimum": 0.0 33 | }, 34 | "email": { 35 | "type": "string" 36 | }, 37 | "id": { 38 | "type": "integer", 39 | "format": "uint32", 40 | "minimum": 0.0 41 | }, 42 | "name": { 43 | "type": "string" 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/id_hash_map_as_map_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "IdHashMapAsMap", 4 | "type": "object", 5 | "additionalProperties": { 6 | "$ref": "#/definitions/TestUser" 7 | }, 8 | "x-rust-type": { 9 | "crate": "iddqd", 10 | "parameters": [ 11 | { 12 | "$ref": "#/definitions/TestUser" 13 | } 14 | ], 15 | "path": "iddqd::IdHashMap", 16 | "version": "*" 17 | }, 18 | "definitions": { 19 | "TestUser": { 20 | "type": "object", 21 | "required": [ 22 | "age", 23 | "email", 24 | "id", 25 | "name" 26 | ], 27 | "properties": { 28 | "age": { 29 | "type": "integer", 30 | "format": "uint32", 31 | "minimum": 0.0 32 | }, 33 | "email": { 34 | "type": "string" 35 | }, 36 | "id": { 37 | "type": "integer", 38 | "format": "uint32", 39 | "minimum": 0.0 40 | }, 41 | "name": { 42 | "type": "string" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/id_hash_map_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "IdHashMap", 4 | "type": "array", 5 | "items": { 6 | "$ref": "#/definitions/TestUser" 7 | }, 8 | "uniqueItems": true, 9 | "x-rust-type": { 10 | "crate": "iddqd", 11 | "parameters": [ 12 | { 13 | "$ref": "#/definitions/TestUser" 14 | } 15 | ], 16 | "path": "iddqd::IdHashMap", 17 | "version": "*" 18 | }, 19 | "definitions": { 20 | "TestUser": { 21 | "type": "object", 22 | "required": [ 23 | "age", 24 | "email", 25 | "id", 26 | "name" 27 | ], 28 | "properties": { 29 | "age": { 30 | "type": "integer", 31 | "format": "uint32", 32 | "minimum": 0.0 33 | }, 34 | "email": { 35 | "type": "string" 36 | }, 37 | "id": { 38 | "type": "integer", 39 | "format": "uint32", 40 | "minimum": 0.0 41 | }, 42 | "name": { 43 | "type": "string" 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/id_ord_map_as_map_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "IdOrdMapAsMap", 4 | "type": "object", 5 | "additionalProperties": { 6 | "$ref": "#/definitions/TestUser" 7 | }, 8 | "x-rust-type": { 9 | "crate": "iddqd", 10 | "parameters": [ 11 | { 12 | "$ref": "#/definitions/TestUser" 13 | } 14 | ], 15 | "path": "iddqd::IdOrdMap", 16 | "version": "*" 17 | }, 18 | "definitions": { 19 | "TestUser": { 20 | "type": "object", 21 | "required": [ 22 | "age", 23 | "email", 24 | "id", 25 | "name" 26 | ], 27 | "properties": { 28 | "age": { 29 | "type": "integer", 30 | "format": "uint32", 31 | "minimum": 0.0 32 | }, 33 | "email": { 34 | "type": "string" 35 | }, 36 | "id": { 37 | "type": "integer", 38 | "format": "uint32", 39 | "minimum": 0.0 40 | }, 41 | "name": { 42 | "type": "string" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/id_ord_map_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "IdOrdMap", 4 | "type": "array", 5 | "items": { 6 | "$ref": "#/definitions/TestUser" 7 | }, 8 | "uniqueItems": true, 9 | "x-rust-type": { 10 | "crate": "iddqd", 11 | "parameters": [ 12 | { 13 | "$ref": "#/definitions/TestUser" 14 | } 15 | ], 16 | "path": "iddqd::IdOrdMap", 17 | "version": "*" 18 | }, 19 | "definitions": { 20 | "TestUser": { 21 | "type": "object", 22 | "required": [ 23 | "age", 24 | "email", 25 | "id", 26 | "name" 27 | ], 28 | "properties": { 29 | "age": { 30 | "type": "integer", 31 | "format": "uint32", 32 | "minimum": 0.0 33 | }, 34 | "email": { 35 | "type": "string" 36 | }, 37 | "id": { 38 | "type": "integer", 39 | "format": "uint32", 40 | "minimum": 0.0 41 | }, 42 | "name": { 43 | "type": "string" 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/tri_hash_map_as_map_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "TriHashMapAsMap", 4 | "type": "object", 5 | "additionalProperties": { 6 | "$ref": "#/definitions/TestUser" 7 | }, 8 | "x-rust-type": { 9 | "crate": "iddqd", 10 | "parameters": [ 11 | { 12 | "$ref": "#/definitions/TestUser" 13 | } 14 | ], 15 | "path": "iddqd::TriHashMap", 16 | "version": "*" 17 | }, 18 | "definitions": { 19 | "TestUser": { 20 | "type": "object", 21 | "required": [ 22 | "age", 23 | "email", 24 | "id", 25 | "name" 26 | ], 27 | "properties": { 28 | "age": { 29 | "type": "integer", 30 | "format": "uint32", 31 | "minimum": 0.0 32 | }, 33 | "email": { 34 | "type": "string" 35 | }, 36 | "id": { 37 | "type": "integer", 38 | "format": "uint32", 39 | "minimum": 0.0 40 | }, 41 | "name": { 42 | "type": "string" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/tri_hash_map_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "TriHashMap", 4 | "type": "array", 5 | "items": { 6 | "$ref": "#/definitions/TestUser" 7 | }, 8 | "uniqueItems": true, 9 | "x-rust-type": { 10 | "crate": "iddqd", 11 | "parameters": [ 12 | { 13 | "$ref": "#/definitions/TestUser" 14 | } 15 | ], 16 | "path": "iddqd::TriHashMap", 17 | "version": "*" 18 | }, 19 | "definitions": { 20 | "TestUser": { 21 | "type": "object", 22 | "required": [ 23 | "age", 24 | "email", 25 | "id", 26 | "name" 27 | ], 28 | "properties": { 29 | "age": { 30 | "type": "integer", 31 | "format": "uint32", 32 | "minimum": 0.0 33 | }, 34 | "email": { 35 | "type": "string" 36 | }, 37 | "id": { 38 | "type": "integer", 39 | "format": "uint32", 40 | "minimum": 0.0 41 | }, 42 | "name": { 43 | "type": "string" 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | set positional-arguments 2 | 3 | # Note: help messages should be 1 line long as required by just. 4 | 5 | # Print a help message. 6 | help: 7 | just --list 8 | 9 | # Run `cargo hack --feature-powerset` on crates 10 | powerset *args: 11 | NEXTEST_NO_TESTS=pass cargo hack --feature-powerset --workspace "$@" 12 | 13 | # Build docs for crates and direct dependencies 14 | rustdoc *args: 15 | @cargo tree --depth 1 -e normal --prefix none --workspace --all-features \ 16 | | gawk '{ gsub(" v", "@", $0); printf("%s\n", $1); }' \ 17 | | xargs printf -- '-p %s\n' \ 18 | | RUSTC_BOOTSTRAP=1 RUSTDOCFLAGS='--cfg=doc_cfg' xargs cargo doc --no-deps --all-features {{args}} 19 | 20 | # Generate README.md files using `cargo-sync-rdme`. 21 | generate-readmes: 22 | cargo sync-rdme --toolchain nightly-2025-08-31 --workspace --all-features 23 | 24 | # Run cargo release in CI. 25 | ci-cargo-release: 26 | # cargo-release requires a release off a branch. 27 | git checkout -B to-release 28 | cargo release publish --publish --execute --no-confirm --workspace 29 | git checkout - 30 | git branch -D to-release 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Oxide Computer Company 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /crates/iddqd/src/bi_hash_map/mod.rs: -------------------------------------------------------------------------------- 1 | //! A hash map where values are uniquely indexed by two keys. 2 | //! 3 | //! For more information, see [`BiHashMap`]. 4 | 5 | #[cfg(feature = "daft")] 6 | mod daft_impls; 7 | mod entry; 8 | mod entry_indexes; 9 | pub(crate) mod imp; 10 | mod iter; 11 | #[cfg(feature = "proptest")] 12 | mod proptest_impls; 13 | mod ref_mut; 14 | #[cfg(feature = "schemars08")] 15 | mod schemars_impls; 16 | #[cfg(feature = "serde")] 17 | mod serde_impls; 18 | mod tables; 19 | pub(crate) mod trait_defs; 20 | 21 | #[cfg(feature = "daft")] 22 | pub use daft_impls::{ByK1, ByK2, Diff, MapLeaf}; 23 | pub use entry::{ 24 | Entry, OccupiedEntry, OccupiedEntryMut, OccupiedEntryRef, VacantEntry, 25 | }; 26 | pub use imp::BiHashMap; 27 | pub use iter::{IntoIter, Iter, IterMut}; 28 | #[cfg(all(feature = "proptest", feature = "default-hasher"))] 29 | pub use proptest_impls::prop_strategy; 30 | #[cfg(feature = "proptest")] 31 | pub use proptest_impls::{ 32 | BiHashMapStrategy, BiHashMapValueTree, prop_strategy_with_hasher, 33 | prop_strategy_with_hasher_in, 34 | }; 35 | pub use ref_mut::RefMut; 36 | #[cfg(feature = "serde")] 37 | pub use serde_impls::BiHashMapAsMap; 38 | pub use trait_defs::BiHashItem; 39 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/hash_builder.rs: -------------------------------------------------------------------------------- 1 | /// Default hasher for hash map types. 2 | /// 3 | /// To disable this hasher, disable the `default-hasher` feature. 4 | #[cfg(feature = "default-hasher")] 5 | pub type DefaultHashBuilder = foldhash::fast::RandomState; 6 | 7 | #[cfg(not(feature = "default-hasher"))] 8 | mod dummy { 9 | use core::hash::{BuildHasher, Hasher}; 10 | 11 | /// Dummy default hasher for hash map types. 12 | /// 13 | /// The `default-hasher` feature is currently disabled. 14 | #[derive(Clone, Copy, Debug)] 15 | pub enum DefaultHashBuilder {} 16 | 17 | impl BuildHasher for DefaultHashBuilder { 18 | type Hasher = Self; 19 | 20 | fn build_hasher(&self) -> Self::Hasher { 21 | unreachable!("this is an empty enum so self cannot exist") 22 | } 23 | } 24 | 25 | impl Hasher for DefaultHashBuilder { 26 | fn write(&mut self, _bytes: &[u8]) { 27 | unreachable!("this is an empty enum so self cannot exist") 28 | } 29 | 30 | fn finish(&self) -> u64 { 31 | unreachable!("this is an empty enum so self cannot exist") 32 | } 33 | } 34 | } 35 | 36 | #[cfg(not(feature = "default-hasher"))] 37 | pub use dummy::DefaultHashBuilder; 38 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/tables.rs: -------------------------------------------------------------------------------- 1 | use super::IdOrdItem; 2 | use crate::{ 3 | internal::{ValidateCompact, ValidationError}, 4 | support::{btree_table::MapBTreeTable, map_hash::MapHash}, 5 | }; 6 | use core::hash::Hash; 7 | 8 | #[derive(Clone, Debug, Default)] 9 | pub(super) struct IdOrdMapTables { 10 | pub(super) key_to_item: MapBTreeTable, 11 | } 12 | 13 | impl IdOrdMapTables { 14 | pub(super) const fn new() -> Self { 15 | Self { key_to_item: MapBTreeTable::new() } 16 | } 17 | 18 | pub(super) fn state(&self) -> &foldhash::fast::FixedState { 19 | self.key_to_item.state() 20 | } 21 | 22 | #[doc(hidden)] 23 | pub(super) fn validate( 24 | &self, 25 | expected_len: usize, 26 | compactness: ValidateCompact, 27 | ) -> Result<(), ValidationError> { 28 | self.key_to_item.validate(expected_len, compactness).map_err( 29 | |error| ValidationError::Table { name: "key_to_item", error }, 30 | )?; 31 | 32 | Ok(()) 33 | } 34 | 35 | pub(super) fn make_hash<'a, T>(&self, item: &'a T) -> MapHash 36 | where 37 | T::Key<'a>: Hash, 38 | T: 'a + IdOrdItem, 39 | { 40 | self.key_to_item.compute_hash(item.key()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/schemars_impls.rs: -------------------------------------------------------------------------------- 1 | //! Schemars implementations for IdOrdMap. 2 | 3 | use crate::{ 4 | id_ord_map::{ 5 | imp::IdOrdMap, serde_impls::IdOrdMapAsMap, trait_defs::IdOrdItem, 6 | }, 7 | support::schemars_utils::{create_map_schema, create_object_schema}, 8 | }; 9 | use alloc::string::String; 10 | use schemars::{JsonSchema, gen::SchemaGenerator, schema::Schema}; 11 | 12 | impl JsonSchema for IdOrdMap 13 | where 14 | T: JsonSchema + IdOrdItem, 15 | { 16 | fn schema_name() -> String { 17 | alloc::format!("IdOrdMap_of_{}", T::schema_name()) 18 | } 19 | 20 | fn json_schema(generator: &mut SchemaGenerator) -> Schema { 21 | create_map_schema::("IdOrdMap", "iddqd::IdOrdMap", generator) 22 | } 23 | 24 | fn is_referenceable() -> bool { 25 | false 26 | } 27 | } 28 | 29 | impl JsonSchema for IdOrdMapAsMap 30 | where 31 | T: JsonSchema + IdOrdItem, 32 | { 33 | fn schema_name() -> String { 34 | alloc::format!("IdOrdMapAsMap_of_{}", T::schema_name()) 35 | } 36 | 37 | fn json_schema(generator: &mut SchemaGenerator) -> Schema { 38 | create_object_schema::("IdOrdMapAsMap", "iddqd::IdOrdMap", generator) 39 | } 40 | 41 | fn is_referenceable() -> bool { 42 | false 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/simple_container_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "SimpleContainer", 4 | "type": "object", 5 | "required": [ 6 | "users" 7 | ], 8 | "properties": { 9 | "users": { 10 | "title": "IdHashMap", 11 | "type": "array", 12 | "items": { 13 | "$ref": "#/definitions/TestUser" 14 | }, 15 | "uniqueItems": true, 16 | "x-rust-type": { 17 | "crate": "iddqd", 18 | "parameters": [ 19 | { 20 | "$ref": "#/definitions/TestUser" 21 | } 22 | ], 23 | "path": "iddqd::IdHashMap", 24 | "version": "*" 25 | } 26 | } 27 | }, 28 | "definitions": { 29 | "TestUser": { 30 | "type": "object", 31 | "required": [ 32 | "age", 33 | "email", 34 | "id", 35 | "name" 36 | ], 37 | "properties": { 38 | "age": { 39 | "type": "integer", 40 | "format": "uint32", 41 | "minimum": 0.0 42 | }, 43 | "email": { 44 | "type": "string" 45 | }, 46 | "id": { 47 | "type": "integer", 48 | "format": "uint32", 49 | "minimum": 0.0 50 | }, 51 | "name": { 52 | "type": "string" 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # adapted from https://github.com/taiki-e/cargo-hack/blob/main/.github/workflows/release.yml 2 | 3 | name: Publish releases to GitHub 4 | on: 5 | push: 6 | tags: 7 | - "*" 8 | 9 | jobs: 10 | iddqd-release: 11 | if: github.repository_owner == 'oxidecomputer' && startsWith(github.ref_name, 'iddqd-0') 12 | runs-on: ubuntu-latest 13 | environment: release 14 | permissions: 15 | id-token: write # Required for OIDC token exchange 16 | contents: write # Required for creating releases 17 | steps: 18 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 19 | with: 20 | persist-credentials: false 21 | - uses: rust-lang/crates-io-auth-action@v1 22 | id: auth 23 | - name: Install Rust 24 | uses: dtolnay/rust-toolchain@stable 25 | - name: Install cargo release 26 | uses: taiki-e/install-action@62da238c048aa0f865cc5a322082957d34e7fc1a # v2 27 | with: 28 | tool: cargo-release@0.25.17,just 29 | - uses: taiki-e/create-gh-release-action@26b80501670402f1999aff4b934e1574ef2d3705 # v1 30 | with: 31 | prefix: iddqd 32 | changelog: CHANGELOG.md 33 | title: $prefix $version 34 | branch: main 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | - run: just ci-cargo-release 38 | env: 39 | CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} 40 | -------------------------------------------------------------------------------- /crates/iddqd/src/bi_hash_map/schemars_impls.rs: -------------------------------------------------------------------------------- 1 | //! Schemars implementations for BiHashMap. 2 | 3 | use crate::{ 4 | bi_hash_map::{ 5 | imp::BiHashMap, serde_impls::BiHashMapAsMap, trait_defs::BiHashItem, 6 | }, 7 | support::{ 8 | alloc::Allocator, 9 | schemars_utils::{create_map_schema, create_object_schema}, 10 | }, 11 | }; 12 | use alloc::string::String; 13 | use schemars::{JsonSchema, gen::SchemaGenerator, schema::Schema}; 14 | 15 | impl JsonSchema for BiHashMap 16 | where 17 | T: JsonSchema + BiHashItem, 18 | A: Allocator, 19 | { 20 | fn schema_name() -> String { 21 | alloc::format!("BiHashMap_of_{}", T::schema_name()) 22 | } 23 | 24 | fn json_schema(generator: &mut SchemaGenerator) -> Schema { 25 | create_map_schema::("BiHashMap", "iddqd::BiHashMap", generator) 26 | } 27 | 28 | fn is_referenceable() -> bool { 29 | false 30 | } 31 | } 32 | 33 | impl JsonSchema for BiHashMapAsMap 34 | where 35 | T: JsonSchema + BiHashItem, 36 | A: Allocator, 37 | { 38 | fn schema_name() -> String { 39 | alloc::format!("BiHashMapAsMap_of_{}", T::schema_name()) 40 | } 41 | 42 | fn json_schema(generator: &mut SchemaGenerator) -> Schema { 43 | create_object_schema::( 44 | "BiHashMapAsMap", 45 | "iddqd::BiHashMap", 46 | generator, 47 | ) 48 | } 49 | 50 | fn is_referenceable() -> bool { 51 | false 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_hash_map/schemars_impls.rs: -------------------------------------------------------------------------------- 1 | //! Schemars implementations for IdHashMap. 2 | 3 | use crate::{ 4 | id_hash_map::{ 5 | imp::IdHashMap, serde_impls::IdHashMapAsMap, trait_defs::IdHashItem, 6 | }, 7 | support::{ 8 | alloc::Allocator, 9 | schemars_utils::{create_map_schema, create_object_schema}, 10 | }, 11 | }; 12 | use alloc::string::String; 13 | use schemars::{JsonSchema, gen::SchemaGenerator, schema::Schema}; 14 | 15 | impl JsonSchema for IdHashMap 16 | where 17 | T: JsonSchema + IdHashItem, 18 | A: Allocator, 19 | { 20 | fn schema_name() -> String { 21 | alloc::format!("IdHashMap_of_{}", T::schema_name()) 22 | } 23 | 24 | fn json_schema(generator: &mut SchemaGenerator) -> Schema { 25 | create_map_schema::("IdHashMap", "iddqd::IdHashMap", generator) 26 | } 27 | 28 | fn is_referenceable() -> bool { 29 | false 30 | } 31 | } 32 | 33 | impl JsonSchema for IdHashMapAsMap 34 | where 35 | T: JsonSchema + IdHashItem, 36 | A: Allocator, 37 | { 38 | fn schema_name() -> String { 39 | alloc::format!("IdHashMapAsMap_of_{}", T::schema_name()) 40 | } 41 | 42 | fn json_schema(generator: &mut SchemaGenerator) -> Schema { 43 | create_object_schema::( 44 | "IdHashMapAsMap", 45 | "iddqd::IdHashMap", 46 | generator, 47 | ) 48 | } 49 | 50 | fn is_referenceable() -> bool { 51 | false 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/iddqd/src/tri_hash_map/schemars_impls.rs: -------------------------------------------------------------------------------- 1 | //! Schemars implementations for TriHashMap. 2 | 3 | use crate::{ 4 | support::{ 5 | alloc::Allocator, 6 | schemars_utils::{create_map_schema, create_object_schema}, 7 | }, 8 | tri_hash_map::{ 9 | imp::TriHashMap, serde_impls::TriHashMapAsMap, trait_defs::TriHashItem, 10 | }, 11 | }; 12 | use alloc::string::String; 13 | use schemars::{JsonSchema, gen::SchemaGenerator, schema::Schema}; 14 | 15 | impl JsonSchema for TriHashMap 16 | where 17 | T: JsonSchema + TriHashItem, 18 | A: Allocator, 19 | { 20 | fn schema_name() -> String { 21 | alloc::format!("TriHashMap_of_{}", T::schema_name()) 22 | } 23 | 24 | fn json_schema(generator: &mut SchemaGenerator) -> Schema { 25 | create_map_schema::("TriHashMap", "iddqd::TriHashMap", generator) 26 | } 27 | 28 | fn is_referenceable() -> bool { 29 | false 30 | } 31 | } 32 | 33 | impl JsonSchema for TriHashMapAsMap 34 | where 35 | T: JsonSchema + TriHashItem, 36 | A: Allocator, 37 | { 38 | fn schema_name() -> String { 39 | alloc::format!("TriHashMapAsMap_of_{}", T::schema_name()) 40 | } 41 | 42 | fn json_schema(generator: &mut SchemaGenerator) -> Schema { 43 | create_object_schema::( 44 | "TriHashMapAsMap", 45 | "iddqd::TriHashMap", 46 | generator, 47 | ) 48 | } 49 | 50 | fn is_referenceable() -> bool { 51 | false 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/iddqd/src/bi_hash_map/entry_indexes.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug)] 2 | pub(super) enum EntryIndexes { 3 | Unique(usize), 4 | NonUnique { 5 | // Invariant: at least one index is Some, and indexes are different from 6 | // each other. 7 | index1: Option, 8 | index2: Option, 9 | }, 10 | } 11 | 12 | impl EntryIndexes { 13 | #[inline] 14 | pub(super) fn is_unique(&self) -> bool { 15 | matches!(self, EntryIndexes::Unique(_)) 16 | } 17 | 18 | #[inline] 19 | pub(super) fn disjoint_keys(&self) -> DisjointKeys<'_> { 20 | match self { 21 | EntryIndexes::Unique(index) => DisjointKeys::Unique(*index), 22 | EntryIndexes::NonUnique { 23 | index1: Some(index1), 24 | index2: Some(index2), 25 | } => { 26 | debug_assert_ne!( 27 | index1, index2, 28 | "index1 and index2 shouldn't match" 29 | ); 30 | DisjointKeys::Key12([index1, index2]) 31 | } 32 | EntryIndexes::NonUnique { index1: Some(index), index2: None } => { 33 | DisjointKeys::Key1(*index) 34 | } 35 | EntryIndexes::NonUnique { index1: None, index2: Some(index) } => { 36 | DisjointKeys::Key2(*index) 37 | } 38 | EntryIndexes::NonUnique { index1: None, index2: None } => { 39 | unreachable!("At least one index must be Some") 40 | } 41 | } 42 | } 43 | } 44 | 45 | pub(super) enum DisjointKeys<'a> { 46 | Unique(usize), 47 | Key1(usize), 48 | Key2(usize), 49 | Key12([&'a usize; 2]), 50 | } 51 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["crates/*"] 4 | 5 | [workspace.package] 6 | edition = "2021" 7 | license = "MIT OR Apache-2.0" 8 | rust-version = "1.81" 9 | 10 | [workspace.lints.clippy] 11 | undocumented_unsafe_blocks = { level = "warn" } 12 | 13 | [workspace.lints.rust] 14 | unexpected_cfgs = { level = "warn", check-cfg = ["cfg(doc_cfg)"] } 15 | 16 | [workspace.dependencies] 17 | allocator-api2 = { version = "0.2.21", default-features = false, features = ["alloc"] } 18 | bumpalo = { version = "3.19.0", features = ["allocator-api2", "collections"] } 19 | criterion = "0.7.0" 20 | daft = { version = "0.1.3", default-features = false } 21 | equivalent = "1.0.2" 22 | expectorate = "1.2.0" 23 | foldhash = "0.2.0" 24 | # We have to turn on hashbrown's allocator-api2 feature even if we don't expose 25 | # it in our public API. There's no way to refer to the hashbrown Allocator trait 26 | # without it. (The alternative would be to define everything twice: if 27 | # allocator-api2 is turned on, then for e.g. IdHashMap, otherwise 28 | # IdHashMap.) 29 | hashbrown = { version = "0.16.0", default-features = false, features = ["allocator-api2", "inline-more"] } 30 | hugealloc = "0.1.1" 31 | iddqd = { path = "crates/iddqd", default-features = false } 32 | iddqd-test-utils = { path = "crates/iddqd-test-utils" } 33 | proptest = { version = "1.7.0", default-features = false, features = ["std"] } 34 | ref-cast = "1.0.24" 35 | rustc-hash = { version = "2.1.1", default-features = false } 36 | schemars = "0.8.22" 37 | serde = "1.0.223" 38 | serde_core = "1.0.223" 39 | serde_json = "1.0.145" 40 | test-strategy = "0.4.3" 41 | typify = "0.4.2" 42 | 43 | [profile.dev] 44 | # Builds with opt-level 1 speed up test runs by around 20x. 45 | opt-level = 1 46 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_hash_map/tables.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | IdHashItem, 3 | internal::{ValidateCompact, ValidationError}, 4 | support::{alloc::Allocator, hash_table::MapHashTable, map_hash::MapHash}, 5 | }; 6 | use core::hash::BuildHasher; 7 | 8 | #[derive(Clone, Debug, Default)] 9 | pub(super) struct IdHashMapTables { 10 | pub(super) state: S, 11 | pub(super) key_to_item: MapHashTable, 12 | } 13 | 14 | impl IdHashMapTables { 15 | #[cfg(feature = "daft")] 16 | pub(crate) fn hasher(&self) -> &S { 17 | &self.state 18 | } 19 | 20 | pub(super) const fn with_hasher_in(hasher: S, alloc: A) -> Self { 21 | Self { state: hasher, key_to_item: MapHashTable::new_in(alloc) } 22 | } 23 | 24 | pub(super) fn with_capacity_and_hasher_in( 25 | capacity: usize, 26 | hasher: S, 27 | alloc: A, 28 | ) -> Self { 29 | Self { 30 | state: hasher, 31 | key_to_item: MapHashTable::with_capacity_in(capacity, alloc), 32 | } 33 | } 34 | 35 | pub(super) fn validate( 36 | &self, 37 | expected_len: usize, 38 | compactness: ValidateCompact, 39 | ) -> Result<(), ValidationError> { 40 | self.key_to_item.validate(expected_len, compactness).map_err( 41 | |error| ValidationError::Table { name: "key_to_table", error }, 42 | )?; 43 | 44 | Ok(()) 45 | } 46 | 47 | pub(super) fn make_hash(&self, item: &T) -> MapHash { 48 | let k1 = item.key(); 49 | self.key_to_item.compute_hash(&self.state, k1) 50 | } 51 | 52 | pub(super) fn make_key_hash( 53 | &self, 54 | key: &T::Key<'_>, 55 | ) -> MapHash { 56 | self.key_to_item.compute_hash(&self.state, key) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/iddqd-extended-examples/examples/typify-types.rs: -------------------------------------------------------------------------------- 1 | //! Example showing how to use the `x-rust-type` extension with [typify]. 2 | //! 3 | //! For this example, we use the `simple_container_schema.json` file generated 4 | //! by one of our tests. 5 | 6 | use iddqd::{IdHashItem, id_upcast}; 7 | use typify::import_types; 8 | 9 | import_types!( 10 | // Import types from the schema file. 11 | schema = "../iddqd/tests/output/simple_container_schema.json", 12 | // Add iddqd to your dependency list, and specify that you have the "iddqd" 13 | // crate available. 14 | crates = { 15 | "iddqd" = "0.3.17", 16 | }, 17 | ); 18 | 19 | // You'll have to either implement the iddqd traits for the item types (in this 20 | // case, TestUser), or use `replace` if the original type is available. 21 | // 22 | // If you're implementing the trait yourself, be sure to match the key type(s) 23 | // with the original implementation! Information about which field(s) form the 24 | // key is not part of the schema. 25 | impl IdHashItem for TestUser { 26 | type Key<'a> = &'a str; 27 | 28 | fn key(&self) -> Self::Key<'_> { 29 | &self.name 30 | } 31 | 32 | id_upcast!(); 33 | } 34 | 35 | fn main() { 36 | // Here's an example JSON value that represents an IdHashMap serialized as a 37 | // JSON array. 38 | let value = serde_json::json!({ 39 | "users": [{ 40 | "id": 20, 41 | "name": "Alice", 42 | "age": 30, 43 | "email": "alice@example.com" 44 | }], 45 | }); 46 | 47 | // Deserialize the value into a IdHashMap. 48 | let container = serde_json::from_value::(value).unwrap(); 49 | 50 | // Get the user from the `IdHashMap`. 51 | let user = container.users.get("Alice").unwrap(); 52 | println!("user: {user:?}"); 53 | } 54 | -------------------------------------------------------------------------------- /crates/iddqd-test-utils/src/borrowed_item.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "std")] 2 | use iddqd::IdOrdItem; 3 | use iddqd::{ 4 | BiHashItem, IdHashItem, TriHashItem, bi_upcast, id_upcast, tri_upcast, 5 | }; 6 | use std::{borrow::Cow, path::Path}; 7 | 8 | #[derive(Clone, Debug, PartialEq, Eq)] 9 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 10 | pub struct BorrowedItem<'a> { 11 | pub key1: &'a str, 12 | pub key2: Cow<'a, [u8]>, 13 | pub key3: &'a Path, 14 | } 15 | 16 | impl<'a> IdHashItem for BorrowedItem<'a> { 17 | type Key<'k> 18 | = &'a str 19 | where 20 | Self: 'k; 21 | 22 | fn key(&self) -> Self::Key<'_> { 23 | self.key1 24 | } 25 | 26 | id_upcast!(); 27 | } 28 | 29 | #[cfg(feature = "std")] 30 | impl<'a> IdOrdItem for BorrowedItem<'a> { 31 | type Key<'k> 32 | = &'a str 33 | where 34 | Self: 'k; 35 | 36 | fn key(&self) -> Self::Key<'_> { 37 | self.key1 38 | } 39 | 40 | id_upcast!(); 41 | } 42 | 43 | impl<'a> BiHashItem for BorrowedItem<'a> { 44 | type K1<'k> 45 | = &'a str 46 | where 47 | Self: 'k; 48 | type K2<'k> 49 | = &'k [u8] 50 | where 51 | Self: 'k; 52 | 53 | fn key1(&self) -> Self::K1<'_> { 54 | self.key1 55 | } 56 | 57 | fn key2(&self) -> Self::K2<'_> { 58 | &*self.key2 59 | } 60 | 61 | bi_upcast!(); 62 | } 63 | 64 | impl<'a> TriHashItem for BorrowedItem<'a> { 65 | type K1<'k> 66 | = &'a str 67 | where 68 | Self: 'k; 69 | type K2<'k> 70 | = &'k [u8] 71 | where 72 | Self: 'k; 73 | type K3<'k> 74 | = &'a Path 75 | where 76 | Self: 'k; 77 | 78 | fn key1(&self) -> Self::K1<'_> { 79 | self.key1 80 | } 81 | 82 | fn key2(&self) -> Self::K2<'_> { 83 | &*self.key2 84 | } 85 | 86 | fn key3(&self) -> Self::K3<'_> { 87 | self.key3 88 | } 89 | 90 | tri_upcast!(); 91 | } 92 | -------------------------------------------------------------------------------- /crates/iddqd/tests/integration/size_tests.rs: -------------------------------------------------------------------------------- 1 | use iddqd::{BiHashMap, IdHashMap, IdOrdMap, TriHashMap}; 2 | use iddqd_test_utils::test_item::TestItem; 3 | use std::collections::hash_map::RandomState; 4 | 5 | #[test] 6 | fn test_map_sizes() { 7 | let mut output = String::new(); 8 | 9 | let id_hash_map_default_size = std::mem::size_of::>(); 10 | output.push_str(&format!( 11 | "IdHashMap: {}\n", 12 | id_hash_map_default_size 13 | )); 14 | 15 | let id_hash_map_random_size = 16 | std::mem::size_of::>(); 17 | output.push_str(&format!( 18 | "IdHashMap: {}\n", 19 | id_hash_map_random_size 20 | )); 21 | output.push('\n'); 22 | 23 | let bi_hash_map_default_size = std::mem::size_of::>(); 24 | output.push_str(&format!( 25 | "BiHashMap: {}\n", 26 | bi_hash_map_default_size 27 | )); 28 | 29 | let bi_hash_map_random_size = 30 | std::mem::size_of::>(); 31 | output.push_str(&format!( 32 | "BiHashMap: {}\n", 33 | bi_hash_map_random_size 34 | )); 35 | output.push('\n'); 36 | 37 | let tri_hash_map_default_size = std::mem::size_of::>(); 38 | output.push_str(&format!( 39 | "TriHashMap: {}\n", 40 | tri_hash_map_default_size 41 | )); 42 | 43 | let tri_hash_map_random_size = 44 | std::mem::size_of::>(); 45 | output.push_str(&format!( 46 | "TriHashMap: {}\n", 47 | tri_hash_map_random_size 48 | )); 49 | output.push('\n'); 50 | 51 | let id_ord_map_size = std::mem::size_of::>(); 52 | output.push_str(&format!("IdOrdMap: {}\n", id_ord_map_size)); 53 | 54 | expectorate::assert_contents("tests/snapshots/map_sizes.txt", &output); 55 | } 56 | -------------------------------------------------------------------------------- /crates/iddqd/src/internal.rs: -------------------------------------------------------------------------------- 1 | /// Re-export `Global` if allocator-api2 isn't enabled; it's not public but is 2 | /// used within tests. 3 | pub use crate::support::alloc::Global; 4 | use alloc::string::String; 5 | use core::fmt; 6 | 7 | /// For validation, indicate whether we expect integer tables to be compact 8 | /// (have all values in the range 0..table.len()). 9 | /// 10 | /// Maps are expected to be compact if no remove operations were performed. 11 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 12 | pub enum ValidateCompact { 13 | Compact, 14 | NonCompact, 15 | } 16 | 17 | /// For validation, indicates whether chaos testing is in effect. 18 | /// 19 | /// If it is, then we fall back to linear searches for table lookups. 20 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 21 | pub enum ValidateChaos { 22 | Yes, 23 | No, 24 | } 25 | 26 | #[derive(Debug)] 27 | pub enum ValidationError { 28 | Table { name: &'static str, error: TableValidationError }, 29 | General(String), 30 | } 31 | 32 | impl ValidationError { 33 | pub(crate) fn general(msg: impl Into) -> Self { 34 | ValidationError::General(msg.into()) 35 | } 36 | } 37 | 38 | impl fmt::Display for ValidationError { 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 | match self { 41 | Self::Table { name, error } => { 42 | write!(f, "validation error in table {name}: {error}") 43 | } 44 | Self::General(msg) => msg.fmt(f), 45 | } 46 | } 47 | } 48 | 49 | impl core::error::Error for ValidationError { 50 | fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { 51 | match self { 52 | ValidationError::Table { error, .. } => Some(error), 53 | ValidationError::General(_) => None, 54 | } 55 | } 56 | } 57 | 58 | #[derive(Debug)] 59 | pub struct TableValidationError(String); 60 | 61 | impl TableValidationError { 62 | pub(crate) fn new(msg: impl Into) -> Self { 63 | TableValidationError(msg.into()) 64 | } 65 | } 66 | 67 | impl fmt::Display for TableValidationError { 68 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 69 | self.0.fmt(f) 70 | } 71 | } 72 | 73 | impl core::error::Error for TableValidationError {} 74 | -------------------------------------------------------------------------------- /crates/iddqd-extended-examples/examples/bumpalo-alloc.rs: -------------------------------------------------------------------------------- 1 | //! An example demonstrating use of the [`bumpalo`] crate to allocate against. 2 | //! 3 | //! Bumpalo and other custom allocators can be used with iddqd's hash map types. 4 | //! 5 | //! Requires the `allocator-api2` feature in iddqd. 6 | 7 | use bumpalo::Bump; 8 | use iddqd::{IdHashItem, IdHashMap, id_upcast}; 9 | 10 | /// These are the items we'll store in the `IdHashMap`. 11 | #[derive(Clone, Debug, PartialEq, Eq)] 12 | struct MyStruct<'bump> { 13 | // Because bumpalo doesn't run destructors on drop, we use strings and 14 | // vectors provided by the bumpalo crate. See https://docs.rs/bumpalo for 15 | // more. 16 | a: bumpalo::collections::String<'bump>, 17 | b: usize, 18 | c: bumpalo::collections::Vec<'bump, usize>, 19 | } 20 | 21 | /// The map will be indexed uniquely by (b, c). Note that this is a borrowed key 22 | /// that can be constructed efficiently. 23 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 24 | struct MyKey<'a> { 25 | b: usize, 26 | c: &'a [usize], 27 | } 28 | 29 | impl<'bump> IdHashItem for MyStruct<'bump> { 30 | type Key<'a> 31 | = MyKey<'a> 32 | where 33 | Self: 'a; 34 | 35 | fn key(&self) -> Self::Key<'_> { 36 | MyKey { b: self.b, c: &self.c } 37 | } 38 | 39 | id_upcast!(); 40 | } 41 | 42 | fn main() { 43 | // Create a new bumpalo arena. 44 | let bump = Bump::new(); 45 | 46 | // Create a new IdHashMap using the bumpalo allocator. 47 | let mut map = IdHashMap::new_in(&bump); 48 | 49 | // Insert some items into the map. 50 | let v1 = MyStruct { 51 | a: bumpalo::format!(in &bump, "Hello",), 52 | b: 42, 53 | c: bumpalo::vec![in ≎ 1, 2, 3], 54 | }; 55 | map.insert_unique(v1.clone()).unwrap(); 56 | 57 | let v2 = MyStruct { 58 | a: bumpalo::format!(in &bump, "World",), 59 | b: 42, 60 | c: bumpalo::vec![in ≎ 4, 5, 6], 61 | }; 62 | map.insert_unique(v2).unwrap(); 63 | 64 | // Retrieve an item from the map. 65 | let item = map.get(&MyKey { b: 42, c: &[4, 5, 6] }); 66 | println!("retrieved {item:?}"); 67 | assert_eq!(item, Some(&v1)); 68 | 69 | // map cannot live longer than the bumpalo arena, thus ensuring memory 70 | // safety. 71 | } 72 | -------------------------------------------------------------------------------- /crates/iddqd/src/bi_hash_map/tables.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | BiHashItem, 3 | internal::{ValidateCompact, ValidationError}, 4 | support::{ 5 | alloc::{Allocator, Global, global_alloc}, 6 | hash_table::MapHashTable, 7 | map_hash::MapHash, 8 | }, 9 | }; 10 | use core::hash::BuildHasher; 11 | 12 | #[derive(Clone, Debug, Default)] 13 | pub(super) struct BiHashMapTables { 14 | pub(super) state: S, 15 | pub(super) k1_to_item: MapHashTable, 16 | pub(super) k2_to_item: MapHashTable, 17 | } 18 | 19 | impl BiHashMapTables { 20 | pub(super) const fn with_hasher(hasher: S) -> Self { 21 | Self { 22 | state: hasher, 23 | k1_to_item: MapHashTable::new_in(global_alloc()), 24 | k2_to_item: MapHashTable::new_in(global_alloc()), 25 | } 26 | } 27 | } 28 | 29 | impl BiHashMapTables { 30 | pub(super) fn with_capacity_and_hasher_in( 31 | capacity: usize, 32 | hasher: S, 33 | alloc: A, 34 | ) -> Self { 35 | Self { 36 | state: hasher, 37 | k1_to_item: MapHashTable::with_capacity_in(capacity, alloc.clone()), 38 | k2_to_item: MapHashTable::with_capacity_in(capacity, alloc), 39 | } 40 | } 41 | } 42 | 43 | impl BiHashMapTables { 44 | #[cfg(feature = "daft")] 45 | pub(super) fn hasher(&self) -> &S { 46 | &self.state 47 | } 48 | 49 | pub(super) fn validate( 50 | &self, 51 | expected_len: usize, 52 | compactness: ValidateCompact, 53 | ) -> Result<(), ValidationError> { 54 | // Check that all the maps are of the right size. 55 | self.k1_to_item.validate(expected_len, compactness).map_err( 56 | |error| ValidationError::Table { name: "k1_to_table", error }, 57 | )?; 58 | self.k2_to_item.validate(expected_len, compactness).map_err( 59 | |error| ValidationError::Table { name: "k2_to_table", error }, 60 | )?; 61 | 62 | Ok(()) 63 | } 64 | 65 | pub(super) fn make_hashes( 66 | &self, 67 | k1: &T::K1<'_>, 68 | k2: &T::K2<'_>, 69 | ) -> [MapHash; 2] { 70 | let h1 = self.k1_to_item.compute_hash(&self.state, k1); 71 | let h2 = self.k2_to_item.compute_hash(&self.state, k2); 72 | 73 | [h1, h2] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/iddqd/examples/tri-complex.rs: -------------------------------------------------------------------------------- 1 | //! An example demonstrating `TriHashMap` use with complex borrowed keys. 2 | 3 | use iddqd::{TriHashItem, TriHashMap, tri_upcast}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | /// These are the items we'll store in the `TriHashMap`. 7 | #[derive(Clone, Debug, PartialEq, Eq)] 8 | struct MyStruct { 9 | a: String, 10 | b: usize, 11 | c: PathBuf, 12 | d: Vec, 13 | } 14 | 15 | /// The map will be indexed uniquely by (usize, &Path). Note that this is a 16 | /// borrowed key that can be constructed efficiently. 17 | #[derive(Clone, Debug, Hash, Eq, PartialEq)] 18 | struct MyKey1<'a> { 19 | b: usize, 20 | c: &'a Path, 21 | } 22 | 23 | /// The map will also be indexed uniquely by (&Path, &[usize]). 24 | #[derive(Clone, Debug, Hash, Eq, PartialEq)] 25 | struct MyKey2<'a> { 26 | c: &'a Path, 27 | d: &'a [usize], 28 | } 29 | 30 | impl TriHashItem for MyStruct { 31 | type K1<'a> = MyKey1<'a>; 32 | type K2<'a> = MyKey2<'a>; 33 | // And finally, the map will be indexed uniquely by the `a` field, i.e. 34 | // String. (This could also be a borrowed key like `&'a str`, but we're 35 | // using String for this example to demonstrate the use of the `Borrow` 36 | // trait below.) 37 | type K3<'a> = String; 38 | 39 | fn key1(&self) -> Self::K1<'_> { 40 | MyKey1 { b: self.b, c: &self.c } 41 | } 42 | 43 | fn key2(&self) -> Self::K2<'_> { 44 | MyKey2 { c: &self.c, d: &self.d } 45 | } 46 | 47 | fn key3(&self) -> Self::K3<'_> { 48 | self.a.clone() 49 | } 50 | 51 | tri_upcast!(); 52 | } 53 | 54 | fn main() { 55 | // Make a `TriHashMap` with the keys we defined above. 56 | let mut map = TriHashMap::new(); 57 | 58 | let item = MyStruct { 59 | a: "example".to_owned(), 60 | b: 20, 61 | c: PathBuf::from("/"), 62 | d: Vec::new(), 63 | }; 64 | 65 | // Add an item to the map. 66 | map.insert_unique(item.clone()).unwrap(); 67 | 68 | // This item will conflict with the previous one due to the `a` field 69 | // matching. 70 | map.insert_unique(MyStruct { 71 | a: "example".to_owned(), 72 | b: 30, 73 | c: PathBuf::from("/xyz"), 74 | d: vec![0], 75 | }) 76 | .unwrap_err(); 77 | 78 | // Lookups can happen based on any of the keys. For example, we can look up 79 | // an item by the first key. 80 | assert_eq!(map.get1(&MyKey1 { b: 20, c: Path::new("/") }), Some(&item)); 81 | 82 | // We can also look up an item by anything that implements `Borrow`. For 83 | // example, &str for the third key. 84 | assert_eq!(map.get3("example"), Some(&item)); 85 | 86 | // For hash-based maps, iteration yields the items in an arbitrary order. 87 | for item in &map { 88 | println!("item: {item:?}"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/container_as_map_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "ContainerAsMap", 4 | "type": "object", 5 | "required": [ 6 | "users_bi", 7 | "users_hash", 8 | "users_ord", 9 | "users_tri" 10 | ], 11 | "properties": { 12 | "users_bi": { 13 | "title": "BiHashMapAsMap", 14 | "type": "object", 15 | "additionalProperties": { 16 | "$ref": "#/definitions/TestUser" 17 | }, 18 | "x-rust-type": { 19 | "crate": "iddqd", 20 | "parameters": [ 21 | { 22 | "$ref": "#/definitions/TestUser" 23 | } 24 | ], 25 | "path": "iddqd::BiHashMap", 26 | "version": "*" 27 | } 28 | }, 29 | "users_hash": { 30 | "title": "IdHashMapAsMap", 31 | "type": "object", 32 | "additionalProperties": { 33 | "$ref": "#/definitions/TestUser" 34 | }, 35 | "x-rust-type": { 36 | "crate": "iddqd", 37 | "parameters": [ 38 | { 39 | "$ref": "#/definitions/TestUser" 40 | } 41 | ], 42 | "path": "iddqd::IdHashMap", 43 | "version": "*" 44 | } 45 | }, 46 | "users_ord": { 47 | "title": "IdOrdMapAsMap", 48 | "type": "object", 49 | "additionalProperties": { 50 | "$ref": "#/definitions/TestUser" 51 | }, 52 | "x-rust-type": { 53 | "crate": "iddqd", 54 | "parameters": [ 55 | { 56 | "$ref": "#/definitions/TestUser" 57 | } 58 | ], 59 | "path": "iddqd::IdOrdMap", 60 | "version": "*" 61 | } 62 | }, 63 | "users_tri": { 64 | "title": "TriHashMapAsMap", 65 | "type": "object", 66 | "additionalProperties": { 67 | "$ref": "#/definitions/TestUser" 68 | }, 69 | "x-rust-type": { 70 | "crate": "iddqd", 71 | "parameters": [ 72 | { 73 | "$ref": "#/definitions/TestUser" 74 | } 75 | ], 76 | "path": "iddqd::TriHashMap", 77 | "version": "*" 78 | } 79 | } 80 | }, 81 | "definitions": { 82 | "TestUser": { 83 | "type": "object", 84 | "required": [ 85 | "age", 86 | "email", 87 | "id", 88 | "name" 89 | ], 90 | "properties": { 91 | "age": { 92 | "type": "integer", 93 | "format": "uint32", 94 | "minimum": 0.0 95 | }, 96 | "email": { 97 | "type": "string" 98 | }, 99 | "id": { 100 | "type": "integer", 101 | "format": "uint32", 102 | "minimum": 0.0 103 | }, 104 | "name": { 105 | "type": "string" 106 | } 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /crates/iddqd/tests/output/container_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Container", 4 | "type": "object", 5 | "required": [ 6 | "users_bi", 7 | "users_hash", 8 | "users_ord", 9 | "users_tri" 10 | ], 11 | "properties": { 12 | "users_bi": { 13 | "title": "BiHashMap", 14 | "type": "array", 15 | "items": { 16 | "$ref": "#/definitions/TestUser" 17 | }, 18 | "uniqueItems": true, 19 | "x-rust-type": { 20 | "crate": "iddqd", 21 | "parameters": [ 22 | { 23 | "$ref": "#/definitions/TestUser" 24 | } 25 | ], 26 | "path": "iddqd::BiHashMap", 27 | "version": "*" 28 | } 29 | }, 30 | "users_hash": { 31 | "title": "IdHashMap", 32 | "type": "array", 33 | "items": { 34 | "$ref": "#/definitions/TestUser" 35 | }, 36 | "uniqueItems": true, 37 | "x-rust-type": { 38 | "crate": "iddqd", 39 | "parameters": [ 40 | { 41 | "$ref": "#/definitions/TestUser" 42 | } 43 | ], 44 | "path": "iddqd::IdHashMap", 45 | "version": "*" 46 | } 47 | }, 48 | "users_ord": { 49 | "title": "IdOrdMap", 50 | "type": "array", 51 | "items": { 52 | "$ref": "#/definitions/TestUser" 53 | }, 54 | "uniqueItems": true, 55 | "x-rust-type": { 56 | "crate": "iddqd", 57 | "parameters": [ 58 | { 59 | "$ref": "#/definitions/TestUser" 60 | } 61 | ], 62 | "path": "iddqd::IdOrdMap", 63 | "version": "*" 64 | } 65 | }, 66 | "users_tri": { 67 | "title": "TriHashMap", 68 | "type": "array", 69 | "items": { 70 | "$ref": "#/definitions/TestUser" 71 | }, 72 | "uniqueItems": true, 73 | "x-rust-type": { 74 | "crate": "iddqd", 75 | "parameters": [ 76 | { 77 | "$ref": "#/definitions/TestUser" 78 | } 79 | ], 80 | "path": "iddqd::TriHashMap", 81 | "version": "*" 82 | } 83 | } 84 | }, 85 | "definitions": { 86 | "TestUser": { 87 | "type": "object", 88 | "required": [ 89 | "age", 90 | "email", 91 | "id", 92 | "name" 93 | ], 94 | "properties": { 95 | "age": { 96 | "type": "integer", 97 | "format": "uint32", 98 | "minimum": 0.0 99 | }, 100 | "email": { 101 | "type": "string" 102 | }, 103 | "id": { 104 | "type": "integer", 105 | "format": "uint32", 106 | "minimum": 0.0 107 | }, 108 | "name": { 109 | "type": "string" 110 | } 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /crates/iddqd/src/tri_hash_map/tables.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | TriHashItem, 3 | internal::{ValidateCompact, ValidationError}, 4 | support::{ 5 | alloc::{Allocator, Global, global_alloc}, 6 | hash_table::MapHashTable, 7 | map_hash::MapHash, 8 | }, 9 | }; 10 | use core::hash::BuildHasher; 11 | 12 | #[derive(Clone, Debug, Default)] 13 | pub(super) struct TriHashMapTables { 14 | pub(super) state: S, 15 | pub(super) k1_to_item: MapHashTable, 16 | pub(super) k2_to_item: MapHashTable, 17 | pub(super) k3_to_item: MapHashTable, 18 | } 19 | 20 | impl TriHashMapTables { 21 | pub(super) const fn with_hasher(hasher: S) -> Self { 22 | Self { 23 | state: hasher, 24 | k1_to_item: MapHashTable::new_in(global_alloc()), 25 | k2_to_item: MapHashTable::new_in(global_alloc()), 26 | k3_to_item: MapHashTable::new_in(global_alloc()), 27 | } 28 | } 29 | } 30 | 31 | impl TriHashMapTables { 32 | pub(super) fn with_capacity_and_hasher_in( 33 | capacity: usize, 34 | hasher: S, 35 | alloc: A, 36 | ) -> Self { 37 | Self { 38 | state: hasher, 39 | k1_to_item: MapHashTable::with_capacity_in(capacity, alloc.clone()), 40 | k2_to_item: MapHashTable::with_capacity_in(capacity, alloc.clone()), 41 | k3_to_item: MapHashTable::with_capacity_in(capacity, alloc), 42 | } 43 | } 44 | } 45 | 46 | impl TriHashMapTables { 47 | #[cfg(feature = "daft")] 48 | pub(super) fn hasher(&self) -> &S { 49 | &self.state 50 | } 51 | 52 | pub(super) fn validate( 53 | &self, 54 | expected_len: usize, 55 | compactness: ValidateCompact, 56 | ) -> Result<(), ValidationError> { 57 | // Check that all the maps are of the right size. 58 | self.k1_to_item.validate(expected_len, compactness).map_err( 59 | |error| ValidationError::Table { name: "k1_to_table", error }, 60 | )?; 61 | self.k2_to_item.validate(expected_len, compactness).map_err( 62 | |error| ValidationError::Table { name: "k2_to_table", error }, 63 | )?; 64 | self.k3_to_item.validate(expected_len, compactness).map_err( 65 | |error| ValidationError::Table { name: "k3_to_table", error }, 66 | )?; 67 | 68 | Ok(()) 69 | } 70 | 71 | pub(super) fn make_hashes(&self, item: &T) -> [MapHash; 3] { 72 | let k1 = item.key1(); 73 | let k2 = item.key2(); 74 | let k3 = item.key3(); 75 | 76 | let h1 = self.k1_to_item.compute_hash(&self.state, k1); 77 | let h2 = self.k2_to_item.compute_hash(&self.state, k2); 78 | let h3 = self.k3_to_item.compute_hash(&self.state, k3); 79 | 80 | [h1, h2, h3] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/iddqd/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iddqd" 3 | version = "0.3.17" 4 | description = "Maps where keys borrow from values, including bijective and trijective maps." 5 | readme = "README.md" 6 | documentation = "https://docs.rs/iddqd" 7 | repository = "https://github.com/oxidecomputer/iddqd" 8 | keywords = ["iddqd", "id_map", "bijective", "hashmap", "btreemap"] 9 | categories = ["data-structures", "no-std"] 10 | edition.workspace = true 11 | license.workspace = true 12 | rust-version.workspace = true 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | rustdoc-args = ["--cfg=doc_cfg"] 17 | 18 | [lints] 19 | workspace = true 20 | 21 | [dependencies] 22 | # We have to turn on allocator-api2 here even if we don't expose it in our 23 | # public API. Even if the allocator-api2 feature is not enabled, we still rely 24 | # on being able to implement it for our Global type, so we can pass it into 25 | # hashbrown. 26 | allocator-api2 = { workspace = true } 27 | daft = { workspace = true, optional = true } 28 | equivalent.workspace = true 29 | foldhash = { workspace = true, optional = true } 30 | hashbrown.workspace = true 31 | ref-cast = { workspace = true, optional = true } 32 | rustc-hash.workspace = true 33 | schemars = { workspace = true, optional = true } 34 | serde_core = { workspace = true, optional = true } 35 | serde_json = { workspace = true, optional = true } 36 | proptest = { workspace = true, optional = true } 37 | 38 | [dev-dependencies] 39 | expectorate.workspace = true 40 | iddqd-test-utils.workspace = true 41 | proptest.workspace = true 42 | serde.workspace = true 43 | test-strategy.workspace = true 44 | 45 | [features] 46 | allocator-api2 = ["iddqd-test-utils/allocator-api2"] 47 | daft = ["dep:daft", "dep:ref-cast"] 48 | default = ["allocator-api2", "std", "default-hasher"] 49 | default-hasher = ["dep:foldhash", "iddqd-test-utils/default-hasher"] 50 | proptest = ["dep:proptest"] 51 | schemars08 = ["dep:schemars", "dep:serde_json", "serde"] 52 | serde = ["dep:serde_core", "iddqd-test-utils/serde"] 53 | std = ["dep:foldhash", "iddqd-test-utils/std", "rustc-hash/std"] 54 | 55 | # Internal-only feature for testing that schemars/preserve_order works. 56 | internal-schemars08-preserve-order = ["schemars08", "schemars/preserve_order"] 57 | 58 | [package.metadata.cargo-sync-rdme.badge.badges] 59 | license = true 60 | crates-io = true 61 | docs-rs = true 62 | rust-version = true 63 | 64 | [[example]] 65 | name = "id-complex" 66 | required-features = ["default-hasher", "std"] 67 | 68 | [[example]] 69 | name = "bi-complex" 70 | required-features = ["default-hasher"] 71 | 72 | [[example]] 73 | name = "tri-complex" 74 | required-features = ["default-hasher"] 75 | 76 | [[example]] 77 | name = "schemars-examples" 78 | required-features = ["default-hasher", "schemars08"] 79 | 80 | [package.metadata.release] 81 | pre-release-replacements = [ 82 | { file = "../iddqd-extended-examples/examples/typify-types.rs", search = "iddqd\" = \".*\"", replace = "iddqd\" = \"{{version}}\"", exactly = 1 }, 83 | ] 84 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_hash_map/trait_defs.rs: -------------------------------------------------------------------------------- 1 | use alloc::{boxed::Box, rc::Rc, sync::Arc}; 2 | use core::hash::Hash; 3 | 4 | /// An element stored in an [`IdHashMap`]. 5 | /// 6 | /// This trait is used to define the key type for the map. 7 | /// 8 | /// # Examples 9 | /// 10 | /// ``` 11 | /// # #[cfg(feature = "default-hasher")] { 12 | /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; 13 | /// 14 | /// // Define a struct with a key. 15 | /// #[derive(Debug, PartialEq, Eq, Hash)] 16 | /// struct MyItem { 17 | /// id: String, 18 | /// value: u32, 19 | /// } 20 | /// 21 | /// // Implement IdHashItem for the struct. 22 | /// impl IdHashItem for MyItem { 23 | /// // Keys can borrow from the item. 24 | /// type Key<'a> = &'a str; 25 | /// 26 | /// fn key(&self) -> Self::Key<'_> { 27 | /// &self.id 28 | /// } 29 | /// 30 | /// id_upcast!(); 31 | /// } 32 | /// 33 | /// // Create an IdHashMap and insert items. 34 | /// let mut map = IdHashMap::new(); 35 | /// map.insert_unique(MyItem { id: "foo".to_string(), value: 42 }).unwrap(); 36 | /// map.insert_unique(MyItem { id: "bar".to_string(), value: 20 }).unwrap(); 37 | /// # } 38 | /// ``` 39 | /// 40 | /// [`IdHashMap`]: crate::IdHashMap 41 | pub trait IdHashItem { 42 | /// The key type. 43 | type Key<'a>: Eq + Hash 44 | where 45 | Self: 'a; 46 | 47 | /// Retrieves the key. 48 | fn key(&self) -> Self::Key<'_>; 49 | 50 | /// Upcasts the key to a shorter lifetime, in effect asserting that the 51 | /// lifetime `'a` on [`IdHashItem::Key`] is covariant. 52 | /// 53 | /// Typically implemented via the [`id_upcast`] macro. 54 | fn upcast_key<'short, 'long: 'short>( 55 | long: Self::Key<'long>, 56 | ) -> Self::Key<'short>; 57 | } 58 | 59 | macro_rules! impl_for_ref { 60 | ($type:ty) => { 61 | impl<'b, T: 'b + ?Sized + IdHashItem> IdHashItem for $type { 62 | type Key<'a> 63 | = T::Key<'a> 64 | where 65 | Self: 'a; 66 | 67 | fn key(&self) -> Self::Key<'_> { 68 | (**self).key() 69 | } 70 | 71 | fn upcast_key<'short, 'long: 'short>( 72 | long: Self::Key<'long>, 73 | ) -> Self::Key<'short> 74 | where 75 | Self: 'long, 76 | { 77 | T::upcast_key(long) 78 | } 79 | } 80 | }; 81 | } 82 | 83 | impl_for_ref!(&'b T); 84 | impl_for_ref!(&'b mut T); 85 | 86 | macro_rules! impl_for_box { 87 | ($type:ty) => { 88 | impl IdHashItem for $type { 89 | type Key<'a> 90 | = T::Key<'a> 91 | where 92 | Self: 'a; 93 | 94 | fn key(&self) -> Self::Key<'_> { 95 | (**self).key() 96 | } 97 | 98 | fn upcast_key<'short, 'long: 'short>( 99 | long: Self::Key<'long>, 100 | ) -> Self::Key<'short> { 101 | T::upcast_key(long) 102 | } 103 | } 104 | }; 105 | } 106 | 107 | impl_for_box!(Box); 108 | impl_for_box!(Rc); 109 | impl_for_box!(Arc); 110 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/trait_defs.rs: -------------------------------------------------------------------------------- 1 | //! Trait definitions for `IdOrdMap`. 2 | 3 | use alloc::{boxed::Box, rc::Rc, sync::Arc}; 4 | 5 | /// An element stored in an [`IdOrdMap`]. 6 | /// 7 | /// This trait is used to define the key type for the map. 8 | /// 9 | /// # Examples 10 | /// 11 | /// ``` 12 | /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; 13 | /// 14 | /// // Define a struct with a key. 15 | /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 16 | /// struct MyItem { 17 | /// id: String, 18 | /// value: u32, 19 | /// } 20 | /// 21 | /// // Implement IdOrdItem for the struct. 22 | /// impl IdOrdItem for MyItem { 23 | /// // Keys can borrow from the item. 24 | /// type Key<'a> = &'a str; 25 | /// 26 | /// fn key(&self) -> Self::Key<'_> { 27 | /// &self.id 28 | /// } 29 | /// 30 | /// id_upcast!(); 31 | /// } 32 | /// 33 | /// // Create an IdOrdMap and insert items. 34 | /// let mut map = IdOrdMap::new(); 35 | /// map.insert_unique(MyItem { id: "foo".to_string(), value: 42 }).unwrap(); 36 | /// map.insert_unique(MyItem { id: "bar".to_string(), value: 20 }).unwrap(); 37 | /// ``` 38 | /// 39 | /// [`IdOrdMap`]: crate::IdOrdMap 40 | pub trait IdOrdItem { 41 | /// The key type. 42 | type Key<'a>: Ord 43 | where 44 | Self: 'a; 45 | 46 | /// Retrieves the key. 47 | fn key(&self) -> Self::Key<'_>; 48 | 49 | /// Upcasts the key to a shorter lifetime, in effect asserting that the 50 | /// lifetime `'a` on [`IdOrdItem::Key`] is covariant. 51 | /// 52 | /// Typically implemented via the [`id_upcast`] macro. 53 | /// 54 | /// [`id_upcast`]: crate::id_upcast 55 | fn upcast_key<'short, 'long: 'short>( 56 | long: Self::Key<'long>, 57 | ) -> Self::Key<'short>; 58 | } 59 | 60 | macro_rules! impl_for_ref { 61 | ($type:ty) => { 62 | impl<'b, T: 'b + ?Sized + IdOrdItem> IdOrdItem for $type { 63 | type Key<'a> 64 | = T::Key<'a> 65 | where 66 | Self: 'a; 67 | 68 | fn key(&self) -> Self::Key<'_> { 69 | (**self).key() 70 | } 71 | 72 | fn upcast_key<'short, 'long: 'short>( 73 | long: Self::Key<'long>, 74 | ) -> Self::Key<'short> 75 | where 76 | Self: 'long, 77 | { 78 | T::upcast_key(long) 79 | } 80 | } 81 | }; 82 | } 83 | 84 | impl_for_ref!(&'b T); 85 | impl_for_ref!(&'b mut T); 86 | 87 | macro_rules! impl_for_box { 88 | ($type:ty) => { 89 | impl IdOrdItem for $type { 90 | type Key<'a> 91 | = T::Key<'a> 92 | where 93 | Self: 'a; 94 | 95 | fn key(&self) -> Self::Key<'_> { 96 | (**self).key() 97 | } 98 | 99 | fn upcast_key<'short, 'long: 'short>( 100 | long: Self::Key<'long>, 101 | ) -> Self::Key<'short> { 102 | T::upcast_key(long) 103 | } 104 | } 105 | }; 106 | } 107 | 108 | impl_for_box!(Box); 109 | impl_for_box!(Rc); 110 | impl_for_box!(Arc); 111 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/borrow.rs: -------------------------------------------------------------------------------- 1 | // Adapted from the Rust standard library, which is licensed under MIT OR 2 | // Apache-2.0. 3 | // Copyright (c) The Rust Project Developers 4 | // SPDX-License-Identifier: MIT OR Apache-2.0 5 | 6 | use core::{marker::PhantomData, ptr::NonNull}; 7 | 8 | /// Models a reborrow of some unique reference, when you know that the reborrow 9 | /// and all its descendants (i.e., all pointers and references derived from it) 10 | /// will not be used any more at some point, after which you want to use the 11 | /// original unique reference again. 12 | /// 13 | /// The borrow checker usually handles this stacking of borrows for you, but 14 | /// some control flows that accomplish this stacking are too complicated for 15 | /// the compiler to follow. A `DormantMutRef` allows you to check borrowing 16 | /// yourself, while still expressing its stacked nature, and encapsulating 17 | /// the raw pointer code needed to do this without undefined behavior. 18 | pub(crate) struct DormantMutRef<'a, T> { 19 | ptr: NonNull, 20 | _marker: PhantomData<&'a mut T>, 21 | } 22 | 23 | // SAFETY: DormantMutRef<'a, T> stores exactly a reference to T. The "where" 24 | // clause is that &mut T implements Sync. 25 | unsafe impl<'a, T> Sync for DormantMutRef<'a, T> where &'a mut T: Sync {} 26 | 27 | // SAFETY: DormantMutRef<'a, T> stores exactly a reference to T. The "where" 28 | // clause is that &mut T implements Send. 29 | unsafe impl<'a, T> Send for DormantMutRef<'a, T> where &'a mut T: Send {} 30 | 31 | impl<'a, T> DormantMutRef<'a, T> { 32 | /// Capture a unique borrow, and immediately reborrow it. For the compiler, 33 | /// the lifetime of the new reference is the same as the lifetime of the 34 | /// original reference, but you promise to use it for a shorter period. 35 | pub(crate) fn new(t: &'a mut T) -> (&'a mut T, Self) { 36 | let ptr = NonNull::from(t); 37 | // SAFETY: we hold the borrow throughout 'a via `_marker`, and we expose 38 | // only this reference, so it is unique. 39 | let new_ref = unsafe { &mut *ptr.as_ptr() }; 40 | (new_ref, Self { ptr, _marker: PhantomData }) 41 | } 42 | 43 | /// Revert to the unique borrow initially captured. 44 | /// 45 | /// # Safety 46 | /// 47 | /// The reborrow must have ended, i.e., the reference returned by `new` and 48 | /// all pointers and references derived from it, must not be used anymore. 49 | pub(crate) unsafe fn awaken(self) -> &'a mut T { 50 | // SAFETY: our own safety conditions imply this reference is again unique. 51 | unsafe { &mut *self.ptr.as_ptr() } 52 | } 53 | 54 | /// Borrows a new mutable reference from the unique borrow initially captured. 55 | /// 56 | /// # Safety 57 | /// 58 | /// The reborrow must have ended, i.e., the reference returned by `new` and 59 | /// all pointers and references derived from it, must not be used anymore. 60 | pub(crate) unsafe fn reborrow(&mut self) -> &'a mut T { 61 | // SAFETY: our own safety conditions imply this reference is again unique. 62 | unsafe { &mut *self.ptr.as_ptr() } 63 | } 64 | 65 | /// Borrows a new shared reference from the unique borrow initially captured. 66 | /// 67 | /// # Safety 68 | /// 69 | /// The reborrow must have ended, i.e., the reference returned by `new` and 70 | /// all pointers and references derived from it, must not be used anymore. 71 | pub(crate) unsafe fn reborrow_shared(&self) -> &'a T { 72 | // SAFETY: our own safety conditions imply this reference is again unique. 73 | unsafe { &*self.ptr.as_ptr() } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/iddqd/examples/schemars-examples.rs: -------------------------------------------------------------------------------- 1 | //! Basic usage example for iddqd with schemars. 2 | 3 | use iddqd::{ 4 | BiHashItem, BiHashMap, IdHashItem, IdHashMap, TriHashItem, TriHashMap, 5 | bi_upcast, id_upcast, tri_upcast, 6 | }; 7 | use schemars::{JsonSchema, schema_for}; 8 | use serde::{Deserialize, Serialize}; 9 | use std::collections::HashMap; 10 | 11 | #[derive(Debug, Serialize, Deserialize, JsonSchema)] 12 | struct User { 13 | name: String, 14 | email: String, 15 | age: u32, 16 | } 17 | 18 | impl IdHashItem for User { 19 | type Key<'a> = &'a str; 20 | 21 | fn key(&self) -> Self::Key<'_> { 22 | &self.name 23 | } 24 | 25 | id_upcast!(); 26 | } 27 | 28 | impl BiHashItem for User { 29 | type K1<'a> = &'a str; // name 30 | type K2<'a> = &'a str; // email 31 | 32 | fn key1(&self) -> Self::K1<'_> { 33 | &self.name 34 | } 35 | 36 | fn key2(&self) -> Self::K2<'_> { 37 | &self.email 38 | } 39 | 40 | bi_upcast!(); 41 | } 42 | 43 | impl TriHashItem for User { 44 | type K1<'a> = &'a str; // name 45 | type K2<'a> = &'a str; // email 46 | type K3<'a> = u32; // age 47 | 48 | fn key1(&self) -> Self::K1<'_> { 49 | &self.name 50 | } 51 | 52 | fn key2(&self) -> Self::K2<'_> { 53 | &self.email 54 | } 55 | 56 | fn key3(&self) -> Self::K3<'_> { 57 | self.age 58 | } 59 | 60 | tri_upcast!(); 61 | } 62 | 63 | // Example container struct that uses iddqd maps with schema generation 64 | #[derive(Serialize, Deserialize, JsonSchema)] 65 | struct UserDatabase { 66 | /// Users indexed by name 67 | users_by_name: IdHashMap, 68 | 69 | /// Users with bidirectional lookup by name and email 70 | users_bi: BiHashMap, 71 | 72 | /// Users with three-way lookup by name, email, and age 73 | users_tri: TriHashMap, 74 | 75 | /// Regular HashMap for comparison 76 | metadata: HashMap, 77 | } 78 | 79 | fn main() { 80 | println!("* schemas:\n"); 81 | 82 | // Generate schemas for individual map types. 83 | println!("*** schema for IdHashMap:"); 84 | let id_hash_schema = schema_for!(IdHashMap); 85 | println!("{}\n", serde_json::to_string_pretty(&id_hash_schema).unwrap()); 86 | 87 | println!("*** schema for BiHashMap:"); 88 | let bi_hash_schema = schema_for!(BiHashMap); 89 | println!("{}\n", serde_json::to_string_pretty(&bi_hash_schema).unwrap()); 90 | 91 | println!("*** schema for TriHashMap:"); 92 | let tri_hash_schema = schema_for!(TriHashMap); 93 | println!("{}\n", serde_json::to_string_pretty(&tri_hash_schema).unwrap()); 94 | 95 | // Generate a schema for the container struct. 96 | println!("schema for UserDatabase (container with multiple map types):"); 97 | let database_schema = schema_for!(UserDatabase); 98 | println!("{}\n", serde_json::to_string_pretty(&database_schema).unwrap()); 99 | 100 | let mut users = IdHashMap::new(); 101 | users 102 | .insert_unique(User { 103 | name: "Alice".to_string(), 104 | email: "alice@example.com".to_string(), 105 | age: 30, 106 | }) 107 | .unwrap(); 108 | users 109 | .insert_unique(User { 110 | name: "Bob".to_string(), 111 | email: "bob@example.com".to_string(), 112 | age: 25, 113 | }) 114 | .unwrap(); 115 | 116 | println!("IdHashMap serializes as:"); 117 | println!("{}", serde_json::to_string_pretty(&users).unwrap()); 118 | } 119 | -------------------------------------------------------------------------------- /crates/iddqd/examples/bi-complex.rs: -------------------------------------------------------------------------------- 1 | //! An example demonstrating `BiHashMap` use with complex borrowed keys. 2 | 3 | use iddqd::{BiHashItem, BiHashMap, bi_hash_map::Entry, bi_upcast}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | /// These are the items we'll store in the `BiHashMap`. 7 | #[derive(Clone, Debug, PartialEq, Eq)] 8 | struct MyStruct { 9 | a: String, 10 | b: usize, 11 | c: PathBuf, 12 | d: Vec, 13 | } 14 | 15 | /// The map will be indexed uniquely by (b, c). Note that this is a 16 | /// borrowed key that can be constructed efficiently. 17 | #[derive(Clone, Debug, Hash, Eq, PartialEq)] 18 | struct MyKey1<'a> { 19 | b: usize, 20 | c: &'a Path, 21 | } 22 | 23 | /// The map will also be indexed uniquely by (&Path, &[usize]). 24 | #[derive(Clone, Debug, Hash, Eq, PartialEq)] 25 | struct MyKey2<'a> { 26 | c: &'a Path, 27 | d: &'a [usize], 28 | } 29 | 30 | impl BiHashItem for MyStruct { 31 | type K1<'a> = MyKey1<'a>; 32 | type K2<'a> = MyKey2<'a>; 33 | 34 | fn key1(&self) -> Self::K1<'_> { 35 | MyKey1 { b: self.b, c: &self.c } 36 | } 37 | 38 | fn key2(&self) -> Self::K2<'_> { 39 | MyKey2 { c: &self.c, d: &self.d } 40 | } 41 | 42 | bi_upcast!(); 43 | } 44 | 45 | fn main() { 46 | // Make a `TriHashMap` with the keys we defined above. 47 | let mut map = BiHashMap::new(); 48 | 49 | let item = MyStruct { 50 | a: "example".to_owned(), 51 | b: 20, 52 | c: PathBuf::from("/"), 53 | d: Vec::new(), 54 | }; 55 | 56 | // Add an item to the map. 57 | map.insert_unique(item.clone()).unwrap(); 58 | 59 | // This item will conflict with the previous one due to b and c matching. 60 | map.insert_unique(MyStruct { 61 | a: "something-else".to_owned(), 62 | b: 20, 63 | c: PathBuf::from("/"), 64 | d: vec![1], 65 | }) 66 | .unwrap_err(); 67 | 68 | // Add another item to the map. Note that this item has the same b and d 69 | // but a different c. 70 | let item2 = MyStruct { 71 | a: "example".to_owned(), 72 | b: 10, 73 | c: PathBuf::from("/home"), 74 | d: Vec::new(), 75 | }; 76 | map.insert_unique(item2.clone()).unwrap(); 77 | 78 | // Lookups can happen based on a borrowed key. For example: 79 | assert_eq!(map.get1(&MyKey1 { b: 20, c: Path::new("/") }), Some(&item)); 80 | 81 | // While iterating over the map, items will be returned in arbitrary order. 82 | for item in map.iter() { 83 | println!("{item:?}"); 84 | } 85 | 86 | // This matches by key1 but not key2. 87 | let item3 = MyStruct { 88 | a: "example".to_owned(), 89 | b: 20, 90 | c: PathBuf::from("/"), 91 | d: vec![1, 2, 3], 92 | }; 93 | 94 | // This matches by neither key1 nor key2. 95 | let item4 = MyStruct { 96 | a: "example".to_owned(), 97 | b: 20, 98 | c: PathBuf::from("/home"), 99 | d: vec![1, 2, 3], 100 | }; 101 | 102 | for item in [item, item2, item3, item4] { 103 | let entry = map.entry(item.key1(), item.key2()); 104 | match entry { 105 | Entry::Occupied(entry) => { 106 | // Get the entry's item. 107 | let item = entry.get(); 108 | println!("occupied: {item:?}"); 109 | } 110 | Entry::Vacant(entry) => { 111 | // Insert a new item. 112 | let item_ref = entry.insert(item); 113 | println!("inserted: {item_ref:?}"); 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/alloc.rs: -------------------------------------------------------------------------------- 1 | // Adapted from the hashbrown crate, which is licensed under MIT OR Apache-2.0. 2 | // Copyright (c) 2016-2025 Amanieu d'Antras and others 3 | // SPDX-License-Identifier: MIT OR Apache-2.0 4 | 5 | pub use self::inner::Global; 6 | pub(crate) use self::inner::{AllocWrapper, Allocator, global_alloc}; 7 | 8 | // TODO: support nightly. 9 | 10 | // Basic non-nightly case. 11 | #[cfg(feature = "allocator-api2")] 12 | mod inner { 13 | use allocator_api2::alloc::AllocError; 14 | pub use allocator_api2::alloc::{Allocator, Global, Layout}; 15 | use core::ptr::NonNull; 16 | 17 | #[inline] 18 | pub(crate) const fn global_alloc() -> Global { 19 | Global 20 | } 21 | 22 | #[derive(Clone, Copy, Default)] 23 | pub(crate) struct AllocWrapper(pub(crate) T); 24 | 25 | // SAFETY: These functions just forward to the wrapped allocator. 26 | unsafe impl allocator_api2::alloc::Allocator for AllocWrapper { 27 | #[inline] 28 | fn allocate( 29 | &self, 30 | layout: Layout, 31 | ) -> Result, AllocError> { 32 | allocator_api2::alloc::Allocator::allocate(&self.0, layout) 33 | } 34 | 35 | #[inline] 36 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { 37 | allocator_api2::alloc::Allocator::deallocate(&self.0, ptr, layout); 38 | } 39 | } 40 | } 41 | 42 | // No-defaults case. 43 | #[cfg(not(feature = "allocator-api2"))] 44 | mod inner { 45 | use crate::alloc::alloc::Layout; 46 | use allocator_api2::alloc::AllocError; 47 | use core::ptr::NonNull; 48 | 49 | #[inline] 50 | pub(crate) const fn global_alloc() -> Global { 51 | Global::new() 52 | } 53 | 54 | #[allow(clippy::missing_safety_doc)] // not exposed outside of this crate 55 | pub unsafe trait Allocator { 56 | fn allocate(&self, layout: Layout) 57 | -> Result, AllocError>; 58 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout); 59 | } 60 | 61 | #[derive(Copy, Clone, Default)] 62 | #[doc(hidden)] 63 | pub struct Global(allocator_api2::alloc::Global); 64 | 65 | impl Global { 66 | #[inline] 67 | pub const fn new() -> Self { 68 | Global(allocator_api2::alloc::Global) 69 | } 70 | } 71 | 72 | // SAFETY: These functions just forward to the wrapped allocator. 73 | unsafe impl Allocator for Global { 74 | #[inline] 75 | fn allocate( 76 | &self, 77 | layout: Layout, 78 | ) -> Result, AllocError> { 79 | allocator_api2::alloc::Allocator::allocate(&self.0, layout) 80 | } 81 | 82 | #[inline] 83 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { 84 | allocator_api2::alloc::Allocator::deallocate(&self.0, ptr, layout); 85 | } 86 | } 87 | 88 | #[derive(Clone, Copy, Default)] 89 | pub(crate) struct AllocWrapper(pub(crate) T); 90 | 91 | // SAFETY: These functions just forward to the wrapped allocator. 92 | unsafe impl allocator_api2::alloc::Allocator for AllocWrapper { 93 | #[inline] 94 | fn allocate( 95 | &self, 96 | layout: Layout, 97 | ) -> Result, AllocError> { 98 | Allocator::allocate(&self.0, layout) 99 | } 100 | #[inline] 101 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { 102 | Allocator::deallocate(&self.0, ptr, layout); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/daft_utils.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Deref, DerefMut}; 2 | use daft::{Diffable, Leaf}; 3 | 4 | /// A leaf type similar to [`daft::Leaf`], which statically guarantees that the 5 | /// before and after values have the same key or keys. 6 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 7 | pub struct IdLeaf { 8 | before: T, 9 | after: T, 10 | } 11 | 12 | impl IdLeaf { 13 | pub(crate) fn new(before: T, after: T) -> Self { 14 | IdLeaf { before, after } 15 | } 16 | 17 | /// Returns the `before` value. 18 | #[inline] 19 | pub fn before(&self) -> &T { 20 | &self.before 21 | } 22 | 23 | /// Returns the `after` value. 24 | #[inline] 25 | pub fn after(&self) -> &T { 26 | &self.after 27 | } 28 | 29 | /// Converts self into a [`daft::Leaf`]. 30 | #[inline] 31 | pub fn into_leaf(self) -> Leaf { 32 | Leaf { before: self.before, after: self.after } 33 | } 34 | 35 | /// Converts from `&IdLeaf` to `IdLeaf<&T>`. 36 | #[inline] 37 | pub fn as_ref(&self) -> IdLeaf<&T> { 38 | IdLeaf { before: &self.before, after: &self.after } 39 | } 40 | 41 | /// Converts from `&mut IdLeaf` to `IdLeaf<&mut T>`. 42 | #[inline] 43 | pub fn as_mut(&mut self) -> IdLeaf<&mut T> { 44 | IdLeaf { before: &mut self.before, after: &mut self.after } 45 | } 46 | 47 | /// Converts from `IdLeaf` or `&IdLeaf` to `IdLeaf<&T::Target>`. 48 | #[inline] 49 | pub fn as_deref(&self) -> IdLeaf<&T::Target> 50 | where 51 | T: Deref, 52 | { 53 | IdLeaf { before: &*self.before, after: &*self.after } 54 | } 55 | 56 | /// Converts from `IdLeaf` or `&mut IdLeaf` to `IdLeaf<&mut 57 | /// T::Target>`. 58 | #[inline] 59 | pub fn as_deref_mut(&mut self) -> IdLeaf<&mut T::Target> 60 | where 61 | T: DerefMut, 62 | { 63 | IdLeaf { before: &mut *self.before, after: &mut *self.after } 64 | } 65 | 66 | /// Return true if before is the same as after. 67 | /// 68 | /// This is the same as `self.before() == self.after()`, but is easier to 69 | /// use in a chained series of method calls. 70 | #[inline] 71 | pub fn is_unchanged(&self) -> bool 72 | where 73 | T: Eq, 74 | { 75 | self.before == self.after 76 | } 77 | 78 | /// Return true if before is different from after. 79 | /// 80 | /// This is the same as `self.before != self.after`, but is easier to use in 81 | /// a chained series of method calls. 82 | #[inline] 83 | pub fn is_modified(&self) -> bool 84 | where 85 | T: Eq, 86 | { 87 | self.before != self.after 88 | } 89 | } 90 | 91 | impl<'daft, T: ?Sized + Diffable> IdLeaf<&'daft T> { 92 | /// Perform a diff on [`before`][Self::before] and [`after`][Self::after], 93 | /// returning `T::Diff`. 94 | /// 95 | /// This is useful when `T::Diff` is not a leaf node. 96 | #[inline] 97 | pub fn diff_pair(self) -> T::Diff<'daft> { 98 | self.before.diff(self.after) 99 | } 100 | } 101 | 102 | impl IdLeaf<&T> { 103 | /// Create a clone of the `IdLeaf` with owned values. 104 | #[inline] 105 | pub fn cloned(self) -> IdLeaf 106 | where 107 | T: Clone, 108 | { 109 | IdLeaf { before: self.before.clone(), after: self.after.clone() } 110 | } 111 | 112 | /// Create a copy of the leaf with owned values. 113 | #[inline] 114 | pub fn copied(self) -> IdLeaf 115 | where 116 | T: Copy, 117 | { 118 | IdLeaf { before: *self.before, after: *self.after } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /crates/iddqd/src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Error types for this crate. 2 | //! 3 | //! These types are shared across all map implementations in this crate. 4 | 5 | use alloc::vec::Vec; 6 | use core::fmt; 7 | 8 | /// An item conflicts with existing items. 9 | #[derive(Debug)] 10 | pub struct DuplicateItem { 11 | new: T, 12 | duplicates: Vec, 13 | } 14 | 15 | impl DuplicateItem { 16 | /// Creates a new `DuplicateItem` error. 17 | #[doc(hidden)] 18 | pub fn __internal_new(new: T, duplicates: Vec) -> Self { 19 | DuplicateItem { new, duplicates } 20 | } 21 | 22 | /// Returns the new item that was attempted to be inserted. 23 | #[inline] 24 | pub fn new_item(&self) -> &T { 25 | &self.new 26 | } 27 | 28 | /// Returns the list of items that conflict with the new item. 29 | #[inline] 30 | pub fn duplicates(&self) -> &[D] { 31 | &self.duplicates 32 | } 33 | 34 | /// Converts self into its constituent parts. 35 | pub fn into_parts(self) -> (T, Vec) { 36 | (self.new, self.duplicates) 37 | } 38 | } 39 | 40 | impl DuplicateItem { 41 | /// Converts self to an owned `DuplicateItem` by cloning the list of 42 | /// duplicates. 43 | /// 44 | /// If `T` is `'static`, the owned form is suitable for conversion to 45 | /// `Box`, `anyhow::Error`, and so on. 46 | pub fn into_owned(self) -> DuplicateItem { 47 | DuplicateItem { 48 | new: self.new, 49 | duplicates: self.duplicates.into_iter().cloned().collect(), 50 | } 51 | } 52 | } 53 | 54 | impl fmt::Display for DuplicateItem { 55 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 56 | write!( 57 | f, 58 | "new item: {:?} conflicts with existing: {:?}", 59 | self.new, self.duplicates 60 | ) 61 | } 62 | } 63 | 64 | impl core::error::Error for DuplicateItem {} 65 | 66 | /// The error type for `try_reserve` methods. 67 | /// 68 | /// This wraps the underlying allocation error from the hash table implementation. 69 | #[derive(Clone, PartialEq, Eq, Debug)] 70 | pub struct TryReserveError { 71 | kind: TryReserveErrorKind, 72 | } 73 | 74 | #[derive(Clone, PartialEq, Eq, Debug)] 75 | enum TryReserveErrorKind { 76 | /// Error due to the computed capacity exceeding the collection's maximum 77 | /// (usually `isize::MAX` bytes). 78 | CapacityOverflow, 79 | 80 | /// The memory allocator returned an error 81 | AllocError { 82 | /// The layout of the allocation request that failed 83 | layout: core::alloc::Layout, 84 | }, 85 | } 86 | 87 | impl TryReserveError { 88 | /// Converts from a hashbrown `TryReserveError`. 89 | pub(crate) fn from_hashbrown(error: hashbrown::TryReserveError) -> Self { 90 | let kind = match error { 91 | hashbrown::TryReserveError::CapacityOverflow => { 92 | TryReserveErrorKind::CapacityOverflow 93 | } 94 | hashbrown::TryReserveError::AllocError { layout } => { 95 | TryReserveErrorKind::AllocError { layout } 96 | } 97 | }; 98 | Self { kind } 99 | } 100 | } 101 | 102 | impl fmt::Display for TryReserveError { 103 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 104 | match &self.kind { 105 | TryReserveErrorKind::CapacityOverflow => { 106 | write!(f, "capacity overflow") 107 | } 108 | TryReserveErrorKind::AllocError { .. } => { 109 | write!(f, "memory allocation failed") 110 | } 111 | } 112 | } 113 | } 114 | 115 | impl core::error::Error for TryReserveError {} 116 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | 7 | name: CI 8 | 9 | jobs: 10 | lint: 11 | name: Lint 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | partition: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] 16 | env: 17 | RUSTFLAGS: -D warnings 18 | steps: 19 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 20 | - uses: dtolnay/rust-toolchain@stable 21 | with: 22 | components: rustfmt, clippy 23 | - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 24 | with: 25 | key: partition-${{ matrix.partition }} 26 | - name: Install just, cargo-hack, and cargo-sync-rdme 27 | uses: taiki-e/install-action@v2 28 | with: 29 | tool: just,cargo-hack,cargo-sync-rdme 30 | - name: Lint (clippy) 31 | run: just powerset --partition ${{ matrix.partition }}/10 clippy --all-targets 32 | - name: Lint (rustfmt) 33 | run: cargo xfmt --check 34 | - name: Run rustdoc 35 | run: just rustdoc 36 | # Pin to a specific nightly for compatibility with cargo-sync-rdme. 37 | - name: Install nightly toolchain for cargo-sync-rdme 38 | uses: dtolnay/rust-toolchain@master 39 | with: 40 | toolchain: nightly-2025-08-31 41 | - name: Regenerate readmes 42 | run: just generate-readmes 43 | - name: Check for differences 44 | run: git diff --exit-code 45 | 46 | build-and-test: 47 | name: Build and test 48 | runs-on: ${{ matrix.os }} 49 | strategy: 50 | matrix: 51 | os: [ubuntu-latest] 52 | # 1.81 is the MSRV 53 | rust-version: ["1.81", "stable"] 54 | partition: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] 55 | fail-fast: false 56 | env: 57 | RUSTFLAGS: -D warnings 58 | steps: 59 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 60 | - uses: dtolnay/rust-toolchain@master 61 | with: 62 | toolchain: ${{ matrix.rust-version }} 63 | - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 64 | with: 65 | key: partition-${{ matrix.partition }} 66 | - uses: taiki-e/install-action@cargo-hack 67 | - uses: taiki-e/install-action@just 68 | - uses: taiki-e/install-action@nextest 69 | - name: Build 70 | run: just powerset --partition ${{ matrix.partition }}/10 build 71 | - name: Run tests 72 | run: just powerset --partition ${{ matrix.partition }}/10 nextest run 73 | - name: Doctests 74 | run: just powerset --partition ${{ matrix.partition }}/10 test --doc 75 | 76 | build-no-std: 77 | name: Build on no-std 78 | runs-on: ${{ matrix.os }} 79 | strategy: 80 | matrix: 81 | os: [ubuntu-latest] 82 | # 1.81 is the MSRV 83 | rust-version: ["1.81", "stable"] 84 | fail-fast: false 85 | env: 86 | RUSTFLAGS: -D warnings 87 | steps: 88 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 89 | - uses: dtolnay/rust-toolchain@master 90 | with: 91 | toolchain: ${{ matrix.rust-version }} 92 | - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 93 | - uses: taiki-e/install-action@cross 94 | - name: Check 95 | run: cross check --target thumbv7em-none-eabi --no-default-features -p iddqd 96 | 97 | miri: 98 | name: Run tests with miri 99 | runs-on: ubuntu-latest 100 | env: 101 | RUSTFLAGS: -D warnings 102 | steps: 103 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 104 | - uses: dtolnay/rust-toolchain@master 105 | with: 106 | toolchain: nightly 107 | components: miri 108 | - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 109 | - uses: taiki-e/install-action@cargo-hack 110 | - uses: taiki-e/install-action@nextest 111 | # Run tests for all crates containing unsafe code. Currently, that's just 112 | # iddqd. 113 | - name: Run tests 114 | run: cargo +nightly miri nextest run -p iddqd 115 | - name: Doctests 116 | run: cargo +nightly miri test --doc -p iddqd 117 | -------------------------------------------------------------------------------- /crates/iddqd/examples/id-complex.rs: -------------------------------------------------------------------------------- 1 | //! An example demonstrating `IdOrdMap` use with complex borrowed keys. 2 | 3 | use iddqd::{ 4 | Comparable, Equivalent, IdOrdItem, IdOrdMap, id_ord_map::Entry, id_upcast, 5 | }; 6 | use std::path::{Path, PathBuf}; 7 | 8 | /// These are the items we'll store in the `IdOrdMap`. 9 | #[derive(Clone, Debug, PartialEq, Eq)] 10 | struct MyStruct { 11 | a: String, 12 | b: usize, 13 | c: PathBuf, 14 | d: Vec, 15 | } 16 | 17 | /// The map will be indexed uniquely by (b, c, d). Note that this is a 18 | /// borrowed key that can be constructed efficiently. 19 | #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 20 | struct MyKey<'a> { 21 | b: usize, 22 | c: &'a Path, 23 | d: &'a [usize], 24 | } 25 | 26 | impl IdOrdItem for MyStruct { 27 | type Key<'a> = MyKey<'a>; 28 | 29 | fn key(&self) -> Self::Key<'_> { 30 | MyKey { b: self.b, c: &self.c, d: &self.d } 31 | } 32 | 33 | id_upcast!(); 34 | } 35 | 36 | fn main() { 37 | // Make a `TriHashMap` with the keys we defined above. 38 | let mut map = IdOrdMap::new(); 39 | 40 | let item = MyStruct { 41 | a: "example".to_owned(), 42 | b: 20, 43 | c: PathBuf::from("/"), 44 | d: Vec::new(), 45 | }; 46 | 47 | // Add an item to the map. 48 | map.insert_unique(item.clone()).unwrap(); 49 | 50 | // This item will conflict with the previous one due to b, c and d 51 | // matching. 52 | map.insert_unique(MyStruct { 53 | a: "something-else".to_owned(), 54 | b: 20, 55 | c: PathBuf::from("/"), 56 | d: Vec::new(), 57 | }) 58 | .unwrap_err(); 59 | 60 | // Add another item to the map. Note that this item has the same c and d 61 | // but a different b. 62 | let item2 = MyStruct { 63 | a: "example".to_owned(), 64 | b: 10, 65 | c: PathBuf::from("/"), 66 | d: Vec::new(), 67 | }; 68 | map.insert_unique(item2.clone()).unwrap(); 69 | 70 | // Lookups can happen based on a borrowed key. For example: 71 | assert_eq!( 72 | map.get(&MyKey { b: 20, c: Path::new("/"), d: &[] }), 73 | Some(&item) 74 | ); 75 | 76 | // Values can also be mutated in place, as long as the key type implements 77 | // `Hash`. For example: 78 | { 79 | let mut item = 80 | map.get_mut(&MyKey { b: 20, c: Path::new("/"), d: &[] }).unwrap(); 81 | item.a = "changed".to_owned(); 82 | 83 | // Key changes will be checked when the item is dropped. 84 | } 85 | 86 | // While iterating over the map, items will be sorted by their key. 87 | for item in map.iter() { 88 | println!("{item:?}"); 89 | } 90 | 91 | let item3 = MyStruct { 92 | a: "example".to_owned(), 93 | b: 20, 94 | c: PathBuf::from("/"), 95 | d: vec![1, 2, 3], 96 | }; 97 | 98 | for item in [item, item2, item3.clone()] { 99 | let entry = map.entry(item.key()); 100 | match entry { 101 | Entry::Occupied(entry) => { 102 | // Get the entry's item. 103 | let item = entry.get(); 104 | println!("occupied: {item:?}"); 105 | } 106 | Entry::Vacant(entry) => { 107 | // Insert a new item. 108 | let item_ref = entry.insert_ref(item); 109 | println!("inserted: {item_ref:?}"); 110 | } 111 | } 112 | } 113 | 114 | // Lookups can be done with any key type that implements `Comparable`. This 115 | // is strictly more general than the Borrow you might be used to. For 116 | // example, lookups against an owned key: 117 | struct MyKeyOwned { 118 | b: usize, 119 | c: PathBuf, 120 | d: Vec, 121 | } 122 | 123 | impl Equivalent> for MyKeyOwned { 124 | fn equivalent(&self, other: &MyKey<'_>) -> bool { 125 | self.b == other.b && self.c == other.c && self.d == other.d 126 | } 127 | } 128 | 129 | impl Comparable> for MyKeyOwned { 130 | fn compare(&self, other: &MyKey<'_>) -> std::cmp::Ordering { 131 | self.b 132 | .cmp(&other.b) 133 | .then_with(|| self.c.as_path().cmp(other.c)) 134 | .then_with(|| self.d.as_slice().cmp(other.d)) 135 | } 136 | } 137 | 138 | let key = MyKeyOwned { b: 20, c: PathBuf::from("/"), d: vec![1, 2, 3] }; 139 | assert_eq!(map.get(&key), Some(&item3)); 140 | } 141 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/proptest_impls.rs: -------------------------------------------------------------------------------- 1 | //! Proptest strategies for generating [`IdOrdMap`]s with random inputs. 2 | 3 | use crate::{IdOrdItem, id_ord_map::IdOrdMap}; 4 | use core::fmt; 5 | use proptest::{ 6 | arbitrary::{Arbitrary, StrategyFor, any_with}, 7 | collection::{SizeRange, VecStrategy, VecValueTree}, 8 | strategy::{NewTree, Strategy, ValueTree}, 9 | test_runner::TestRunner, 10 | }; 11 | 12 | /// Strategy to create [`IdOrdMap`]s with a length in a certain range. 13 | /// 14 | /// Created by the [`prop_strategy()`] function. 15 | #[must_use = "strategies do nothing unless used"] 16 | #[derive(Clone)] 17 | pub struct IdOrdMapStrategy 18 | where 19 | T: Strategy, 20 | { 21 | inner: VecStrategy, 22 | } 23 | 24 | impl fmt::Debug for IdOrdMapStrategy 25 | where 26 | T: Strategy, 27 | { 28 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 29 | f.debug_struct("IdOrdMapStrategy") 30 | .field("inner", &self.inner) 31 | .finish_non_exhaustive() 32 | } 33 | } 34 | 35 | /// Creates a strategy to generate [`IdOrdMap`]s containing items drawn from 36 | /// `element` and with a size within the given range. 37 | /// 38 | /// # Examples 39 | /// 40 | /// ``` 41 | /// use iddqd::{IdOrdItem, IdOrdMap, id_ord_map, id_upcast}; 42 | /// use proptest::{ 43 | /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, 44 | /// }; 45 | /// 46 | /// #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 47 | /// struct Person { 48 | /// id: u32, 49 | /// name: String, 50 | /// } 51 | /// 52 | /// impl IdOrdItem for Person { 53 | /// type Key<'a> = u32; 54 | /// 55 | /// fn key(&self) -> Self::Key<'_> { 56 | /// self.id 57 | /// } 58 | /// id_upcast!(); 59 | /// } 60 | /// 61 | /// // Create a strategy using a tuple and mapping it to Person. 62 | /// let strategy = id_ord_map::prop_strategy( 63 | /// (any::(), any::()) 64 | /// .prop_map(|(id, name)| Person { id, name }), 65 | /// 0..=5, 66 | /// ); 67 | /// 68 | /// // The strategy can be used in proptest contexts. 69 | /// let mut runner = TestRunner::default(); 70 | /// let _tree = strategy.new_tree(&mut runner).unwrap(); 71 | /// ``` 72 | pub fn prop_strategy( 73 | element: T, 74 | size: impl Into, 75 | ) -> IdOrdMapStrategy { 76 | IdOrdMapStrategy { inner: proptest::collection::vec(element, size) } 77 | } 78 | 79 | impl<'a, T> Strategy for IdOrdMapStrategy 80 | where 81 | T: Strategy, 82 | T::Value: 'a + IdOrdItem, 83 | ::Key<'a>: fmt::Debug, 84 | { 85 | type Tree = IdOrdMapValueTree; 86 | type Value = IdOrdMap; 87 | 88 | fn new_tree(&self, runner: &mut TestRunner) -> NewTree { 89 | let inner = self.inner.new_tree(runner)?; 90 | 91 | Ok(IdOrdMapValueTree { inner }) 92 | } 93 | } 94 | 95 | /// `ValueTree` corresponding to [`IdOrdMapStrategy`]. 96 | #[derive(Clone)] 97 | pub struct IdOrdMapValueTree 98 | where 99 | T: ValueTree, 100 | { 101 | inner: VecValueTree, 102 | } 103 | 104 | impl fmt::Debug for IdOrdMapValueTree 105 | where 106 | T: ValueTree + fmt::Debug, 107 | { 108 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 109 | f.debug_struct("IdOrdMapValueTree") 110 | .field("inner", &self.inner) 111 | .finish_non_exhaustive() 112 | } 113 | } 114 | 115 | impl<'a, T> ValueTree for IdOrdMapValueTree 116 | where 117 | T: ValueTree, 118 | T::Value: 'a + IdOrdItem, 119 | ::Key<'a>: fmt::Debug, 120 | { 121 | type Value = IdOrdMap; 122 | 123 | fn current(&self) -> Self::Value { 124 | let items = self.inner.current(); 125 | let mut map = IdOrdMap::new(); 126 | 127 | for item in items { 128 | // Use insert_overwrite to handle duplicate keys. 129 | map.insert_overwrite(item); 130 | } 131 | 132 | map 133 | } 134 | 135 | fn simplify(&mut self) -> bool { 136 | self.inner.simplify() 137 | } 138 | 139 | fn complicate(&mut self) -> bool { 140 | self.inner.complicate() 141 | } 142 | } 143 | 144 | impl<'a, T> Arbitrary for IdOrdMap 145 | where 146 | T: 'a + IdOrdItem + Arbitrary, 147 | ::Key<'a>: fmt::Debug, 148 | { 149 | type Parameters = (SizeRange, T::Parameters); 150 | type Strategy = IdOrdMapStrategy>; 151 | 152 | fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { 153 | let (size, element_args) = args; 154 | prop_strategy(any_with::(element_args), size) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_hash_map/iter.rs: -------------------------------------------------------------------------------- 1 | use super::{RefMut, tables::IdHashMapTables}; 2 | use crate::{ 3 | DefaultHashBuilder, IdHashItem, 4 | support::{ 5 | alloc::{AllocWrapper, Allocator, Global}, 6 | item_set::ItemSet, 7 | }, 8 | }; 9 | use core::{hash::BuildHasher, iter::FusedIterator}; 10 | use hashbrown::hash_map; 11 | 12 | /// An iterator over the elements of a [`IdHashMap`] by shared reference. 13 | /// Created by [`IdHashMap::iter`]. 14 | /// 15 | /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed 16 | /// to be stable. 17 | /// 18 | /// [`IdHashMap`]: crate::IdHashMap 19 | /// [`IdHashMap::iter`]: crate::IdHashMap::iter 20 | /// [`HashMap`]: std::collections::HashMap 21 | #[derive(Clone, Debug, Default)] 22 | pub struct Iter<'a, T: IdHashItem> { 23 | inner: hash_map::Values<'a, usize, T>, 24 | } 25 | 26 | impl<'a, T: IdHashItem> Iter<'a, T> { 27 | pub(crate) fn new(items: &'a ItemSet) -> Self { 28 | Self { inner: items.values() } 29 | } 30 | } 31 | 32 | impl<'a, T: IdHashItem> Iterator for Iter<'a, T> { 33 | type Item = &'a T; 34 | 35 | #[inline] 36 | fn next(&mut self) -> Option { 37 | self.inner.next() 38 | } 39 | } 40 | 41 | impl ExactSizeIterator for Iter<'_, T> { 42 | #[inline] 43 | fn len(&self) -> usize { 44 | self.inner.len() 45 | } 46 | } 47 | 48 | // hash_map::Iter is a FusedIterator, so Iter is as well. 49 | impl FusedIterator for Iter<'_, T> {} 50 | 51 | /// An iterator over the elements of a [`IdHashMap`] by mutable reference. 52 | /// Created by [`IdHashMap::iter_mut`]. 53 | /// 54 | /// This iterator returns [`RefMut`] instances. 55 | /// 56 | /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed 57 | /// to be stable. 58 | /// 59 | /// [`IdHashMap`]: crate::IdHashMap 60 | /// [`IdHashMap::iter_mut`]: crate::IdHashMap::iter_mut 61 | /// [`HashMap`]: std::collections::HashMap 62 | #[derive(Debug)] 63 | pub struct IterMut< 64 | 'a, 65 | T: IdHashItem, 66 | S = DefaultHashBuilder, 67 | A: Allocator = Global, 68 | > { 69 | tables: &'a IdHashMapTables, 70 | inner: hash_map::ValuesMut<'a, usize, T>, 71 | } 72 | 73 | impl<'a, T: IdHashItem, S: BuildHasher, A: Allocator> IterMut<'a, T, S, A> { 74 | pub(super) fn new( 75 | tables: &'a IdHashMapTables, 76 | items: &'a mut ItemSet, 77 | ) -> Self { 78 | Self { tables, inner: items.values_mut() } 79 | } 80 | } 81 | 82 | impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> Iterator 83 | for IterMut<'a, T, S, A> 84 | { 85 | type Item = RefMut<'a, T, S>; 86 | 87 | #[inline] 88 | fn next(&mut self) -> Option { 89 | let next = self.inner.next()?; 90 | let hashes = self.tables.make_hash(next); 91 | Some(RefMut::new(self.tables.state.clone(), hashes, next)) 92 | } 93 | } 94 | 95 | impl ExactSizeIterator 96 | for IterMut<'_, T, S, A> 97 | { 98 | #[inline] 99 | fn len(&self) -> usize { 100 | self.inner.len() 101 | } 102 | } 103 | 104 | // hash_map::IterMut is a FusedIterator, so IterMut is as well. 105 | impl FusedIterator 106 | for IterMut<'_, T, S, A> 107 | { 108 | } 109 | 110 | /// An iterator over the elements of a [`IdHashMap`] by ownership. Created by 111 | /// [`IdHashMap::into_iter`]. 112 | /// 113 | /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed 114 | /// to be stable. 115 | /// 116 | /// [`IdHashMap`]: crate::IdHashMap 117 | /// [`IdHashMap::into_iter`]: crate::IdHashMap::into_iter 118 | /// [`HashMap`]: std::collections::HashMap 119 | #[derive(Debug)] 120 | pub struct IntoIter { 121 | inner: hash_map::IntoValues>, 122 | } 123 | 124 | impl IntoIter { 125 | pub(crate) fn new(items: ItemSet) -> Self { 126 | Self { inner: items.into_values() } 127 | } 128 | } 129 | 130 | impl Iterator for IntoIter { 131 | type Item = T; 132 | 133 | #[inline] 134 | fn next(&mut self) -> Option { 135 | self.inner.next() 136 | } 137 | } 138 | 139 | impl ExactSizeIterator for IntoIter { 140 | #[inline] 141 | fn len(&self) -> usize { 142 | self.inner.len() 143 | } 144 | } 145 | 146 | // hash_map::IterMut is a FusedIterator, so IterMut is as well. 147 | impl FusedIterator for IntoIter {} 148 | -------------------------------------------------------------------------------- /crates/iddqd/src/bi_hash_map/iter.rs: -------------------------------------------------------------------------------- 1 | use super::{RefMut, tables::BiHashMapTables}; 2 | use crate::{ 3 | BiHashItem, DefaultHashBuilder, 4 | support::{ 5 | alloc::{AllocWrapper, Allocator, Global}, 6 | item_set::ItemSet, 7 | }, 8 | }; 9 | use core::{hash::BuildHasher, iter::FusedIterator}; 10 | use hashbrown::hash_map; 11 | 12 | /// An iterator over the elements of a [`BiHashMap`] by shared reference. 13 | /// Created by [`BiHashMap::iter`]. 14 | /// 15 | /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed 16 | /// to be stable. 17 | /// 18 | /// [`BiHashMap`]: crate::BiHashMap 19 | /// [`BiHashMap::iter`]: crate::BiHashMap::iter 20 | /// [`HashMap`]: std::collections::HashMap 21 | #[derive(Clone, Debug, Default)] 22 | pub struct Iter<'a, T: BiHashItem> { 23 | inner: hash_map::Values<'a, usize, T>, 24 | } 25 | 26 | impl<'a, T: BiHashItem> Iter<'a, T> { 27 | pub(crate) fn new(items: &'a ItemSet) -> Self { 28 | Self { inner: items.values() } 29 | } 30 | } 31 | 32 | impl<'a, T: BiHashItem> Iterator for Iter<'a, T> { 33 | type Item = &'a T; 34 | 35 | #[inline] 36 | fn next(&mut self) -> Option { 37 | self.inner.next() 38 | } 39 | } 40 | 41 | impl ExactSizeIterator for Iter<'_, T> { 42 | #[inline] 43 | fn len(&self) -> usize { 44 | self.inner.len() 45 | } 46 | } 47 | 48 | // hash_map::Iter is a FusedIterator, so Iter is as well. 49 | impl FusedIterator for Iter<'_, T> {} 50 | 51 | /// An iterator over the elements of a [`BiHashMap`] by mutable reference. 52 | /// Created by [`BiHashMap::iter_mut`]. 53 | /// 54 | /// This iterator returns [`RefMut`] instances. 55 | /// 56 | /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed 57 | /// to be stable. 58 | /// 59 | /// [`BiHashMap`]: crate::BiHashMap 60 | /// [`BiHashMap::iter_mut`]: crate::BiHashMap::iter_mut 61 | /// [`HashMap`]: std::collections::HashMap 62 | #[derive(Debug)] 63 | pub struct IterMut< 64 | 'a, 65 | T: BiHashItem, 66 | S = DefaultHashBuilder, 67 | A: Allocator = Global, 68 | > { 69 | tables: &'a BiHashMapTables, 70 | inner: hash_map::ValuesMut<'a, usize, T>, 71 | } 72 | 73 | impl<'a, T: BiHashItem, S: Clone + BuildHasher, A: Allocator> 74 | IterMut<'a, T, S, A> 75 | { 76 | pub(super) fn new( 77 | tables: &'a BiHashMapTables, 78 | items: &'a mut ItemSet, 79 | ) -> Self { 80 | Self { tables, inner: items.values_mut() } 81 | } 82 | } 83 | 84 | impl<'a, T: BiHashItem, S: Clone + BuildHasher, A: Allocator> Iterator 85 | for IterMut<'a, T, S, A> 86 | { 87 | type Item = RefMut<'a, T, S>; 88 | 89 | #[inline] 90 | fn next(&mut self) -> Option { 91 | let next = self.inner.next()?; 92 | let hashes = self.tables.make_hashes::(&next.key1(), &next.key2()); 93 | Some(RefMut::new(self.tables.state.clone(), hashes, next)) 94 | } 95 | } 96 | 97 | impl ExactSizeIterator 98 | for IterMut<'_, T, S, A> 99 | { 100 | #[inline] 101 | fn len(&self) -> usize { 102 | self.inner.len() 103 | } 104 | } 105 | 106 | // hash_map::IterMut is a FusedIterator, so IterMut is as well. 107 | impl FusedIterator 108 | for IterMut<'_, T, S, A> 109 | { 110 | } 111 | 112 | /// An iterator over the elements of a [`BiHashMap`] by ownership. Created by 113 | /// [`BiHashMap::into_iter`]. 114 | /// 115 | /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed 116 | /// to be stable. 117 | /// 118 | /// [`BiHashMap`]: crate::BiHashMap 119 | /// [`BiHashMap::into_iter`]: crate::BiHashMap::into_iter 120 | /// [`HashMap`]: std::collections::HashMap 121 | #[derive(Debug)] 122 | pub struct IntoIter { 123 | inner: hash_map::IntoValues>, 124 | } 125 | 126 | impl IntoIter { 127 | pub(crate) fn new(items: ItemSet) -> Self { 128 | Self { inner: items.into_values() } 129 | } 130 | } 131 | 132 | impl Iterator for IntoIter { 133 | type Item = T; 134 | 135 | #[inline] 136 | fn next(&mut self) -> Option { 137 | self.inner.next() 138 | } 139 | } 140 | 141 | impl ExactSizeIterator for IntoIter { 142 | #[inline] 143 | fn len(&self) -> usize { 144 | self.inner.len() 145 | } 146 | } 147 | 148 | // hash_map::IterMut is a FusedIterator, so IterMut is as well. 149 | impl FusedIterator for IntoIter {} 150 | -------------------------------------------------------------------------------- /crates/iddqd/src/tri_hash_map/iter.rs: -------------------------------------------------------------------------------- 1 | use super::{RefMut, tables::TriHashMapTables}; 2 | use crate::{ 3 | DefaultHashBuilder, TriHashItem, 4 | support::{ 5 | alloc::{AllocWrapper, Allocator, Global}, 6 | item_set::ItemSet, 7 | }, 8 | }; 9 | use core::{hash::BuildHasher, iter::FusedIterator}; 10 | use hashbrown::hash_map; 11 | 12 | /// An iterator over the elements of a [`TriHashMap`] by shared reference. 13 | /// Created by [`TriHashMap::iter`]. 14 | /// 15 | /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed 16 | /// to be stable. 17 | /// 18 | /// [`TriHashMap`]: crate::TriHashMap 19 | /// [`TriHashMap::iter`]: crate::TriHashMap::iter 20 | /// [`HashMap`]: std::collections::HashMap 21 | #[derive(Clone, Debug, Default)] 22 | pub struct Iter<'a, T: TriHashItem> { 23 | inner: hash_map::Values<'a, usize, T>, 24 | } 25 | 26 | impl<'a, T: TriHashItem> Iter<'a, T> { 27 | pub(crate) fn new(items: &'a ItemSet) -> Self { 28 | Self { inner: items.values() } 29 | } 30 | } 31 | 32 | impl<'a, T: TriHashItem> Iterator for Iter<'a, T> { 33 | type Item = &'a T; 34 | 35 | #[inline] 36 | fn next(&mut self) -> Option { 37 | self.inner.next() 38 | } 39 | } 40 | 41 | impl ExactSizeIterator for Iter<'_, T> { 42 | #[inline] 43 | fn len(&self) -> usize { 44 | self.inner.len() 45 | } 46 | } 47 | 48 | // hash_map::Iter is a FusedIterator, so Iter is as well. 49 | impl FusedIterator for Iter<'_, T> {} 50 | 51 | /// An iterator over the elements of a [`TriHashMap`] by mutable reference. 52 | /// Created by [`TriHashMap::iter_mut`]. 53 | /// 54 | /// This iterator returns [`RefMut`] instances. 55 | /// 56 | /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed 57 | /// to be stable. 58 | /// 59 | /// [`TriHashMap`]: crate::TriHashMap 60 | /// [`TriHashMap::iter_mut`]: crate::TriHashMap::iter_mut 61 | /// [`HashMap`]: std::collections::HashMap 62 | #[derive(Debug)] 63 | pub struct IterMut< 64 | 'a, 65 | T: TriHashItem, 66 | S: Clone + BuildHasher = DefaultHashBuilder, 67 | A: Allocator = Global, 68 | > { 69 | tables: &'a TriHashMapTables, 70 | inner: hash_map::ValuesMut<'a, usize, T>, 71 | } 72 | 73 | impl<'a, T: TriHashItem, S: Clone + BuildHasher, A: Allocator> 74 | IterMut<'a, T, S, A> 75 | { 76 | pub(super) fn new( 77 | tables: &'a TriHashMapTables, 78 | items: &'a mut ItemSet, 79 | ) -> Self { 80 | Self { tables, inner: items.values_mut() } 81 | } 82 | } 83 | 84 | impl<'a, T: TriHashItem, S: Clone + BuildHasher, A: Allocator> Iterator 85 | for IterMut<'a, T, S, A> 86 | { 87 | type Item = RefMut<'a, T, S>; 88 | 89 | #[inline] 90 | fn next(&mut self) -> Option { 91 | let next = self.inner.next()?; 92 | let hashes = self.tables.make_hashes(next); 93 | Some(RefMut::new(self.tables.state.clone(), hashes, next)) 94 | } 95 | } 96 | 97 | impl ExactSizeIterator 98 | for IterMut<'_, T, S, A> 99 | { 100 | #[inline] 101 | fn len(&self) -> usize { 102 | self.inner.len() 103 | } 104 | } 105 | 106 | // hash_map::IterMut is a FusedIterator, so IterMut is as well. 107 | impl FusedIterator 108 | for IterMut<'_, T, S, A> 109 | { 110 | } 111 | 112 | /// An iterator over the elements of a [`TriHashMap`] by ownership. Created by 113 | /// [`TriHashMap::into_iter`]. 114 | /// 115 | /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed 116 | /// to be stable. 117 | /// 118 | /// [`TriHashMap`]: crate::TriHashMap 119 | /// [`TriHashMap::into_iter`]: crate::TriHashMap::into_iter 120 | /// [`HashMap`]: std::collections::HashMap 121 | #[derive(Debug)] 122 | pub struct IntoIter { 123 | inner: hash_map::IntoValues>, 124 | } 125 | 126 | impl IntoIter { 127 | pub(crate) fn new(items: ItemSet) -> Self { 128 | Self { inner: items.into_values() } 129 | } 130 | } 131 | 132 | impl Iterator for IntoIter { 133 | type Item = T; 134 | 135 | #[inline] 136 | fn next(&mut self) -> Option { 137 | self.inner.next() 138 | } 139 | } 140 | 141 | impl ExactSizeIterator for IntoIter { 142 | #[inline] 143 | fn len(&self) -> usize { 144 | self.inner.len() 145 | } 146 | } 147 | 148 | // hash_map::IterMut is a FusedIterator, so IterMut is as well. 149 | impl FusedIterator for IntoIter {} 150 | -------------------------------------------------------------------------------- /crates/iddqd/src/bi_hash_map/trait_defs.rs: -------------------------------------------------------------------------------- 1 | //! Trait definitions for `BiHashMap`. 2 | 3 | use alloc::{boxed::Box, rc::Rc, sync::Arc}; 4 | use core::hash::Hash; 5 | 6 | /// An item in a [`BiHashMap`]. 7 | /// 8 | /// This trait is used to define the keys. 9 | /// 10 | /// # Examples 11 | /// 12 | /// ``` 13 | /// # #[cfg(feature = "default-hasher")] { 14 | /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; 15 | /// 16 | /// // Define a struct with two keys. 17 | /// #[derive(Debug, PartialEq, Eq, Hash)] 18 | /// struct MyPair { 19 | /// id: u32, 20 | /// name: String, 21 | /// } 22 | /// 23 | /// // Implement BiHashItem for the struct. 24 | /// impl BiHashItem for MyPair { 25 | /// type K1<'a> = u32; 26 | /// type K2<'a> = &'a str; 27 | /// 28 | /// fn key1(&self) -> Self::K1<'_> { 29 | /// self.id 30 | /// } 31 | /// 32 | /// fn key2(&self) -> Self::K2<'_> { 33 | /// &self.name 34 | /// } 35 | /// 36 | /// bi_upcast!(); 37 | /// } 38 | /// 39 | /// // Create a BiHashMap and insert items. 40 | /// let mut map = BiHashMap::new(); 41 | /// map.insert_unique(MyPair { id: 1, name: "Alice".to_string() }).unwrap(); 42 | /// map.insert_unique(MyPair { id: 2, name: "Bob".to_string() }).unwrap(); 43 | /// # } 44 | /// ``` 45 | /// 46 | /// [`BiHashMap`]: crate::BiHashMap 47 | pub trait BiHashItem { 48 | /// The first key type. 49 | type K1<'a>: Eq + Hash 50 | where 51 | Self: 'a; 52 | 53 | /// The second key type. 54 | type K2<'a>: Eq + Hash 55 | where 56 | Self: 'a; 57 | 58 | /// Retrieves the first key. 59 | fn key1(&self) -> Self::K1<'_>; 60 | 61 | /// Retrieves the second key. 62 | fn key2(&self) -> Self::K2<'_>; 63 | 64 | /// Upcasts the first key to a shorter lifetime, in effect asserting that 65 | /// the lifetime `'a` on [`BiHashItem::K1`] is covariant. 66 | /// 67 | /// Typically implemented via the [`bi_upcast`] macro. 68 | /// 69 | /// [`bi_upcast`]: crate::bi_upcast 70 | fn upcast_key1<'short, 'long: 'short>( 71 | long: Self::K1<'long>, 72 | ) -> Self::K1<'short>; 73 | 74 | /// Upcasts the second key to a shorter lifetime, in effect asserting that 75 | /// the lifetime `'a` on [`BiHashItem::K2`] is covariant. 76 | /// 77 | /// Typically implemented via the [`bi_upcast`] macro. 78 | /// 79 | /// [`bi_upcast`]: crate::bi_upcast 80 | fn upcast_key2<'short, 'long: 'short>( 81 | long: Self::K2<'long>, 82 | ) -> Self::K2<'short>; 83 | } 84 | 85 | macro_rules! impl_for_ref { 86 | ($type:ty) => { 87 | impl<'b, T: 'b + ?Sized + BiHashItem> BiHashItem for $type { 88 | type K1<'a> 89 | = T::K1<'a> 90 | where 91 | Self: 'a; 92 | type K2<'a> 93 | = T::K2<'a> 94 | where 95 | Self: 'a; 96 | 97 | fn key1(&self) -> Self::K1<'_> { 98 | (**self).key1() 99 | } 100 | 101 | fn key2(&self) -> Self::K2<'_> { 102 | (**self).key2() 103 | } 104 | 105 | fn upcast_key1<'short, 'long: 'short>( 106 | long: Self::K1<'long>, 107 | ) -> Self::K1<'short> 108 | where 109 | Self: 'long, 110 | { 111 | T::upcast_key1(long) 112 | } 113 | 114 | fn upcast_key2<'short, 'long: 'short>( 115 | long: Self::K2<'long>, 116 | ) -> Self::K2<'short> 117 | where 118 | Self: 'long, 119 | { 120 | T::upcast_key2(long) 121 | } 122 | } 123 | }; 124 | } 125 | 126 | impl_for_ref!(&'b T); 127 | impl_for_ref!(&'b mut T); 128 | 129 | macro_rules! impl_for_box { 130 | ($type:ty) => { 131 | impl BiHashItem for $type { 132 | type K1<'a> 133 | = T::K1<'a> 134 | where 135 | Self: 'a; 136 | 137 | type K2<'a> 138 | = T::K2<'a> 139 | where 140 | Self: 'a; 141 | 142 | fn key1(&self) -> Self::K1<'_> { 143 | (**self).key1() 144 | } 145 | 146 | fn key2(&self) -> Self::K2<'_> { 147 | (**self).key2() 148 | } 149 | 150 | fn upcast_key1<'short, 'long: 'short>( 151 | long: Self::K1<'long>, 152 | ) -> Self::K1<'short> { 153 | T::upcast_key1(long) 154 | } 155 | 156 | fn upcast_key2<'short, 'long: 'short>( 157 | long: Self::K2<'long>, 158 | ) -> Self::K2<'short> { 159 | T::upcast_key2(long) 160 | } 161 | } 162 | }; 163 | } 164 | 165 | impl_for_box!(Box); 166 | impl_for_box!(Rc); 167 | impl_for_box!(Arc); 168 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_hash_map/ref_mut.rs: -------------------------------------------------------------------------------- 1 | use crate::{DefaultHashBuilder, IdHashItem, support::map_hash::MapHash}; 2 | use core::{ 3 | fmt, 4 | hash::BuildHasher, 5 | ops::{Deref, DerefMut}, 6 | }; 7 | 8 | /// A mutable reference to an [`IdHashMap`] item. 9 | /// 10 | /// This is a wrapper around a `&mut T` that panics when dropped, if the 11 | /// borrowed value's keys have changed since the wrapper was created. 12 | /// 13 | /// # Change detection 14 | /// 15 | /// It is illegal to change the key of a borrowed `&mut T`. `RefMut` attempts to 16 | /// enforce this invariant. 17 | /// 18 | /// `RefMut` stores the `Hash` output of the key at creation time, and 19 | /// recomputes this hash when it is dropped or when [`Self::into_ref`] is 20 | /// called. If a key changes, there's a small but non-negligible chance that its 21 | /// hash value stays the same[^collision-chance]. In that case, as long as the 22 | /// new key is not the same as another existing one, internal invariants are not 23 | /// violated and the [`IdHashMap`] will continue to work correctly. (But don't 24 | /// rely on this!) 25 | /// 26 | /// It is also possible to deliberately write pathological `Hash` 27 | /// implementations that collide more often. (Don't do this either.) 28 | /// 29 | /// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is 30 | /// called on it. If the key is changed and `mem::forget` is then called on the 31 | /// `RefMut`, the [`IdHashMap`] will stop functioning correctly. This will not 32 | /// introduce memory safety issues, however. 33 | /// 34 | /// The issues here are similar to using interior mutability (e.g. `RefCell` or 35 | /// `Mutex`) to mutate keys in a regular `HashMap`. 36 | /// 37 | /// [`mem::forget`]: std::mem::forget 38 | /// 39 | /// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability 40 | /// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday 41 | /// problem], the probability of a collision by chance reaches 10⁻⁶ within 42 | /// around 6 × 10⁶ elements. 43 | /// 44 | /// [`IdHashMap`]: crate::IdHashMap 45 | /// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table 46 | pub struct RefMut< 47 | 'a, 48 | T: IdHashItem, 49 | S: Clone + BuildHasher = DefaultHashBuilder, 50 | > { 51 | inner: Option>, 52 | } 53 | 54 | impl<'a, T: IdHashItem, S: Clone + BuildHasher> RefMut<'a, T, S> { 55 | pub(super) fn new(state: S, hash: MapHash, borrowed: &'a mut T) -> Self { 56 | Self { inner: Some(RefMutInner { state, hash, borrowed }) } 57 | } 58 | 59 | /// Borrows self into a shorter-lived `RefMut`. 60 | /// 61 | /// This `RefMut` will also check hash equality on drop. 62 | pub fn reborrow(&mut self) -> RefMut<'_, T, S> { 63 | let inner = self.inner.as_mut().unwrap(); 64 | let borrowed = &mut *inner.borrowed; 65 | RefMut::new(inner.state.clone(), inner.hash.clone(), borrowed) 66 | } 67 | 68 | /// Converts this `RefMut` into a `&'a T`. 69 | pub fn into_ref(mut self) -> &'a T { 70 | let inner = self.inner.take().unwrap(); 71 | inner.into_ref() 72 | } 73 | } 74 | 75 | impl Drop for RefMut<'_, T, S> { 76 | fn drop(&mut self) { 77 | if let Some(inner) = self.inner.take() { 78 | inner.into_ref(); 79 | } 80 | } 81 | } 82 | 83 | impl Deref for RefMut<'_, T, S> { 84 | type Target = T; 85 | 86 | fn deref(&self) -> &Self::Target { 87 | self.inner.as_ref().unwrap().borrowed 88 | } 89 | } 90 | 91 | impl DerefMut for RefMut<'_, T, S> { 92 | fn deref_mut(&mut self) -> &mut Self::Target { 93 | self.inner.as_mut().unwrap().borrowed 94 | } 95 | } 96 | 97 | impl fmt::Debug 98 | for RefMut<'_, T, S> 99 | { 100 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 101 | match self.inner { 102 | Some(ref inner) => inner.fmt(f), 103 | None => { 104 | f.debug_struct("RefMut").field("borrowed", &"missing").finish() 105 | } 106 | } 107 | } 108 | } 109 | 110 | struct RefMutInner<'a, T: IdHashItem, S> { 111 | state: S, 112 | hash: MapHash, 113 | borrowed: &'a mut T, 114 | } 115 | 116 | impl<'a, T: IdHashItem, S: BuildHasher> RefMutInner<'a, T, S> { 117 | fn into_ref(self) -> &'a T { 118 | if !self.hash.is_same_hash(&self.state, self.borrowed.key()) { 119 | panic!("key changed during RefMut borrow"); 120 | } 121 | 122 | self.borrowed 123 | } 124 | } 125 | 126 | impl fmt::Debug for RefMutInner<'_, T, S> { 127 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 128 | self.borrowed.fmt(f) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/schemars_utils.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for schemars support. 2 | 3 | use alloc::{ 4 | boxed::Box, 5 | string::{String, ToString}, 6 | }; 7 | use schemars::schema::{ 8 | ArrayValidation, InstanceType, Metadata, ObjectValidation, Schema, 9 | SchemaObject, SingleOrVec, 10 | }; 11 | 12 | /// The crate name for iddqd, used in the x-rust-type extensions. 13 | pub(crate) static IDDQD_CRATE_NAME: &str = "iddqd"; 14 | 15 | /// The crate version for iddqd, used in the x-rust-type extensions. 16 | /// 17 | /// We use * here because we assume map types are going to stay the same 18 | /// across breaking changes. 19 | pub(crate) static IDDQD_CRATE_VERSION: &str = "*"; 20 | 21 | /// Helper function to create array validation for map types. 22 | /// All iddqd map types serialize as arrays of their values. 23 | pub(crate) fn array_validation( 24 | generator: &mut schemars::gen::SchemaGenerator, 25 | ) -> Box 26 | where 27 | T: schemars::JsonSchema, 28 | { 29 | Box::new(ArrayValidation { 30 | items: Some(SingleOrVec::Single(Box::new( 31 | generator.subschema_for::(), 32 | ))), 33 | // Setting unique_items to true here requires a bit of reasoning. For 34 | // two items T1 and T2: 35 | // 36 | // * If T1 == T2 (schema validation fails), then for all keys Key, 37 | // T1::Key == T2::Key (would be rejected by the map). The map's 38 | // behavior is consistent with the schema. 39 | // 40 | // * If T1 != T2 (schema validation succeeds), then there are two 41 | // cases: 42 | // 1. For all keys Key, T1::Key != T2::Key. In this case, the map 43 | // accepts the key. The map's behavior is consistent with the 44 | // schema. 45 | // 2. There is at least one key for which T1::Key == T2::Key. In 46 | // this case, the map will reject the key. 47 | // 48 | // Overall, the map's validation is strictly stronger than the schema. 49 | // This is normal in cases where JSON Schema cannot represent a 50 | // particular kind of validation. 51 | unique_items: Some(true), 52 | ..Default::default() 53 | }) 54 | } 55 | 56 | /// Helper function to create the `extension` table for a given path and 57 | /// type parameter. 58 | pub(crate) fn make_extension_table( 59 | path: &'static str, 60 | generator: &mut schemars::gen::SchemaGenerator, 61 | ) -> schemars::Map 62 | where 63 | T: schemars::JsonSchema, 64 | { 65 | [( 66 | "x-rust-type".to_string(), 67 | serde_json::json!({ 68 | "crate": IDDQD_CRATE_NAME, 69 | "version": IDDQD_CRATE_VERSION, 70 | "path": path, 71 | "parameters": [generator.subschema_for::()] 72 | }), 73 | )] 74 | .into_iter() 75 | .collect() 76 | } 77 | 78 | /// Creates a schema object with common properties for iddqd map types. 79 | pub(crate) fn create_map_schema( 80 | title: &str, 81 | rust_type_path: &'static str, 82 | generator: &mut schemars::gen::SchemaGenerator, 83 | ) -> schemars::schema::Schema 84 | where 85 | T: schemars::JsonSchema, 86 | { 87 | Schema::Object(SchemaObject { 88 | instance_type: Some(InstanceType::Array.into()), 89 | array: Some(array_validation::(generator)), 90 | metadata: Some(Box::new(Metadata { 91 | title: Some(title.to_string()), 92 | ..Default::default() 93 | })), 94 | extensions: make_extension_table::(rust_type_path, generator), 95 | ..Default::default() 96 | }) 97 | } 98 | 99 | /// Helper function to create object validation for map types serialized as objects. 100 | pub(crate) fn object_validation( 101 | generator: &mut schemars::gen::SchemaGenerator, 102 | ) -> Box 103 | where 104 | V: schemars::JsonSchema, 105 | { 106 | Box::new(ObjectValidation { 107 | additional_properties: Some(Box::new(generator.subschema_for::())), 108 | ..Default::default() 109 | }) 110 | } 111 | 112 | /// Creates a schema object for iddqd map types serialized as JSON objects. 113 | /// This is used by the AsMap wrapper types. 114 | pub(crate) fn create_object_schema( 115 | title: &str, 116 | rust_type_path: &'static str, 117 | generator: &mut schemars::gen::SchemaGenerator, 118 | ) -> schemars::schema::Schema 119 | where 120 | V: schemars::JsonSchema, 121 | { 122 | Schema::Object(SchemaObject { 123 | instance_type: Some(InstanceType::Object.into()), 124 | object: Some(object_validation::(generator)), 125 | metadata: Some(Box::new(Metadata { 126 | title: Some(title.to_string()), 127 | ..Default::default() 128 | })), 129 | extensions: make_extension_table::(rust_type_path, generator), 130 | ..Default::default() 131 | }) 132 | } 133 | -------------------------------------------------------------------------------- /crates/iddqd/src/bi_hash_map/ref_mut.rs: -------------------------------------------------------------------------------- 1 | use crate::{BiHashItem, DefaultHashBuilder, support::map_hash::MapHash}; 2 | use core::{ 3 | fmt, 4 | hash::BuildHasher, 5 | ops::{Deref, DerefMut}, 6 | }; 7 | 8 | /// A mutable reference to a [`BiHashMap`] item. 9 | /// 10 | /// This is a wrapper around a `&mut T` that panics when dropped, if the 11 | /// borrowed value's keys have changed since the wrapper was created. 12 | /// 13 | /// # Change detection 14 | /// 15 | /// It is illegal to change the keys of a borrowed `&mut T`. `RefMut` attempts 16 | /// to enforce this invariant. 17 | /// 18 | /// `RefMut` stores the `Hash` output of keys at creation time, and recomputes 19 | /// these hashes when it is dropped or when [`Self::into_ref`] is called. If a 20 | /// key changes, there's a small but non-negligible chance that its hash value 21 | /// stays the same[^collision-chance]. In that case, as long as the new key is 22 | /// not the same as another existing one, internal invariants are not violated 23 | /// and the [`BiHashMap`] will continue to work correctly. (But don't do this!) 24 | /// 25 | /// It is also possible to deliberately write pathological `Hash` 26 | /// implementations that collide more often. (Don't do this either.) 27 | /// 28 | /// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is 29 | /// called on it. If a key is changed and `mem::forget` is then called on the 30 | /// `RefMut`, the `BiHashMap` will stop functioning correctly. This will not 31 | /// introduce memory safety issues, however. 32 | /// 33 | /// The issues here are similar to using interior mutability (e.g. `RefCell` or 34 | /// `Mutex`) to mutate keys in a regular `HashMap`. 35 | /// 36 | /// [`mem::forget`]: std::mem::forget 37 | /// 38 | /// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability 39 | /// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday 40 | /// problem], the probability of a collision by chance reaches 10⁻⁶ within 41 | /// around 6 × 10⁶ elements. 42 | /// 43 | /// [`BiHashMap`]: crate::BiHashMap 44 | /// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table 45 | pub struct RefMut< 46 | 'a, 47 | T: BiHashItem, 48 | S: Clone + BuildHasher = DefaultHashBuilder, 49 | > { 50 | inner: Option>, 51 | } 52 | 53 | impl<'a, T: BiHashItem, S: Clone + BuildHasher> RefMut<'a, T, S> { 54 | pub(super) fn new( 55 | state: S, 56 | hashes: [MapHash; 2], 57 | borrowed: &'a mut T, 58 | ) -> Self { 59 | Self { inner: Some(RefMutInner { state, hashes, borrowed }) } 60 | } 61 | 62 | /// Borrows self into a shorter-lived `RefMut`. 63 | /// 64 | /// This `RefMut` will also check hash equality on drop. 65 | pub fn reborrow(&mut self) -> RefMut<'_, T, S> { 66 | let inner = self.inner.as_mut().unwrap(); 67 | let borrowed = &mut *inner.borrowed; 68 | RefMut::new(inner.state.clone(), inner.hashes.clone(), borrowed) 69 | } 70 | 71 | /// Converts this `RefMut` into a `&'a T`. 72 | pub fn into_ref(mut self) -> &'a T { 73 | let inner = self.inner.take().unwrap(); 74 | inner.into_ref() 75 | } 76 | } 77 | 78 | impl Drop for RefMut<'_, T, S> { 79 | fn drop(&mut self) { 80 | if let Some(inner) = self.inner.take() { 81 | inner.into_ref(); 82 | } 83 | } 84 | } 85 | 86 | impl Deref for RefMut<'_, T, S> { 87 | type Target = T; 88 | 89 | fn deref(&self) -> &Self::Target { 90 | self.inner.as_ref().unwrap().borrowed 91 | } 92 | } 93 | 94 | impl DerefMut for RefMut<'_, T, S> { 95 | fn deref_mut(&mut self) -> &mut Self::Target { 96 | self.inner.as_mut().unwrap().borrowed 97 | } 98 | } 99 | 100 | impl fmt::Debug 101 | for RefMut<'_, T, S> 102 | { 103 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 104 | match self.inner { 105 | Some(ref inner) => inner.fmt(f), 106 | None => { 107 | f.debug_struct("RefMut").field("borrowed", &"missing").finish() 108 | } 109 | } 110 | } 111 | } 112 | 113 | struct RefMutInner<'a, T: BiHashItem, S> { 114 | state: S, 115 | hashes: [MapHash; 2], 116 | borrowed: &'a mut T, 117 | } 118 | 119 | impl<'a, T: BiHashItem, S: BuildHasher> RefMutInner<'a, T, S> { 120 | fn into_ref(self) -> &'a T { 121 | if !self.hashes[0].is_same_hash(&self.state, self.borrowed.key1()) { 122 | panic!("key1 changed during RefMut borrow"); 123 | } 124 | if !self.hashes[1].is_same_hash(&self.state, self.borrowed.key2()) { 125 | panic!("key2 changed during RefMut borrow"); 126 | } 127 | 128 | self.borrowed 129 | } 130 | } 131 | 132 | impl fmt::Debug for RefMutInner<'_, T, S> { 133 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 134 | self.borrowed.fmt(f) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /crates/iddqd-test-utils/src/serde_utils.rs: -------------------------------------------------------------------------------- 1 | //! Serde-related test utilities. 2 | 3 | use crate::test_item::{ItemMap, MapKind, TestItem}; 4 | use iddqd::internal::ValidateCompact; 5 | use serde::Serialize; 6 | use std::collections::BTreeMap; 7 | 8 | pub fn assert_serialize_roundtrip<'a, M>(values: Vec) 9 | where 10 | M: 'a + ItemMap + Serialize, 11 | M::K1<'a>: Serialize, 12 | { 13 | let mut map = M::make_new(); 14 | let mut first_error = None; 15 | for value in values.clone() { 16 | // Ignore errors from duplicates which are quite possible to occur 17 | // here, since we're just testing serialization. But store the 18 | // first error to ensure that deserialization returns errors. 19 | if let Err(error) = map.insert_unique(value) { 20 | if first_error.is_none() { 21 | first_error = Some(error.into_owned()); 22 | } 23 | } 24 | } 25 | 26 | let serialized = serde_json::to_string(&map).unwrap(); 27 | let serialized_as_map = M::serialize_as_map(&map).unwrap(); 28 | let deserialized: M = M::make_deserialize_in( 29 | &mut serde_json::Deserializer::from_str(&serialized), 30 | ) 31 | .unwrap(); 32 | let deserialized_from_map: M = M::make_deserialize_in( 33 | &mut serde_json::Deserializer::from_str(&serialized_as_map), 34 | ) 35 | .unwrap(); 36 | let deserialized_as_map = M::deserialize_as_map( 37 | &mut serde_json::Deserializer::from_str(&serialized_as_map), 38 | ) 39 | .unwrap(); 40 | // Also check that we can deserialize into a BTreeMap (this ensures that 41 | // serialized_as_map is a map type). 42 | let deserialized_btree_map: BTreeMap = 43 | serde_json::from_str(&serialized_as_map).unwrap(); 44 | deserialized 45 | .validate_(ValidateCompact::Compact) 46 | .expect("deserialized map is valid"); 47 | deserialized_from_map 48 | .validate_(ValidateCompact::Compact) 49 | .expect("deserialized map from map is valid"); 50 | deserialized_as_map 51 | .validate_(ValidateCompact::Compact) 52 | .expect("deserialized map from map is valid"); 53 | 54 | let mut map_items = map.iter().collect::>(); 55 | let mut deserialized_items = deserialized.iter().collect::>(); 56 | let mut deserialized_from_map_items = 57 | deserialized_from_map.iter().collect::>(); 58 | let mut deserialized_as_map_items = 59 | deserialized_as_map.iter().collect::>(); 60 | let deserialized_from_btree_map_items = 61 | deserialized_btree_map.values().collect::>(); 62 | 63 | match M::map_kind() { 64 | MapKind::Ord => { 65 | // No sorting required -- we expect the items to be in order. 66 | } 67 | MapKind::Hash => { 68 | // Sort the items, since we don't care about the order. 69 | map_items.sort(); 70 | deserialized_items.sort(); 71 | deserialized_from_map_items.sort(); 72 | deserialized_as_map_items.sort(); 73 | // The B-Tree map would already be sorted. 74 | } 75 | } 76 | assert_eq!(map_items, deserialized_items, "items match"); 77 | assert_eq!(deserialized_items, deserialized_from_map_items, "items match"); 78 | assert_eq!( 79 | deserialized_from_map_items, deserialized_from_btree_map_items, 80 | "items match" 81 | ); 82 | assert_eq!( 83 | deserialized_from_btree_map_items, deserialized_as_map_items, 84 | "items match" 85 | ); 86 | 87 | // Try deserializing the full list of values directly, and see that the 88 | // error reported is the same as first_error. 89 | // 90 | // Here, we rely on the fact that the map is serialized as just a vector. 91 | let serialized = serde_json::to_string(&values).unwrap(); 92 | let res: Result = M::make_deserialize_in( 93 | &mut serde_json::Deserializer::from_str(&serialized), 94 | ); 95 | match (first_error, res) { 96 | (None, Ok(_)) => {} // No error, should be fine 97 | (Some(first_error), Ok(_)) => { 98 | panic!( 99 | "expected error ({first_error}), but deserialization succeeded" 100 | ) 101 | } 102 | (None, Err(error)) => { 103 | panic!( 104 | "unexpected error: {error}, deserialization should have succeeded" 105 | ) 106 | } 107 | (Some(first_error), Err(error)) => { 108 | // first_error is the error from the map, and error is the 109 | // deserialization error (which should always be a custom error, 110 | // stored as a string). 111 | let expected = first_error.to_string(); 112 | let actual = error.to_string(); 113 | 114 | // Ensure that line and column numbers are reported. 115 | let Some((actual_prefix, _)) = actual.rsplit_once(" at line ") 116 | else { 117 | panic!( 118 | "error does not contain line number at the end: {actual}" 119 | ); 120 | }; 121 | assert_eq!(actual_prefix, expected, "error matches"); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /crates/iddqd/src/tri_hash_map/ref_mut.rs: -------------------------------------------------------------------------------- 1 | use crate::{DefaultHashBuilder, TriHashItem, support::map_hash::MapHash}; 2 | use core::{ 3 | fmt, 4 | hash::BuildHasher, 5 | ops::{Deref, DerefMut}, 6 | }; 7 | 8 | /// A mutable reference to a [`TriHashMap`] item. 9 | /// 10 | /// This is a wrapper around a `&mut T` that panics when dropped, if the 11 | /// borrowed value's keys have changed since the wrapper was created. 12 | /// 13 | /// # Change detection 14 | /// 15 | /// It is illegal to change the keys of a borrowed `&mut T`. `RefMut` attempts 16 | /// to enforce this invariant. 17 | /// 18 | /// `RefMut` stores the `Hash` output of keys at creation time, and recomputes 19 | /// these hashes when it is dropped or when [`Self::into_ref`] is called. If a 20 | /// key changes, there's a small but non-negligible chance that its hash value 21 | /// stays the same[^collision-chance]. In that case, as long as the new key is 22 | /// not the same as another existing one, internal invariants are not violated 23 | /// and the [`TriHashMap`] will continue to work correctly. (But don't rely on 24 | /// this!) 25 | /// 26 | /// It is also possible to deliberately write pathological `Hash` 27 | /// implementations that collide more often. (Don't do this either.) 28 | /// 29 | /// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is 30 | /// called on it. If a key is changed and `mem::forget` is then called on the 31 | /// `RefMut`, the `TriHashMap` will stop functioning correctly. This will not 32 | /// introduce memory safety issues, however. 33 | /// 34 | /// The issues here are similar to using interior mutability (e.g. `RefCell` or 35 | /// `Mutex`) to mutate keys in a regular `HashMap`. 36 | /// 37 | /// [`mem::forget`]: std::mem::forget 38 | /// 39 | /// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability 40 | /// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday 41 | /// problem], the probability of a collision by chance reaches 10⁻⁶ within 42 | /// around 6 × 10⁶ elements. 43 | /// 44 | /// [`TriHashMap`]: crate::TriHashMap 45 | /// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table 46 | pub struct RefMut< 47 | 'a, 48 | T: TriHashItem, 49 | S: Clone + BuildHasher = DefaultHashBuilder, 50 | > { 51 | inner: Option>, 52 | } 53 | 54 | impl<'a, T: TriHashItem, S: Clone + BuildHasher> RefMut<'a, T, S> { 55 | pub(super) fn new( 56 | state: S, 57 | hashes: [MapHash; 3], 58 | borrowed: &'a mut T, 59 | ) -> Self { 60 | Self { inner: Some(RefMutInner { state, hashes, borrowed }) } 61 | } 62 | 63 | /// Borrows self into a shorter-lived `RefMut`. 64 | /// 65 | /// This `RefMut` will also check hash equality on drop. 66 | pub fn reborrow(&mut self) -> RefMut<'_, T, S> { 67 | let inner = self.inner.as_mut().unwrap(); 68 | let borrowed = &mut *inner.borrowed; 69 | RefMut::new(inner.state.clone(), inner.hashes.clone(), borrowed) 70 | } 71 | 72 | /// Converts this `RefMut` into a `&'a T`. 73 | pub fn into_ref(mut self) -> &'a T { 74 | let inner = self.inner.take().unwrap(); 75 | inner.into_ref() 76 | } 77 | } 78 | 79 | impl Drop for RefMut<'_, T, S> { 80 | fn drop(&mut self) { 81 | if let Some(inner) = self.inner.take() { 82 | inner.into_ref(); 83 | } 84 | } 85 | } 86 | 87 | impl Deref for RefMut<'_, T, S> { 88 | type Target = T; 89 | 90 | fn deref(&self) -> &Self::Target { 91 | self.inner.as_ref().unwrap().borrowed 92 | } 93 | } 94 | 95 | impl DerefMut for RefMut<'_, T, S> { 96 | fn deref_mut(&mut self) -> &mut Self::Target { 97 | self.inner.as_mut().unwrap().borrowed 98 | } 99 | } 100 | 101 | impl fmt::Debug 102 | for RefMut<'_, T, S> 103 | { 104 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 105 | match self.inner { 106 | Some(ref inner) => inner.fmt(f), 107 | None => { 108 | f.debug_struct("RefMut").field("borrowed", &"missing").finish() 109 | } 110 | } 111 | } 112 | } 113 | 114 | struct RefMutInner<'a, T: TriHashItem, S> { 115 | state: S, 116 | hashes: [MapHash; 3], 117 | borrowed: &'a mut T, 118 | } 119 | 120 | impl<'a, T: TriHashItem, S: BuildHasher> RefMutInner<'a, T, S> { 121 | fn into_ref(self) -> &'a T { 122 | if !self.hashes[0].is_same_hash(&self.state, self.borrowed.key1()) { 123 | panic!("key1 changed during RefMut borrow"); 124 | } 125 | if !self.hashes[1].is_same_hash(&self.state, self.borrowed.key2()) { 126 | panic!("key2 changed during RefMut borrow"); 127 | } 128 | if !self.hashes[2].is_same_hash(&self.state, self.borrowed.key3()) { 129 | panic!("key3 changed during RefMut borrow"); 130 | } 131 | 132 | self.borrowed 133 | } 134 | } 135 | 136 | impl fmt::Debug for RefMutInner<'_, T, S> { 137 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 138 | self.borrowed.fmt(f) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/ref_mut.rs: -------------------------------------------------------------------------------- 1 | use super::IdOrdItem; 2 | use crate::support::map_hash::MapHash; 3 | use core::{ 4 | fmt, 5 | hash::Hash, 6 | ops::{Deref, DerefMut}, 7 | }; 8 | 9 | /// A mutable reference to an [`IdOrdMap`] entry. 10 | /// 11 | /// This is a wrapper around a `&mut T` that panics when dropped, if the 12 | /// borrowed value's key has changed since the wrapper was created. 13 | /// 14 | /// # Change detection 15 | /// 16 | /// It is illegal to change the keys of a borrowed `&mut T`. `RefMut` attempts 17 | /// to enforce this invariant, and as part of that, it requires that the key 18 | /// type implement [`Hash`]. 19 | /// 20 | /// `RefMut` stores the `Hash` output of keys at creation time, and recomputes 21 | /// these hashes when it is dropped or when [`Self::into_ref`] is called. If a 22 | /// key changes, there's a small but non-negligible chance that its hash value 23 | /// stays the same[^collision-chance]. In that case, the map will no longer 24 | /// function correctly and might panic on access. This will not introduce memory 25 | /// safety issues, however. 26 | /// 27 | /// It is also possible to deliberately write pathological `Hash` 28 | /// implementations that collide more often. (Don't do this.) 29 | /// 30 | /// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is 31 | /// called on it. If a key is changed and `mem::forget` is then called on the 32 | /// `RefMut`, the [`IdOrdMap`] will no longer function correctly and might panic 33 | /// on access. This will not introduce memory safety issues, however. 34 | /// 35 | /// The issues here are similar to using interior mutability (e.g. `RefCell` or 36 | /// `Mutex`) to mutate keys in a regular `HashMap`. 37 | /// 38 | /// [`mem::forget`]: std::mem::forget 39 | /// 40 | /// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability 41 | /// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday 42 | /// problem], the probability of a collision by chance reaches 10⁻⁶ within 43 | /// around 6 × 10⁶ elements. 44 | /// 45 | /// [`IdOrdMap`]: crate::IdOrdMap 46 | /// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table 47 | pub struct RefMut<'a, T: IdOrdItem> 48 | where 49 | T::Key<'a>: Hash, 50 | { 51 | inner: Option>, 52 | } 53 | 54 | impl<'a, T: IdOrdItem> RefMut<'a, T> 55 | where 56 | T::Key<'a>: Hash, 57 | { 58 | pub(super) fn new( 59 | state: foldhash::fast::FixedState, 60 | hash: MapHash, 61 | borrowed: &'a mut T, 62 | ) -> Self { 63 | let inner = RefMutInner { state, hash, borrowed }; 64 | Self { inner: Some(inner) } 65 | } 66 | 67 | /// Converts this `RefMut` into a `&'a T`. 68 | pub fn into_ref(mut self) -> &'a T { 69 | let inner = self.inner.take().unwrap(); 70 | inner.into_ref() 71 | } 72 | } 73 | 74 | impl<'a, T: for<'k> IdOrdItemMut<'k>> RefMut<'a, T> { 75 | /// Borrows self into a shorter-lived `RefMut`. 76 | /// 77 | /// This `RefMut` will also check hash equality on drop. 78 | pub fn reborrow<'b>(&'b mut self) -> RefMut<'b, T> { 79 | let inner = self.inner.as_mut().unwrap(); 80 | let borrowed = &mut *inner.borrowed; 81 | RefMut::new(inner.state.clone(), inner.hash.clone(), borrowed) 82 | } 83 | } 84 | 85 | impl<'a, T: IdOrdItem> Drop for RefMut<'a, T> 86 | where 87 | T::Key<'a>: Hash, 88 | { 89 | fn drop(&mut self) { 90 | if let Some(inner) = self.inner.take() { 91 | inner.into_ref(); 92 | } 93 | } 94 | } 95 | 96 | impl<'a, T: IdOrdItem> Deref for RefMut<'a, T> 97 | where 98 | T::Key<'a>: Hash, 99 | { 100 | type Target = T; 101 | 102 | fn deref(&self) -> &Self::Target { 103 | self.inner.as_ref().unwrap().borrowed 104 | } 105 | } 106 | 107 | impl<'a, T: IdOrdItem> DerefMut for RefMut<'a, T> 108 | where 109 | T::Key<'a>: Hash, 110 | { 111 | fn deref_mut(&mut self) -> &mut Self::Target { 112 | self.inner.as_mut().unwrap().borrowed 113 | } 114 | } 115 | 116 | impl<'a, T: IdOrdItem + fmt::Debug> fmt::Debug for RefMut<'a, T> 117 | where 118 | T::Key<'a>: Hash, 119 | { 120 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 121 | match self.inner { 122 | Some(ref inner) => inner.fmt(f), 123 | None => { 124 | f.debug_struct("RefMut").field("borrowed", &"missing").finish() 125 | } 126 | } 127 | } 128 | } 129 | 130 | struct RefMutInner<'a, T: IdOrdItem> { 131 | state: foldhash::fast::FixedState, 132 | hash: MapHash, 133 | borrowed: &'a mut T, 134 | } 135 | 136 | impl<'a, T: IdOrdItem> RefMutInner<'a, T> 137 | where 138 | T::Key<'a>: Hash, 139 | { 140 | fn into_ref(self) -> &'a T { 141 | let key: T::Key<'_> = self.borrowed.key(); 142 | // SAFETY: The key is borrowed, then dropped immediately. T is valid for 143 | // 'a so T::Key is valid for 'a. 144 | let key: T::Key<'a> = 145 | unsafe { std::mem::transmute::, T::Key<'a>>(key) }; 146 | if !self.hash.is_same_hash(&self.state, &key) { 147 | panic!("key changed during RefMut borrow"); 148 | } 149 | 150 | self.borrowed 151 | } 152 | } 153 | 154 | impl fmt::Debug for RefMutInner<'_, T> { 155 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 156 | self.borrowed.fmt(f) 157 | } 158 | } 159 | 160 | /// A trait for mutable access to items in an [`IdOrdMap`]. 161 | /// 162 | /// This is a non-public trait used to work around a Rust borrow checker 163 | /// limitation. [This will produce a documentation warning if it becomes 164 | /// public]. 165 | /// 166 | /// This is automatically implemented whenever `T::Key` implements [`Hash`]. 167 | /// 168 | /// [`IdOrdMap`]: crate::IdOrdMap 169 | pub trait IdOrdItemMut<'a>: IdOrdItem: Hash> + 'a {} 170 | 171 | impl<'a, T> IdOrdItemMut<'a> for T where T: 'a + IdOrdItem: Hash> {} 172 | -------------------------------------------------------------------------------- /crates/iddqd/tests/integration/schemars_tests.rs: -------------------------------------------------------------------------------- 1 | use expectorate::assert_contents; 2 | use iddqd::{ 3 | BiHashItem, BiHashMap, IdHashItem, IdHashMap, TriHashItem, TriHashMap, 4 | bi_hash_map::BiHashMapAsMap, bi_upcast, id_hash_map::IdHashMapAsMap, 5 | id_upcast, tri_hash_map::TriHashMapAsMap, tri_upcast, 6 | }; 7 | #[cfg(feature = "std")] 8 | use iddqd::{IdOrdItem, IdOrdMap, id_ord_map::IdOrdMapAsMap}; 9 | use schemars::{JsonSchema, schema_for}; 10 | use serde::{Deserialize, Serialize}; 11 | 12 | #[derive(Debug, Serialize, Deserialize, JsonSchema)] 13 | struct TestUser { 14 | // This is in alphabetical order to ensure that the same fixture gets 15 | // generated with or without the `schemars/preserve_order` feature. 16 | age: u32, 17 | email: String, 18 | id: u32, 19 | name: String, 20 | } 21 | 22 | impl IdHashItem for TestUser { 23 | type Key<'a> = &'a str; 24 | 25 | fn key(&self) -> Self::Key<'_> { 26 | &self.name 27 | } 28 | 29 | id_upcast!(); 30 | } 31 | 32 | impl BiHashItem for TestUser { 33 | type K1<'a> = &'a str; 34 | type K2<'a> = u32; 35 | 36 | fn key1(&self) -> Self::K1<'_> { 37 | &self.name 38 | } 39 | 40 | fn key2(&self) -> Self::K2<'_> { 41 | self.id 42 | } 43 | 44 | bi_upcast!(); 45 | } 46 | 47 | impl TriHashItem for TestUser { 48 | type K1<'a> = &'a str; 49 | type K2<'a> = u32; 50 | type K3<'a> = &'a str; 51 | 52 | fn key1(&self) -> Self::K1<'_> { 53 | &self.name 54 | } 55 | 56 | fn key2(&self) -> Self::K2<'_> { 57 | self.id 58 | } 59 | 60 | fn key3(&self) -> Self::K3<'_> { 61 | &self.email 62 | } 63 | 64 | tri_upcast!(); 65 | } 66 | 67 | #[cfg(feature = "std")] 68 | impl IdOrdItem for TestUser { 69 | type Key<'a> = &'a str; 70 | 71 | fn key(&self) -> Self::Key<'_> { 72 | &self.name 73 | } 74 | 75 | id_upcast!(); 76 | } 77 | 78 | #[test] 79 | fn schema_fixtures() { 80 | let schema = schema_for!(IdHashMap); 81 | assert_contents( 82 | "tests/output/id_hash_map_schema.json", 83 | &to_string_pretty_ln(&schema), 84 | ); 85 | 86 | let schema = schema_for!(IdHashMapAsMap); 87 | assert_contents( 88 | "tests/output/id_hash_map_as_map_schema.json", 89 | &to_string_pretty_ln(&schema), 90 | ); 91 | 92 | #[cfg(feature = "std")] 93 | { 94 | let schema = schema_for!(IdOrdMap); 95 | assert_contents( 96 | "tests/output/id_ord_map_schema.json", 97 | &to_string_pretty_ln(&schema), 98 | ); 99 | 100 | let schema = schema_for!(IdOrdMapAsMap); 101 | assert_contents( 102 | "tests/output/id_ord_map_as_map_schema.json", 103 | &to_string_pretty_ln(&schema), 104 | ); 105 | } 106 | 107 | let schema = schema_for!(BiHashMap); 108 | assert_contents( 109 | "tests/output/bi_hash_map_schema.json", 110 | &to_string_pretty_ln(&schema), 111 | ); 112 | 113 | let schema = schema_for!(BiHashMapAsMap); 114 | assert_contents( 115 | "tests/output/bi_hash_map_as_map_schema.json", 116 | &to_string_pretty_ln(&schema), 117 | ); 118 | 119 | let schema = schema_for!(TriHashMap); 120 | assert_contents( 121 | "tests/output/tri_hash_map_schema.json", 122 | &to_string_pretty_ln(&schema), 123 | ); 124 | 125 | let schema = schema_for!(TriHashMapAsMap); 126 | assert_contents( 127 | "tests/output/tri_hash_map_as_map_schema.json", 128 | &to_string_pretty_ln(&schema), 129 | ); 130 | } 131 | 132 | #[cfg(feature = "std")] 133 | #[test] 134 | fn container_fixtures() { 135 | #[derive(JsonSchema)] 136 | #[expect(unused)] 137 | struct Container { 138 | // This is in alphabetical order to ensure that the same fixture gets 139 | // generated with or without the `schemars/preserve_order` feature. 140 | users_bi: BiHashMap, 141 | users_hash: IdHashMap, 142 | users_ord: IdOrdMap, 143 | users_tri: TriHashMap, 144 | } 145 | 146 | // Verify the container can generate a schema. 147 | let schema = schema_for!(Container); 148 | assert_contents( 149 | "tests/output/container_schema.json", 150 | &to_string_pretty_ln(&schema), 151 | ); 152 | 153 | // A simple container with just IdHashMap. This fixture is 154 | // used by `typify-types.rs` to show end-to-end usage. 155 | #[derive(JsonSchema)] 156 | #[expect(unused)] 157 | struct SimpleContainer { 158 | users: IdHashMap, 159 | } 160 | 161 | let schema = schema_for!(SimpleContainer); 162 | assert_contents( 163 | "tests/output/simple_container_schema.json", 164 | &to_string_pretty_ln(&schema), 165 | ); 166 | 167 | // Container using the AsMap types with serde's `with` attribute. 168 | #[derive(JsonSchema)] 169 | #[expect(unused)] 170 | struct ContainerAsMap { 171 | // This is in alphabetical order to ensure that the same fixture gets 172 | // generated with or without the `schemars/preserve_order` feature. 173 | #[serde(with = "BiHashMapAsMap::")] 174 | users_bi: BiHashMap, 175 | #[serde(with = "IdHashMapAsMap::")] 176 | users_hash: IdHashMap, 177 | #[serde(with = "IdOrdMapAsMap::")] 178 | users_ord: IdOrdMap, 179 | #[serde(with = "TriHashMapAsMap::")] 180 | users_tri: TriHashMap, 181 | } 182 | 183 | let schema = schema_for!(ContainerAsMap); 184 | assert_contents( 185 | "tests/output/container_as_map_schema.json", 186 | &to_string_pretty_ln(&schema), 187 | ); 188 | } 189 | 190 | fn to_string_pretty_ln(data: &T) -> String { 191 | let mut s = serde_json::to_string_pretty(data).unwrap(); 192 | s.push('\n'); 193 | s 194 | } 195 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/iter.rs: -------------------------------------------------------------------------------- 1 | use super::{IdOrdItem, RefMut, tables::IdOrdMapTables}; 2 | use crate::support::{ 3 | alloc::Global, borrow::DormantMutRef, btree_table, item_set::ItemSet, 4 | }; 5 | use core::{hash::Hash, iter::FusedIterator}; 6 | 7 | /// An iterator over the elements of an [`IdOrdMap`] by shared reference. 8 | /// 9 | /// Created by [`IdOrdMap::iter`], and ordered by keys. 10 | /// 11 | /// [`IdOrdMap`]: crate::IdOrdMap 12 | /// [`IdOrdMap::iter`]: crate::IdOrdMap::iter 13 | #[derive(Clone, Debug)] 14 | pub struct Iter<'a, T: IdOrdItem> { 15 | items: &'a ItemSet, 16 | iter: btree_table::Iter<'a>, 17 | } 18 | 19 | impl<'a, T: IdOrdItem> Iter<'a, T> { 20 | pub(super) fn new( 21 | items: &'a ItemSet, 22 | tables: &'a IdOrdMapTables, 23 | ) -> Self { 24 | Self { items, iter: tables.key_to_item.iter() } 25 | } 26 | } 27 | 28 | impl<'a, T: IdOrdItem> Iterator for Iter<'a, T> { 29 | type Item = &'a T; 30 | 31 | #[inline] 32 | fn next(&mut self) -> Option { 33 | let index = self.iter.next()?; 34 | Some(&self.items[index]) 35 | } 36 | } 37 | 38 | impl ExactSizeIterator for Iter<'_, T> { 39 | #[inline] 40 | fn len(&self) -> usize { 41 | self.iter.len() 42 | } 43 | } 44 | 45 | // btree_set::Iter is a FusedIterator, so Iter is as well. 46 | impl FusedIterator for Iter<'_, T> {} 47 | 48 | /// An iterator over the elements of a [`IdOrdMap`] by mutable reference. 49 | /// 50 | /// This iterator returns [`RefMut`] instances. 51 | /// 52 | /// Created by [`IdOrdMap::iter_mut`], and ordered by keys. 53 | /// 54 | /// [`IdOrdMap`]: crate::IdOrdMap 55 | /// [`IdOrdMap::iter_mut`]: crate::IdOrdMap::iter_mut 56 | #[derive(Debug)] 57 | pub struct IterMut<'a, T: IdOrdItem> 58 | where 59 | T::Key<'a>: Hash, 60 | { 61 | items: &'a mut ItemSet, 62 | tables: &'a IdOrdMapTables, 63 | iter: btree_table::Iter<'a>, 64 | } 65 | 66 | impl<'a, T: IdOrdItem> IterMut<'a, T> 67 | where 68 | T::Key<'a>: Hash, 69 | { 70 | pub(super) fn new( 71 | items: &'a mut ItemSet, 72 | tables: &'a IdOrdMapTables, 73 | ) -> Self { 74 | Self { items, tables, iter: tables.key_to_item.iter() } 75 | } 76 | } 77 | 78 | impl<'a, T: IdOrdItem + 'a> Iterator for IterMut<'a, T> 79 | where 80 | T::Key<'a>: Hash, 81 | { 82 | type Item = RefMut<'a, T>; 83 | 84 | #[inline] 85 | fn next(&mut self) -> Option { 86 | let index = self.iter.next()?; 87 | 88 | let item = &mut self.items[index]; 89 | 90 | // SAFETY: This lifetime extension from self to 'a is safe based on two 91 | // things: 92 | // 93 | // 1. We never repeat indexes, i.e. for an index i, once we've handed 94 | // out an item at i, creating `&mut T`, we'll never get the index i 95 | // again. (This is guaranteed from the set-based nature of the 96 | // iterator.) This means that we don't ever create a mutable alias to 97 | // the same memory. 98 | // 99 | // In particular, unlike all the other places we look up data from a 100 | // btree table, we don't pass a lookup function into 101 | // self.iter.next(). If we did, then it is possible the lookup 102 | // function would have been called with an old index i. But we don't 103 | // need to do that. 104 | // 105 | // 2. All mutable references to data within self.items are derived from 106 | // self.items. So, the rule described at [1] is upheld: 107 | // 108 | // > When creating a mutable reference, then while this reference 109 | // > exists, the memory it points to must not get accessed (read or 110 | // > written) through any other pointer or reference not derived from 111 | // > this reference. 112 | // 113 | // [1]: 114 | // https://doc.rust-lang.org/std/ptr/index.html#pointer-to-reference-conversion 115 | let item = unsafe { core::mem::transmute::<&mut T, &'a mut T>(item) }; 116 | 117 | let (hash, dormant) = { 118 | let (item, dormant) = DormantMutRef::new(item); 119 | let hash = self.tables.make_hash(item); 120 | (hash, dormant) 121 | }; 122 | 123 | // SAFETY: item is dropped above, and self is no longer used after this 124 | // point. 125 | let item = unsafe { dormant.awaken() }; 126 | 127 | Some(RefMut::new(self.tables.state().clone(), hash, item)) 128 | } 129 | } 130 | 131 | impl<'a, T: IdOrdItem + 'a> ExactSizeIterator for IterMut<'a, T> 132 | where 133 | T::Key<'a>: Hash, 134 | { 135 | #[inline] 136 | fn len(&self) -> usize { 137 | self.iter.len() 138 | } 139 | } 140 | 141 | // hash_map::IterMut is a FusedIterator, so IterMut is as well. 142 | impl<'a, T: IdOrdItem + 'a> FusedIterator for IterMut<'a, T> where 143 | T::Key<'a>: Hash 144 | { 145 | } 146 | 147 | /// An iterator over the elements of a [`IdOrdMap`] by ownership. 148 | /// 149 | /// Created by [`IdOrdMap::into_iter`], and ordered by keys. 150 | /// 151 | /// [`IdOrdMap`]: crate::IdOrdMap 152 | /// [`IdOrdMap::into_iter`]: crate::IdOrdMap::into_iter 153 | #[derive(Debug)] 154 | pub struct IntoIter { 155 | items: ItemSet, 156 | iter: btree_table::IntoIter, 157 | } 158 | 159 | impl IntoIter { 160 | pub(super) fn new( 161 | items: ItemSet, 162 | tables: IdOrdMapTables, 163 | ) -> Self { 164 | Self { items, iter: tables.key_to_item.into_iter() } 165 | } 166 | } 167 | 168 | impl Iterator for IntoIter { 169 | type Item = T; 170 | 171 | #[inline] 172 | fn next(&mut self) -> Option { 173 | let index = self.iter.next()?; 174 | let next = self 175 | .items 176 | .remove(index) 177 | .unwrap_or_else(|| panic!("index {index} not found in items")); 178 | Some(next) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/hash_table.rs: -------------------------------------------------------------------------------- 1 | //! A wrapper around a hash table with some random state. 2 | 3 | use super::{ 4 | alloc::{AllocWrapper, Allocator}, 5 | map_hash::MapHash, 6 | }; 7 | use crate::internal::{TableValidationError, ValidateCompact}; 8 | use alloc::{collections::BTreeSet, vec::Vec}; 9 | use core::{ 10 | borrow::Borrow, 11 | fmt, 12 | hash::{BuildHasher, Hash}, 13 | }; 14 | use equivalent::Equivalent; 15 | use hashbrown::{ 16 | HashTable, 17 | hash_table::{AbsentEntry, Entry, OccupiedEntry}, 18 | }; 19 | 20 | #[derive(Clone, Default)] 21 | pub(crate) struct MapHashTable { 22 | pub(super) items: HashTable>, 23 | } 24 | 25 | impl fmt::Debug for MapHashTable { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | f.debug_struct("MapHashTable").field("items", &self.items).finish() 28 | } 29 | } 30 | 31 | impl MapHashTable { 32 | pub(crate) const fn new_in(alloc: A) -> Self { 33 | Self { items: HashTable::new_in(AllocWrapper(alloc)) } 34 | } 35 | 36 | pub(crate) fn with_capacity_in(capacity: usize, alloc: A) -> Self { 37 | Self { 38 | items: HashTable::with_capacity_in(capacity, AllocWrapper(alloc)), 39 | } 40 | } 41 | 42 | pub(crate) fn len(&self) -> usize { 43 | self.items.len() 44 | } 45 | 46 | pub(crate) fn validate( 47 | &self, 48 | expected_len: usize, 49 | compactness: ValidateCompact, 50 | ) -> Result<(), TableValidationError> { 51 | if self.len() != expected_len { 52 | return Err(TableValidationError::new(format!( 53 | "expected length {expected_len}, was {}", 54 | self.len() 55 | ))); 56 | } 57 | 58 | match compactness { 59 | ValidateCompact::Compact => { 60 | // All items between 0 (inclusive) and self.len() (exclusive) 61 | // are expected to be present, and there are no duplicates. 62 | let mut values: Vec<_> = self.items.iter().copied().collect(); 63 | values.sort_unstable(); 64 | for (i, value) in values.iter().enumerate() { 65 | if *value != i { 66 | return Err(TableValidationError::new(format!( 67 | "expected value at index {i} to be {i}, was {value}" 68 | ))); 69 | } 70 | } 71 | } 72 | ValidateCompact::NonCompact => { 73 | // There should be no duplicates. 74 | let values: Vec<_> = self.items.iter().copied().collect(); 75 | let value_set: BTreeSet<_> = values.iter().copied().collect(); 76 | if value_set.len() != values.len() { 77 | return Err(TableValidationError::new(format!( 78 | "expected no duplicates, but found {} duplicates \ 79 | (values: {:?})", 80 | values.len() - value_set.len(), 81 | values, 82 | ))); 83 | } 84 | } 85 | } 86 | 87 | Ok(()) 88 | } 89 | 90 | pub(crate) fn compute_hash( 91 | &self, 92 | state: &S, 93 | key: K, 94 | ) -> MapHash { 95 | MapHash { hash: state.hash_one(key) } 96 | } 97 | 98 | // Ensure that K has a consistent hash. 99 | pub(crate) fn find_index( 100 | &self, 101 | state: &S, 102 | key: &Q, 103 | lookup: F, 104 | ) -> Option 105 | where 106 | F: Fn(usize) -> K, 107 | Q: ?Sized + Hash + Equivalent, 108 | { 109 | let hash = state.hash_one(key); 110 | self.items.find(hash, |index| key.equivalent(&lookup(*index))).copied() 111 | } 112 | 113 | pub(crate) fn entry( 114 | &mut self, 115 | state: &S, 116 | key: K, 117 | lookup: F, 118 | ) -> Entry<'_, usize, AllocWrapper> 119 | where 120 | F: Fn(usize) -> K, 121 | { 122 | let hash = state.hash_one(&key); 123 | self.items.entry( 124 | hash, 125 | |index| lookup(*index) == key, 126 | |v| state.hash_one(lookup(*v)), 127 | ) 128 | } 129 | 130 | pub(crate) fn find_entry( 131 | &mut self, 132 | state: &S, 133 | key: &Q, 134 | lookup: F, 135 | ) -> Result< 136 | OccupiedEntry<'_, usize, AllocWrapper>, 137 | AbsentEntry<'_, usize, AllocWrapper>, 138 | > 139 | where 140 | F: Fn(usize) -> K, 141 | K: Hash + Eq + Borrow, 142 | Q: ?Sized + Hash + Eq, 143 | { 144 | let hash = state.hash_one(key); 145 | self.items.find_entry(hash, |index| lookup(*index).borrow() == key) 146 | } 147 | 148 | pub(crate) fn find_entry_by_hash( 149 | &mut self, 150 | hash: u64, 151 | mut f: F, 152 | ) -> Result< 153 | OccupiedEntry<'_, usize, AllocWrapper>, 154 | AbsentEntry<'_, usize, AllocWrapper>, 155 | > 156 | where 157 | F: FnMut(usize) -> bool, 158 | { 159 | self.items.find_entry(hash, |index| f(*index)) 160 | } 161 | 162 | pub(crate) fn retain(&mut self, mut f: F) 163 | where 164 | F: FnMut(usize) -> bool, 165 | { 166 | self.items.retain(|index| f(*index)); 167 | } 168 | 169 | /// Clears the hash table, removing all items. 170 | #[inline] 171 | pub(crate) fn clear(&mut self) { 172 | self.items.clear(); 173 | } 174 | 175 | /// Reserves capacity for at least `additional` more items. 176 | #[inline] 177 | pub(crate) fn reserve(&mut self, additional: usize) { 178 | self.items.reserve(additional, |_| 0); 179 | } 180 | 181 | /// Shrinks the capacity of the hash table as much as possible. 182 | #[inline] 183 | pub(crate) fn shrink_to_fit(&mut self) { 184 | self.items.shrink_to_fit(|_| 0); 185 | } 186 | 187 | /// Shrinks the capacity of the hash table with a lower limit. 188 | #[inline] 189 | pub(crate) fn shrink_to(&mut self, min_capacity: usize) { 190 | self.items.shrink_to(min_capacity, |_| 0); 191 | } 192 | 193 | /// Tries to reserve capacity for at least `additional` more items. 194 | #[inline] 195 | pub(crate) fn try_reserve( 196 | &mut self, 197 | additional: usize, 198 | ) -> Result<(), hashbrown::TryReserveError> { 199 | self.items.try_reserve(additional, |_| 0) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /crates/iddqd/src/tri_hash_map/trait_defs.rs: -------------------------------------------------------------------------------- 1 | //! Trait definitions for `TriHashMap`. 2 | 3 | use alloc::{boxed::Box, rc::Rc, sync::Arc}; 4 | use core::hash::Hash; 5 | 6 | /// An item in a [`TriHashMap`]. 7 | /// 8 | /// This trait is used to define the keys. 9 | /// 10 | /// # Examples 11 | /// 12 | /// ``` 13 | /// # #[cfg(feature = "default-hasher")] { 14 | /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; 15 | /// 16 | /// // Define a struct with three keys. 17 | /// #[derive(Debug, PartialEq, Eq, Hash)] 18 | /// struct Person { 19 | /// id: u32, 20 | /// name: String, 21 | /// email: String, 22 | /// } 23 | /// 24 | /// // Implement TriHashItem for the struct. 25 | /// impl TriHashItem for Person { 26 | /// type K1<'a> = u32; 27 | /// type K2<'a> = &'a str; 28 | /// type K3<'a> = &'a str; 29 | /// 30 | /// fn key1(&self) -> Self::K1<'_> { 31 | /// self.id 32 | /// } 33 | /// 34 | /// fn key2(&self) -> Self::K2<'_> { 35 | /// &self.name 36 | /// } 37 | /// 38 | /// fn key3(&self) -> Self::K3<'_> { 39 | /// &self.email 40 | /// } 41 | /// 42 | /// tri_upcast!(); 43 | /// } 44 | /// 45 | /// // Create a TriHashMap and insert items. 46 | /// let mut map = TriHashMap::new(); 47 | /// map.insert_unique(Person { 48 | /// id: 1, 49 | /// name: "Alice".to_string(), 50 | /// email: "alice@example.com".to_string(), 51 | /// }) 52 | /// .unwrap(); 53 | /// map.insert_unique(Person { 54 | /// id: 2, 55 | /// name: "Bob".to_string(), 56 | /// email: "bob@example.com".to_string(), 57 | /// }) 58 | /// .unwrap(); 59 | /// # } 60 | /// ``` 61 | /// 62 | /// [`TriHashMap`]: crate::TriHashMap 63 | pub trait TriHashItem { 64 | /// The first key type. 65 | type K1<'a>: Eq + Hash 66 | where 67 | Self: 'a; 68 | 69 | /// The second key type. 70 | type K2<'a>: Eq + Hash 71 | where 72 | Self: 'a; 73 | 74 | /// The third key type. 75 | type K3<'a>: Eq + Hash 76 | where 77 | Self: 'a; 78 | 79 | /// Retrieves the first key. 80 | fn key1(&self) -> Self::K1<'_>; 81 | 82 | /// Retrieves the second key. 83 | fn key2(&self) -> Self::K2<'_>; 84 | 85 | /// Retrieves the third key. 86 | fn key3(&self) -> Self::K3<'_>; 87 | 88 | /// Upcasts the first key to a shorter lifetime, in effect asserting that 89 | /// the lifetime `'a` on [`TriHashItem::K1`] is covariant. 90 | /// 91 | /// Typically implemented via the [`tri_upcast`] macro. 92 | /// 93 | /// [`tri_upcast`]: crate::tri_upcast 94 | fn upcast_key1<'short, 'long: 'short>( 95 | long: Self::K1<'long>, 96 | ) -> Self::K1<'short>; 97 | 98 | /// Upcasts the second key to a shorter lifetime, in effect asserting that 99 | /// the lifetime `'a` on [`TriHashItem::K2`] is covariant. 100 | /// 101 | /// Typically implemented via the [`tri_upcast`] macro. 102 | /// 103 | /// [`tri_upcast`]: crate::tri_upcast 104 | fn upcast_key2<'short, 'long: 'short>( 105 | long: Self::K2<'long>, 106 | ) -> Self::K2<'short>; 107 | 108 | /// Upcasts the third key to a shorter lifetime, in effect asserting that 109 | /// the lifetime `'a` on [`TriHashItem::K3`] is covariant. 110 | /// 111 | /// Typically implemented via the [`tri_upcast`] macro. 112 | /// 113 | /// [`tri_upcast`]: crate::tri_upcast 114 | fn upcast_key3<'short, 'long: 'short>( 115 | long: Self::K3<'long>, 116 | ) -> Self::K3<'short>; 117 | } 118 | 119 | macro_rules! impl_for_ref { 120 | ($type:ty) => { 121 | impl<'b, T: 'b + ?Sized + TriHashItem> TriHashItem for $type { 122 | type K1<'a> 123 | = T::K1<'a> 124 | where 125 | Self: 'a; 126 | type K2<'a> 127 | = T::K2<'a> 128 | where 129 | Self: 'a; 130 | type K3<'a> 131 | = T::K3<'a> 132 | where 133 | Self: 'a; 134 | 135 | fn key1(&self) -> Self::K1<'_> { 136 | (**self).key1() 137 | } 138 | 139 | fn key2(&self) -> Self::K2<'_> { 140 | (**self).key2() 141 | } 142 | 143 | fn key3(&self) -> Self::K3<'_> { 144 | (**self).key3() 145 | } 146 | 147 | fn upcast_key1<'short, 'long: 'short>( 148 | long: Self::K1<'long>, 149 | ) -> Self::K1<'short> 150 | where 151 | Self: 'long, 152 | { 153 | T::upcast_key1(long) 154 | } 155 | 156 | fn upcast_key2<'short, 'long: 'short>( 157 | long: Self::K2<'long>, 158 | ) -> Self::K2<'short> 159 | where 160 | Self: 'long, 161 | { 162 | T::upcast_key2(long) 163 | } 164 | 165 | fn upcast_key3<'short, 'long: 'short>( 166 | long: Self::K3<'long>, 167 | ) -> Self::K3<'short> 168 | where 169 | Self: 'long, 170 | { 171 | T::upcast_key3(long) 172 | } 173 | } 174 | }; 175 | } 176 | 177 | impl_for_ref!(&'b T); 178 | impl_for_ref!(&'b mut T); 179 | 180 | macro_rules! impl_for_box { 181 | ($type:ty) => { 182 | impl TriHashItem for $type { 183 | type K1<'a> 184 | = T::K1<'a> 185 | where 186 | Self: 'a; 187 | 188 | type K2<'a> 189 | = T::K2<'a> 190 | where 191 | Self: 'a; 192 | 193 | type K3<'a> 194 | = T::K3<'a> 195 | where 196 | Self: 'a; 197 | 198 | fn key1(&self) -> Self::K1<'_> { 199 | (**self).key1() 200 | } 201 | 202 | fn key2(&self) -> Self::K2<'_> { 203 | (**self).key2() 204 | } 205 | 206 | fn key3(&self) -> Self::K3<'_> { 207 | (**self).key3() 208 | } 209 | 210 | fn upcast_key1<'short, 'long: 'short>( 211 | long: Self::K1<'long>, 212 | ) -> Self::K1<'short> { 213 | T::upcast_key1(long) 214 | } 215 | 216 | fn upcast_key2<'short, 'long: 'short>( 217 | long: Self::K2<'long>, 218 | ) -> Self::K2<'short> { 219 | T::upcast_key2(long) 220 | } 221 | 222 | fn upcast_key3<'short, 'long: 'short>( 223 | long: Self::K3<'long>, 224 | ) -> Self::K3<'short> { 225 | T::upcast_key3(long) 226 | } 227 | } 228 | }; 229 | } 230 | 231 | impl_for_box!(Box); 232 | impl_for_box!(Rc); 233 | impl_for_box!(Arc); 234 | -------------------------------------------------------------------------------- /crates/iddqd-test-utils/src/naive_map.rs: -------------------------------------------------------------------------------- 1 | use crate::test_item::TestItem; 2 | use iddqd::errors::DuplicateItem; 3 | 4 | /// A naive, inefficient map that acts as an oracle for property-based tests. 5 | /// 6 | /// This map is stored as a vector without internal indexes, and performs linear 7 | /// scans. 8 | #[derive(Debug)] 9 | pub struct NaiveMap { 10 | items: Vec, 11 | unique_constraint: UniqueConstraint, 12 | } 13 | 14 | impl NaiveMap { 15 | pub fn new_key1() -> Self { 16 | Self { items: Vec::new(), unique_constraint: UniqueConstraint::Key1 } 17 | } 18 | 19 | // Will use in the future. 20 | pub fn new_key12() -> Self { 21 | Self { items: Vec::new(), unique_constraint: UniqueConstraint::Key12 } 22 | } 23 | 24 | pub fn new_key123() -> Self { 25 | Self { items: Vec::new(), unique_constraint: UniqueConstraint::Key123 } 26 | } 27 | 28 | pub fn insert_unique( 29 | &mut self, 30 | item: TestItem, 31 | ) -> Result<(), DuplicateItem> { 32 | // Cannot store the duplicates directly here because of borrow checker 33 | // issues. Instead, we store indexes and then map them to items. 34 | let dup_indexes = self 35 | .items 36 | .iter() 37 | .enumerate() 38 | .filter_map(|(i, e)| { 39 | self.unique_constraint.matches(&item, e).then_some(i) 40 | }) 41 | .collect::>(); 42 | 43 | if dup_indexes.is_empty() { 44 | self.items.push(item); 45 | Ok(()) 46 | } else { 47 | Err(DuplicateItem::__internal_new( 48 | item, 49 | dup_indexes.iter().map(|&i| &self.items[i]).collect(), 50 | )) 51 | } 52 | } 53 | 54 | pub fn insert_overwrite(&mut self, item: TestItem) -> Vec { 55 | let dup_indexes = self 56 | .items 57 | .iter() 58 | .enumerate() 59 | .filter_map(|(i, e)| { 60 | self.unique_constraint.matches(&item, e).then_some(i) 61 | }) 62 | .collect::>(); 63 | let mut dups = Vec::new(); 64 | 65 | // dup_indexes is in sorted order -- remove items in that order to 66 | // handle shifting indexes. (There are more efficient ways to do this. 67 | // But this is a model, not the system under test, so the goal here is 68 | // to be clear more than to be efficient.) 69 | for i in dup_indexes.iter().rev() { 70 | dups.push(self.items.remove(*i)); 71 | } 72 | 73 | // Now we can push the new item. 74 | self.items.push(item); 75 | dups 76 | } 77 | 78 | pub fn get1(&self, key1: u8) -> Option<&TestItem> { 79 | self.items.iter().find(|e| e.key1 == key1) 80 | } 81 | 82 | pub fn get2(&self, key2: char) -> Option<&TestItem> { 83 | self.items.iter().find(|e| e.key2 == key2) 84 | } 85 | 86 | pub fn get3(&self, key3: &str) -> Option<&TestItem> { 87 | self.items.iter().find(|e| e.key3 == key3) 88 | } 89 | 90 | pub fn remove1(&mut self, key1: u8) -> Option { 91 | let index = self.items.iter().position(|e| e.key1 == key1)?; 92 | Some(self.items.remove(index)) 93 | } 94 | 95 | pub fn remove2(&mut self, key2: char) -> Option { 96 | let index = self.items.iter().position(|e| e.key2 == key2)?; 97 | Some(self.items.remove(index)) 98 | } 99 | 100 | pub fn remove3(&mut self, key3: &str) -> Option { 101 | let index = self.items.iter().position(|e| e.key3 == key3)?; 102 | Some(self.items.remove(index)) 103 | } 104 | 105 | pub fn iter(&self) -> impl Iterator { 106 | self.items.iter() 107 | } 108 | 109 | pub fn first(&self) -> Option<&TestItem> { 110 | self.items.iter().min_by_key(|e| e.key1) 111 | } 112 | 113 | pub fn last(&self) -> Option<&TestItem> { 114 | self.items.iter().max_by_key(|e| e.key1) 115 | } 116 | 117 | pub fn pop_first(&mut self) -> Option { 118 | if self.items.is_empty() { 119 | return None; 120 | } 121 | let index = self 122 | .items 123 | .iter() 124 | .enumerate() 125 | .min_by_key(|(_, e)| e.key1) 126 | .map(|(i, _)| i)?; 127 | Some(self.items.remove(index)) 128 | } 129 | 130 | pub fn pop_last(&mut self) -> Option { 131 | if self.items.is_empty() { 132 | return None; 133 | } 134 | let index = self 135 | .items 136 | .iter() 137 | .enumerate() 138 | .max_by_key(|(_, e)| e.key1) 139 | .map(|(i, _)| i)?; 140 | Some(self.items.remove(index)) 141 | } 142 | 143 | pub fn first_mut(&mut self) -> Option<&mut TestItem> { 144 | if self.items.is_empty() { 145 | return None; 146 | } 147 | let index = self 148 | .items 149 | .iter() 150 | .enumerate() 151 | .min_by_key(|(_, e)| e.key1) 152 | .map(|(i, _)| i)?; 153 | Some(&mut self.items[index]) 154 | } 155 | 156 | pub fn last_mut(&mut self) -> Option<&mut TestItem> { 157 | if self.items.is_empty() { 158 | return None; 159 | } 160 | let index = self 161 | .items 162 | .iter() 163 | .enumerate() 164 | .max_by_key(|(_, e)| e.key1) 165 | .map(|(i, _)| i)?; 166 | Some(&mut self.items[index]) 167 | } 168 | 169 | pub fn retain(&mut self, f: F) 170 | where 171 | F: FnMut(&mut TestItem) -> bool, 172 | { 173 | // Sort items by key1 to match IdOrdMap iteration order 174 | self.items.sort_by_key(|e| e.key1); 175 | 176 | // Retain items matching the predicate 177 | self.items.retain_mut(f); 178 | } 179 | 180 | pub fn clear(&mut self) { 181 | self.items.clear(); 182 | } 183 | } 184 | 185 | /// Which keys to check uniqueness against. 186 | #[derive(Clone, Copy, Debug)] 187 | enum UniqueConstraint { 188 | Key1, 189 | Key12, 190 | Key123, 191 | } 192 | 193 | impl UniqueConstraint { 194 | fn matches(&self, item: &TestItem, other: &TestItem) -> bool { 195 | match self { 196 | UniqueConstraint::Key1 => item.key1 == other.key1, 197 | UniqueConstraint::Key12 => { 198 | item.key1 == other.key1 || item.key2 == other.key2 199 | } 200 | UniqueConstraint::Key123 => { 201 | item.key1 == other.key1 202 | || item.key2 == other.key2 203 | || item.key3 == other.key3 204 | } 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/daft_impls.rs: -------------------------------------------------------------------------------- 1 | //! `Diffable` implementation. 2 | 3 | use super::{IdOrdItem, IdOrdMap}; 4 | use crate::support::daft_utils::IdLeaf; 5 | use core::fmt; 6 | use daft::Diffable; 7 | use equivalent::Comparable; 8 | 9 | impl Diffable for IdOrdMap { 10 | type Diff<'a> 11 | = Diff<'a, T> 12 | where 13 | T: 'a; 14 | 15 | fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> { 16 | let mut diff = Diff::new(); 17 | for item in self { 18 | if let Some(other_item) = other.get(&item.key()) { 19 | diff.common.insert_overwrite(IdLeaf::new(item, other_item)); 20 | } else { 21 | diff.removed.insert_overwrite(item); 22 | } 23 | } 24 | for item in other { 25 | if !self.contains_key(&item.key()) { 26 | diff.added.insert_overwrite(item); 27 | } 28 | } 29 | diff 30 | } 31 | } 32 | 33 | /// A diff of two [`IdOrdMap`]s. 34 | /// 35 | /// Generated by the [`Diffable`] implementation for [`IdOrdMap`]. 36 | /// 37 | /// # Examples 38 | /// 39 | /// ``` 40 | /// use daft::Diffable; 41 | /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; 42 | /// 43 | /// #[derive(Eq, PartialEq, PartialOrd, Ord)] 44 | /// struct Item { 45 | /// id: String, 46 | /// value: u32, 47 | /// } 48 | /// 49 | /// impl IdOrdItem for Item { 50 | /// type Key<'a> = &'a str; 51 | /// fn key(&self) -> Self::Key<'_> { 52 | /// &self.id 53 | /// } 54 | /// id_upcast!(); 55 | /// } 56 | /// 57 | /// // Create two IdOrdMaps with overlapping items. 58 | /// let mut map1 = IdOrdMap::new(); 59 | /// map1.insert_unique(Item { id: "a".to_string(), value: 1 }); 60 | /// map1.insert_unique(Item { id: "b".to_string(), value: 2 }); 61 | /// 62 | /// let mut map2 = IdOrdMap::new(); 63 | /// map2.insert_unique(Item { id: "b".to_string(), value: 3 }); 64 | /// map2.insert_unique(Item { id: "c".to_string(), value: 4 }); 65 | /// 66 | /// // Compute the diff between the two maps. 67 | /// let diff = map1.diff(&map2); 68 | /// 69 | /// // "a" is removed. 70 | /// assert!(diff.removed.contains_key("a")); 71 | /// // "b" is modified (value changed from 2 to 3). 72 | /// assert!(diff.is_modified("b")); 73 | /// // "c" is added. 74 | /// assert!(diff.added.contains_key("c")); 75 | /// ``` 76 | /// 77 | /// [`Diffable`]: daft::Diffable 78 | pub struct Diff<'daft, T: ?Sized + IdOrdItem> { 79 | /// Entries common to both maps. 80 | /// 81 | /// Items are stored as [`IdLeaf`]s to references. 82 | pub common: IdOrdMap>, 83 | 84 | /// Added entries. 85 | pub added: IdOrdMap<&'daft T>, 86 | 87 | /// Removed entries. 88 | pub removed: IdOrdMap<&'daft T>, 89 | } 90 | 91 | impl<'a, 'daft, T> fmt::Debug for Diff<'daft, T> 92 | where 93 | T: ?Sized + IdOrdItem + fmt::Debug, 94 | T::Key<'a>: fmt::Debug, 95 | T: 'a, 96 | 'daft: 'a, 97 | { 98 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 99 | f.debug_struct("Diff") 100 | .field("common", &self.common) 101 | .field("added", &self.added) 102 | .field("removed", &self.removed) 103 | .finish() 104 | } 105 | } 106 | 107 | impl<'daft, T: ?Sized + IdOrdItem> Diff<'daft, T> { 108 | /// Creates a new, empty `Diff`. 109 | pub fn new() -> Self { 110 | Self { 111 | common: IdOrdMap::new(), 112 | added: IdOrdMap::new(), 113 | removed: IdOrdMap::new(), 114 | } 115 | } 116 | } 117 | 118 | impl<'daft, T: ?Sized + IdOrdItem + Eq> Diff<'daft, T> { 119 | /// Returns an iterator over unchanged keys and values. 120 | pub fn unchanged(&self) -> impl Iterator + '_ { 121 | self.common 122 | .iter() 123 | .filter_map(|leaf| leaf.is_unchanged().then_some(*leaf.before())) 124 | } 125 | 126 | /// Returns true if the item corresponding to the key is unchanged. 127 | pub fn is_unchanged<'a, Q>(&'a self, key: &Q) -> bool 128 | where 129 | Q: ?Sized + Comparable>, 130 | { 131 | self.common.get(key).is_some_and(|leaf| leaf.is_unchanged()) 132 | } 133 | 134 | /// Returns the value associated with the key if it is unchanged, 135 | /// otherwise `None`. 136 | pub fn get_unchanged<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> 137 | where 138 | Q: ?Sized + Comparable>, 139 | { 140 | self.common 141 | .get(key) 142 | .and_then(|leaf| leaf.is_unchanged().then_some(*leaf.before())) 143 | } 144 | 145 | /// Returns an iterator over modified keys and values. 146 | pub fn modified(&self) -> impl Iterator> + '_ { 147 | self.common 148 | .iter() 149 | .filter_map(|leaf| leaf.is_modified().then_some(*leaf)) 150 | } 151 | 152 | /// Returns true if the value corresponding to the key is 153 | /// modified. 154 | pub fn is_modified<'a, Q>(&'a self, key: &Q) -> bool 155 | where 156 | Q: ?Sized + Comparable>, 157 | { 158 | self.common.get(key).is_some_and(|leaf| leaf.is_modified()) 159 | } 160 | 161 | /// Returns the [`IdLeaf`] associated with the key if it is modified, 162 | /// otherwise `None`. 163 | pub fn get_modified<'a, Q>(&'a self, key: &Q) -> Option> 164 | where 165 | Q: ?Sized + Comparable>, 166 | { 167 | self.common 168 | .get(key) 169 | .and_then(|leaf| leaf.is_modified().then_some(*leaf)) 170 | } 171 | 172 | /// Returns an iterator over modified keys and values, performing 173 | /// a diff on the values. 174 | /// 175 | /// This is useful when `T::Diff` is a complex type, not just a 176 | /// [`daft::Leaf`]. 177 | pub fn modified_diff(&self) -> impl Iterator> + '_ 178 | where 179 | T: Diffable, 180 | { 181 | self.modified().map(|leaf| leaf.diff_pair()) 182 | } 183 | } 184 | 185 | // Note: not deriving Default here because we don't want to require 186 | // T to be Default. 187 | impl<'daft, T: IdOrdItem> Default for Diff<'daft, T> { 188 | fn default() -> Self { 189 | Self::new() 190 | } 191 | } 192 | 193 | impl IdOrdItem for IdLeaf { 194 | type Key<'a> 195 | = T::Key<'a> 196 | where 197 | T: 'a; 198 | 199 | fn key(&self) -> Self::Key<'_> { 200 | let before_key = self.before().key(); 201 | if before_key != self.after().key() { 202 | panic!("key is different between before and after"); 203 | } 204 | before_key 205 | } 206 | 207 | #[inline] 208 | fn upcast_key<'short, 'long: 'short>( 209 | long: Self::Key<'long>, 210 | ) -> Self::Key<'short> { 211 | T::upcast_key(long) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /crates/iddqd-benches/benches/benches.rs: -------------------------------------------------------------------------------- 1 | //! Benchmarks for iddqd. 2 | //! 3 | //! This is very elementary at the moment. In the future, more benchmarks will 4 | //! live here. 5 | 6 | use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; 7 | use iddqd::{DefaultHashBuilder, IdHashMap, IdOrdMap}; 8 | use iddqd_benches::{RecordBorrowedU32, RecordOwnedU32}; 9 | use iddqd_test_utils::test_item::{TestItem, TestKey1}; 10 | use std::collections::{BTreeMap, HashMap}; 11 | 12 | const SIZES: &[usize] = 13 | &[1, 10, 100, 1_000, 10_000, 50_000, 100_000, 500_000, 1_000_000]; 14 | 15 | fn bench_fn(c: &mut Criterion) { 16 | // Benchmark the id_ord_map::RefMut implementation with a very simple hash 17 | // function. 18 | // 19 | // This aims to benchmark the overhead of RefMut itself, without considering 20 | // how long the hash function takes. 21 | c.bench_function("id_ord_map_ref_mut_simple", |b| { 22 | b.iter_batched_ref( 23 | || { 24 | // Create a new IdOrdMap instance. 25 | let mut map = IdOrdMap::new(); 26 | map.insert_overwrite(TestItem::new(1, 'a', "foo", "bar")); 27 | map 28 | }, 29 | |map| { 30 | let mut item = map.get_mut(&TestKey1::new(&1)).unwrap(); 31 | item.key2 = 'b'; 32 | drop(item); 33 | }, 34 | criterion::BatchSize::SmallInput, 35 | ); 36 | }); 37 | 38 | let mut group = c.benchmark_group("hash_map_u32_get"); 39 | for size in SIZES { 40 | group.bench_with_input( 41 | BenchmarkId::from_parameter(size), 42 | size, 43 | |b, &size| { 44 | b.iter_batched_ref( 45 | || { 46 | let mut map = 47 | HashMap::with_hasher(DefaultHashBuilder::default()); 48 | for i in 0..size as u32 { 49 | map.insert( 50 | i, 51 | RecordOwnedU32 { 52 | index: i, 53 | data: format!("data{}", i), 54 | }, 55 | ); 56 | } 57 | map 58 | }, 59 | |map| { 60 | map.get(&0); 61 | }, 62 | criterion::BatchSize::SmallInput, 63 | ); 64 | }, 65 | ); 66 | } 67 | group.finish(); 68 | 69 | let mut group = c.benchmark_group("id_hash_map_owned_u32_get"); 70 | for size in SIZES { 71 | group.bench_with_input( 72 | BenchmarkId::from_parameter(size), 73 | size, 74 | |b, &size| { 75 | b.iter_batched_ref( 76 | || { 77 | // Create a new IdHashMap instance. 78 | let mut map = IdHashMap::new(); 79 | for i in 0..size as u32 { 80 | map.insert_overwrite(RecordOwnedU32 { 81 | index: i, 82 | data: format!("data{}", i), 83 | }); 84 | } 85 | map 86 | }, 87 | |map| { 88 | map.get(&0); 89 | }, 90 | criterion::BatchSize::SmallInput, 91 | ); 92 | }, 93 | ); 94 | } 95 | group.finish(); 96 | 97 | let mut group = c.benchmark_group("id_hash_map_borrowed_u32_get"); 98 | for size in SIZES { 99 | group.bench_with_input( 100 | BenchmarkId::from_parameter(size), 101 | size, 102 | |b, &size| { 103 | b.iter_batched_ref( 104 | || { 105 | // Create a new IdHashMap instance. 106 | let mut map = IdHashMap::new(); 107 | for i in 0..size as u32 { 108 | map.insert_overwrite(RecordBorrowedU32 { 109 | index: i, 110 | data: format!("data{}", i), 111 | }); 112 | } 113 | map 114 | }, 115 | |map| { 116 | map.get(&0); 117 | }, 118 | criterion::BatchSize::SmallInput, 119 | ); 120 | }, 121 | ); 122 | } 123 | group.finish(); 124 | 125 | let mut group = c.benchmark_group("btree_map_u32_get"); 126 | for size in SIZES { 127 | group.bench_with_input( 128 | BenchmarkId::from_parameter(size), 129 | size, 130 | |b, &size| { 131 | b.iter_batched_ref( 132 | || { 133 | let mut map = BTreeMap::new(); 134 | for i in 0..size as u32 { 135 | map.insert( 136 | i, 137 | RecordOwnedU32 { 138 | index: i, 139 | data: format!("data{}", i), 140 | }, 141 | ); 142 | } 143 | map 144 | }, 145 | |map| { 146 | map.get(&0); 147 | }, 148 | criterion::BatchSize::SmallInput, 149 | ); 150 | }, 151 | ); 152 | } 153 | group.finish(); 154 | 155 | let mut group = c.benchmark_group("id_ord_map_owned_u32_get"); 156 | for size in SIZES { 157 | group.bench_with_input( 158 | BenchmarkId::from_parameter(size), 159 | size, 160 | |b, &size| { 161 | b.iter_batched_ref( 162 | || { 163 | // Create a new IdOrdMap instance. 164 | let mut map = IdOrdMap::new(); 165 | for i in 0..size as u32 { 166 | map.insert_overwrite(RecordOwnedU32 { 167 | index: i, 168 | data: format!("data{}", i), 169 | }); 170 | } 171 | map 172 | }, 173 | |map| { 174 | map.get(&0); 175 | }, 176 | criterion::BatchSize::SmallInput, 177 | ); 178 | }, 179 | ); 180 | } 181 | group.finish(); 182 | 183 | let mut group = c.benchmark_group("id_ord_map_borrowed_u32_get"); 184 | for size in SIZES { 185 | group.bench_with_input( 186 | BenchmarkId::from_parameter(size), 187 | size, 188 | |b, &size| { 189 | b.iter_batched_ref( 190 | || { 191 | // Create a new IdOrdMap instance. 192 | let mut map = IdOrdMap::new(); 193 | for i in 0..size as u32 { 194 | map.insert_overwrite(RecordBorrowedU32 { 195 | index: i, 196 | data: format!("data{}", i), 197 | }); 198 | } 199 | map 200 | }, 201 | |map| { 202 | map.get(&0); 203 | }, 204 | criterion::BatchSize::SmallInput, 205 | ); 206 | }, 207 | ); 208 | } 209 | group.finish(); 210 | } 211 | 212 | criterion_group!(benches, bench_fn); 213 | criterion_main!(benches); 214 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/serde_impls.rs: -------------------------------------------------------------------------------- 1 | use super::{IdOrdItem, IdOrdMap}; 2 | use core::{fmt, marker::PhantomData}; 3 | use serde_core::{ 4 | Deserialize, Deserializer, Serialize, Serializer, 5 | de::{MapAccess, SeqAccess, Visitor}, 6 | ser::{SerializeMap, SerializeSeq}, 7 | }; 8 | 9 | /// An `IdOrdMap` serializes to the list of items. Items are serialized in 10 | /// order of their keys. 11 | /// 12 | /// Serializing as a list of items rather than as a map works around the lack of 13 | /// non-string keys in formats like JSON. 14 | /// 15 | /// # Examples 16 | /// 17 | /// ``` 18 | /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; 19 | /// # use iddqd_test_utils::serde_json; 20 | /// use serde::{Deserialize, Serialize}; 21 | /// 22 | /// #[derive(Debug, Serialize)] 23 | /// struct Item { 24 | /// id: u32, 25 | /// name: String, 26 | /// email: String, 27 | /// } 28 | /// 29 | /// // This is a complex key, so it can't be a JSON map key. 30 | /// #[derive(Eq, PartialEq, PartialOrd, Ord)] 31 | /// struct ComplexKey<'a> { 32 | /// id: u32, 33 | /// email: &'a str, 34 | /// } 35 | /// 36 | /// impl IdOrdItem for Item { 37 | /// type Key<'a> = ComplexKey<'a>; 38 | /// fn key(&self) -> Self::Key<'_> { 39 | /// ComplexKey { id: self.id, email: &self.email } 40 | /// } 41 | /// id_upcast!(); 42 | /// } 43 | /// 44 | /// let mut map = IdOrdMap::::new(); 45 | /// map.insert_unique(Item { 46 | /// id: 1, 47 | /// name: "Alice".to_string(), 48 | /// email: "alice@example.com".to_string(), 49 | /// }) 50 | /// .unwrap(); 51 | /// 52 | /// // The map is serialized as a list of items in order of their keys. 53 | /// let serialized = serde_json::to_string(&map).unwrap(); 54 | /// assert_eq!( 55 | /// serialized, 56 | /// r#"[{"id":1,"name":"Alice","email":"alice@example.com"}]"#, 57 | /// ); 58 | /// ``` 59 | impl Serialize for IdOrdMap 60 | where 61 | T: Serialize, 62 | { 63 | fn serialize( 64 | &self, 65 | serializer: S, 66 | ) -> Result { 67 | let mut seq = serializer.serialize_seq(Some(self.len()))?; 68 | for item in self { 69 | seq.serialize_element(item)?; 70 | } 71 | seq.end() 72 | } 73 | } 74 | 75 | /// The `Deserialize` impl deserializes from either a sequence or a map of items, 76 | /// rebuilding the indexes and producing an error if there are any duplicates. 77 | /// 78 | /// The `fmt::Debug` bound on `T` ensures better error reporting. 79 | impl<'de, T: IdOrdItem + fmt::Debug> Deserialize<'de> for IdOrdMap 80 | where 81 | T: Deserialize<'de>, 82 | { 83 | fn deserialize(deserializer: D) -> Result 84 | where 85 | D: Deserializer<'de>, 86 | { 87 | deserializer.deserialize_any(SeqVisitor { _marker: PhantomData }) 88 | } 89 | } 90 | 91 | struct SeqVisitor { 92 | _marker: PhantomData T>, 93 | } 94 | 95 | impl<'de, T> Visitor<'de> for SeqVisitor 96 | where 97 | T: IdOrdItem + Deserialize<'de> + fmt::Debug, 98 | { 99 | type Value = IdOrdMap; 100 | 101 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 102 | formatter 103 | .write_str("a sequence or map of items representing an IdOrdMap") 104 | } 105 | 106 | fn visit_seq( 107 | self, 108 | mut seq: Access, 109 | ) -> Result 110 | where 111 | Access: SeqAccess<'de>, 112 | { 113 | let mut map = match seq.size_hint() { 114 | Some(size) => IdOrdMap::with_capacity(size), 115 | None => IdOrdMap::new(), 116 | }; 117 | 118 | while let Some(element) = seq.next_element()? { 119 | map.insert_unique(element) 120 | .map_err(serde_core::de::Error::custom)?; 121 | } 122 | 123 | Ok(map) 124 | } 125 | 126 | fn visit_map( 127 | self, 128 | mut map_access: Access, 129 | ) -> Result 130 | where 131 | Access: MapAccess<'de>, 132 | { 133 | let mut map = IdOrdMap::new(); 134 | 135 | while let Some((_, value)) = 136 | map_access.next_entry::()? 137 | { 138 | map.insert_unique(value).map_err(serde_core::de::Error::custom)?; 139 | } 140 | 141 | Ok(map) 142 | } 143 | } 144 | 145 | /// Marker type for [`IdOrdMap`] serialized as a map, for use with serde's 146 | /// `with` attribute. 147 | /// 148 | /// # Examples 149 | /// 150 | /// Use with serde's `with` attribute: 151 | /// 152 | /// ``` 153 | /// use iddqd::{IdOrdItem, IdOrdMap, id_ord_map::IdOrdMapAsMap, id_upcast}; 154 | /// use serde::{Deserialize, Serialize}; 155 | /// 156 | /// #[derive(Debug, Serialize, Deserialize)] 157 | /// struct Item { 158 | /// id: u32, 159 | /// name: String, 160 | /// } 161 | /// 162 | /// impl IdOrdItem for Item { 163 | /// type Key<'a> = u32; 164 | /// fn key(&self) -> Self::Key<'_> { 165 | /// self.id 166 | /// } 167 | /// id_upcast!(); 168 | /// } 169 | /// 170 | /// #[derive(Serialize, Deserialize)] 171 | /// struct Config { 172 | /// #[serde(with = "IdOrdMapAsMap")] 173 | /// items: IdOrdMap, 174 | /// } 175 | /// ``` 176 | /// 177 | /// # Requirements 178 | /// 179 | /// - For serialization, the key type must implement [`Serialize`]. 180 | /// - For JSON serialization, the key should be string-like or convertible to a string key. 181 | pub struct IdOrdMapAsMap { 182 | _marker: PhantomData T>, 183 | } 184 | 185 | struct MapVisitorAsMap { 186 | _marker: PhantomData T>, 187 | } 188 | 189 | impl<'de, T> Visitor<'de> for MapVisitorAsMap 190 | where 191 | T: IdOrdItem + Deserialize<'de> + fmt::Debug, 192 | { 193 | type Value = IdOrdMap; 194 | 195 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 196 | formatter.write_str("a map with items representing an IdOrdMap") 197 | } 198 | 199 | fn visit_map( 200 | self, 201 | mut map_access: Access, 202 | ) -> Result 203 | where 204 | Access: MapAccess<'de>, 205 | { 206 | let mut map = IdOrdMap::new(); 207 | 208 | while let Some((_, value)) = 209 | map_access.next_entry::()? 210 | { 211 | map.insert_unique(value).map_err(serde_core::de::Error::custom)?; 212 | } 213 | 214 | Ok(map) 215 | } 216 | } 217 | 218 | impl IdOrdMapAsMap { 219 | /// Serializes an `IdOrdMap` as a JSON object/map using `key()` as keys. 220 | pub fn serialize<'a, Ser>( 221 | map: &IdOrdMap, 222 | serializer: Ser, 223 | ) -> Result 224 | where 225 | T: 'a + IdOrdItem + Serialize, 226 | T::Key<'a>: Serialize, 227 | Ser: Serializer, 228 | { 229 | let mut ser_map = serializer.serialize_map(Some(map.len()))?; 230 | for item in map.iter() { 231 | // SAFETY: 232 | // 233 | // * Lifetime extension: for a type T and two lifetime params 'a and 234 | // 'b, T<'a> and T<'b> aren't guaranteed to have the same layout, 235 | // but (a) that is true today and (b) it would be shocking and 236 | // break half the Rust ecosystem if that were to change in the 237 | // future. 238 | // * We only use key within the scope of this block before 239 | // immediately dropping it. In particular, ser_map.serialize_entry 240 | // serializes the key without holding a reference to it. 241 | let key1 = unsafe { 242 | core::mem::transmute::, T::Key<'a>>(item.key()) 243 | }; 244 | ser_map.serialize_entry(&key1, item)?; 245 | } 246 | ser_map.end() 247 | } 248 | 249 | /// Deserializes an `IdOrdMap` from a JSON object/map. 250 | pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> 251 | where 252 | T: IdOrdItem + Deserialize<'de> + fmt::Debug, 253 | D: Deserializer<'de>, 254 | { 255 | deserializer.deserialize_map(MapVisitorAsMap { _marker: PhantomData }) 256 | } 257 | } 258 | --------------------------------------------------------------------------------