├── .cargo ├── config.toml └── mutants.toml ├── .config └── nextest.toml ├── .github ├── renovate.json └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── Justfile ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── crates ├── iddqd-extended-examples │ ├── Cargo.toml │ ├── README.md │ ├── examples │ │ └── bumpalo-alloc.rs │ └── src │ │ └── lib.rs ├── iddqd-test-utils │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── eq_props.rs │ │ ├── lib.rs │ │ ├── naive_map.rs │ │ ├── serde_utils.rs │ │ ├── test_item.rs │ │ └── unwind.rs └── iddqd │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ ├── examples │ ├── README.md │ ├── bi-complex.rs │ ├── id-complex.rs │ └── tri-complex.rs │ ├── src │ ├── bi_hash_map │ │ ├── daft_impls.rs │ │ ├── entry.rs │ │ ├── entry_indexes.rs │ │ ├── imp.rs │ │ ├── iter.rs │ │ ├── mod.rs │ │ ├── ref_mut.rs │ │ ├── serde_impls.rs │ │ ├── tables.rs │ │ └── trait_defs.rs │ ├── errors.rs │ ├── id_hash_map │ │ ├── daft_impls.rs │ │ ├── entry.rs │ │ ├── imp.rs │ │ ├── iter.rs │ │ ├── mod.rs │ │ ├── ref_mut.rs │ │ ├── serde_impls.rs │ │ ├── tables.rs │ │ └── trait_defs.rs │ ├── id_ord_map │ │ ├── daft_impls.rs │ │ ├── entry.rs │ │ ├── imp.rs │ │ ├── iter.rs │ │ ├── mod.rs │ │ ├── ref_mut.rs │ │ ├── serde_impls.rs │ │ ├── tables.rs │ │ └── trait_defs.rs │ ├── internal.rs │ ├── lib.rs │ ├── macros.rs │ ├── support │ │ ├── alloc.rs │ │ ├── borrow.rs │ │ ├── btree_table.rs │ │ ├── daft_utils.rs │ │ ├── fmt_utils.rs │ │ ├── hash_builder.rs │ │ ├── hash_table.rs │ │ ├── item_set.rs │ │ ├── map_hash.rs │ │ └── mod.rs │ └── tri_hash_map │ │ ├── daft_impls.rs │ │ ├── imp.rs │ │ ├── iter.rs │ │ ├── mod.rs │ │ ├── ref_mut.rs │ │ ├── serde_impls.rs │ │ ├── tables.rs │ │ └── trait_defs.rs │ └── tests │ └── integration │ ├── bi_hash_map.rs │ ├── id_hash_map.rs │ ├── id_ord_map.rs │ ├── main.rs │ └── tri_hash_map.rs ├── release.toml └── rustfmt.toml /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xfmt = "fmt -- --config imports_granularity=Crate --config group_imports=One --config format_code_in_doc_comments=true" 3 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | [profile.default-miri] 2 | # proptests are too slow to be run through miri 3 | default-filter = "not test(proptest)" 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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"] 16 | env: 17 | RUSTFLAGS: -D warnings 18 | steps: 19 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 20 | - uses: dtolnay/rust-toolchain@stable 21 | with: 22 | components: rustfmt, clippy 23 | - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 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 }}/4 clippy --all-targets 32 | - name: Lint (rustfmt) 33 | run: cargo xfmt --check 34 | - name: Run rustdoc 35 | run: just rustdoc 36 | - name: Install nightly toolchain for cargo-sync-rdme 37 | uses: dtolnay/rust-toolchain@nightly 38 | - name: Regenerate readmes 39 | run: just generate-readmes 40 | - name: Check for differences 41 | run: git diff --exit-code 42 | 43 | build-and-test: 44 | name: Build and test 45 | runs-on: ${{ matrix.os }} 46 | strategy: 47 | matrix: 48 | os: [ubuntu-latest] 49 | # 1.81 is the MSRV 50 | rust-version: ["1.81", "stable"] 51 | partition: ["1", "2", "3", "4"] 52 | fail-fast: false 53 | env: 54 | RUSTFLAGS: -D warnings 55 | steps: 56 | - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 57 | - uses: dtolnay/rust-toolchain@master 58 | with: 59 | toolchain: ${{ matrix.rust-version }} 60 | - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 61 | with: 62 | key: partition-${{ matrix.partition }} 63 | - uses: taiki-e/install-action@cargo-hack 64 | - uses: taiki-e/install-action@just 65 | - uses: taiki-e/install-action@nextest 66 | - name: Build 67 | run: just powerset --partition ${{ matrix.partition }}/4 build 68 | - name: Run tests 69 | run: just powerset --partition ${{ matrix.partition }}/4 nextest run 70 | - name: Doctests 71 | run: just powerset --partition ${{ matrix.partition }}/4 test --doc 72 | 73 | build-no-std: 74 | name: Build on no-std 75 | runs-on: ${{ matrix.os }} 76 | strategy: 77 | matrix: 78 | os: [ubuntu-latest] 79 | # 1.81 is the MSRV 80 | rust-version: ["1.81", "stable"] 81 | fail-fast: false 82 | env: 83 | RUSTFLAGS: -D warnings 84 | steps: 85 | - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 86 | - uses: dtolnay/rust-toolchain@master 87 | with: 88 | toolchain: ${{ matrix.rust-version }} 89 | - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 90 | - uses: taiki-e/install-action@cross 91 | - name: Check 92 | run: cross check --target thumbv7em-none-eabi --no-default-features -p iddqd 93 | 94 | miri: 95 | name: Run tests with miri 96 | runs-on: ubuntu-latest 97 | env: 98 | RUSTFLAGS: -D warnings 99 | steps: 100 | - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 101 | - uses: dtolnay/rust-toolchain@master 102 | with: 103 | toolchain: nightly 104 | components: miri 105 | - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 106 | - uses: taiki-e/install-action@cargo-hack 107 | - uses: taiki-e/install-action@nextest 108 | - name: Run tests 109 | run: cargo +nightly miri nextest run 110 | - name: Doctests 111 | run: cargo +nightly miri test --doc 112 | -------------------------------------------------------------------------------- /.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 | release: 11 | if: github.repository_owner == 'oxidecomputer' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 15 | with: 16 | persist-credentials: false 17 | - name: Install Rust 18 | uses: dtolnay/rust-toolchain@stable 19 | - name: Install cargo release 20 | uses: taiki-e/install-action@5bc300ae6202155e070ca83afb334e8aac18c49c # v2 21 | with: 22 | tool: cargo-release@0.25.17,just 23 | - uses: taiki-e/create-gh-release-action@b7abb0cf5e72cb5500307b577f9ca3fd4c5be9d2 # v1 24 | with: 25 | prefix: iddqd 26 | changelog: CHANGELOG.md 27 | title: $prefix $version 28 | branch: main 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | - run: just ci-cargo-release 32 | env: 33 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /mutants.out* 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.3.3] - 2025-05-27 4 | 5 | ### Added 6 | 7 | - A lot of new documentation. Most functions now have doctests. 8 | 9 | ### Fixed 10 | 11 | - Serde implementations no longer do internal buffering. 12 | - Serde implementations now reserve capacity if the size hint is available; thanks [@aatifsyed](https://github.com/aatifsyed) for your first contribution! 13 | - A few unnecessary bounds have been loosened. 14 | 15 | ## [0.3.2] - 2025-05-24 16 | 17 | ### Added 18 | 19 | - The hash map types now support custom hashers. 20 | - With the new `allocator-api2` feature (enabled by default), the hash map types now support custom allocators, including on stable. See [the bumpalo-alloc example](https://github.com/oxidecomputer/iddqd/blob/940c661095cf23c97b4769c9e0fdf9b95e9a7670/crates/iddqd-extended-examples/examples/bumpalo-alloc.rs#L31). 21 | - Added some documentation explaining iteration order. 22 | - Added a note in the README and `lib.rs` that small copyable keys like integers are best returned as owned ones. 23 | 24 | ### Changed 25 | 26 | - Dropped the `Ord` requirement for `Comparable` keys. (The `Hash` requirement for `Equivalent` continues to be required.) 27 | 28 | ## [0.3.1] - 2025-05-22 29 | 30 | ### Added 31 | 32 | - Re-export `equivalent::Equivalent` and `equivalent::Comparable`. 33 | 34 | ## [0.3.0] - 2025-05-22 35 | 36 | ### Changed 37 | 38 | - Lookups now use [`equivalent::Equivalent`] or [`equivalent::Comparable`], which are strictly more general than `Borrow`. 39 | - `get_mut` and `remove` methods no longer require the key type; the borrow checker limitation has been worked around. 40 | 41 | [`equivalent::Equivalent`]: https://docs.rs/equivalent/1.0.2/equivalent/trait.Equivalent.html 42 | [`equivalent::Comparable`]: https://docs.rs/equivalent/1.0.2/equivalent/trait.Comparable.html 43 | 44 | ## [0.2.1] - 2025-05-22 45 | 46 | ### Fixed 47 | 48 | * `MapLeaf<'a, T>`'s `Clone` and `Copy` no longer require `T` to be `Clone` or `Copy`. (`MapLeaf` is just a couple of references, so this is never necessary.) 49 | 50 | ## [0.2.0] - 2025-05-21 51 | 52 | ### Added 53 | 54 | - `Extend` implementations. 55 | 56 | ### Changed 57 | 58 | - Daft implementations for `BiHashMap` and `TriHashMap` changed to also allow diffing by individual keys. 59 | 60 | ## [0.1.2] - 2025-05-21 61 | 62 | ### Added 63 | 64 | - `BiHashMap` and `TriHashMap` now have a `remove_unique` method which removes an item uniquely indexed by all keys. 65 | 66 | ### Changed 67 | 68 | * `upcast` macros are now annotated with `#[inline]`, since they're trivial. 69 | 70 | ## [0.1.1] - 2025-05-21 71 | 72 | ### Added 73 | 74 | - [Daft](https://docs.rs/daft) implementations with the new `daft` feature. 75 | - `BiHashItem` implementations for reference types like `&'a T` and `Box`. 76 | 77 | ## [0.1.0] - 2025-05-21 78 | 79 | Initial release. 80 | 81 | [0.3.3]: https://github.com/oxidecomputer/iddqd/releases/iddqd-0.3.3 82 | [0.3.2]: https://github.com/oxidecomputer/iddqd/releases/iddqd-0.3.2 83 | [0.3.1]: https://github.com/oxidecomputer/iddqd/releases/iddqd-0.3.1 84 | [0.3.0]: https://github.com/oxidecomputer/iddqd/releases/iddqd-0.3.0 85 | [0.2.1]: https://github.com/oxidecomputer/iddqd/releases/iddqd-0.2.1 86 | [0.2.0]: https://github.com/oxidecomputer/iddqd/releases/iddqd-0.2.0 87 | [0.1.2]: https://github.com/oxidecomputer/iddqd/releases/iddqd-0.1.2 88 | [0.1.1]: https://github.com/oxidecomputer/iddqd/releases/iddqd-0.1.1 89 | [0.1.0]: https://github.com/oxidecomputer/iddqd/releases/iddqd-0.1.0 90 | -------------------------------------------------------------------------------- /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.rust] 11 | unexpected_cfgs = { level = "warn", check-cfg = ["cfg(doc_cfg)"] } 12 | 13 | [workspace.dependencies] 14 | allocator-api2 = { version = "0.2.21", default-features = false, features = ["alloc"] } 15 | bumpalo = { version = "3.17.0", features = ["allocator-api2", "collections"] } 16 | daft = { version = "0.1.3", default-features = false } 17 | equivalent = "1.0.2" 18 | foldhash = "0.1.5" 19 | # We have to turn on hashbrown's allocator-api2 feature even if we don't expose 20 | # it in our public API. There's no way to refer to the hashbrown Allocator trait 21 | # without it. (The alternative would be to define everything twice: if 22 | # allocator-api2 is turned on, then for e.g. IdHashMap, otherwise 23 | # IdHashMap.) 24 | hashbrown = { version = "0.15.2", default-features = false, features = ["allocator-api2", "inline-more"] } 25 | hugealloc = "0.1.1" 26 | iddqd = { path = "crates/iddqd", default-features = false } 27 | iddqd-test-utils = { path = "crates/iddqd-test-utils" } 28 | proptest = "1.6.0" 29 | ref-cast = "1.0.24" 30 | rustc-hash = { version = "2.1.1", default-features = false } 31 | serde = { version = "1.0.219", features = ["derive"] } 32 | serde_json = "1.0.140" 33 | test-strategy = "0.4.1" 34 | 35 | [profile.dev] 36 | # Builds with opt-level 1 speed up test runs by around 20x. 37 | opt-level = 1 38 | -------------------------------------------------------------------------------- /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 --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-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) 2016 Alex Crichton 190 | Copyright (c) 2017 The Tokio Authors 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | crates/iddqd/README.md -------------------------------------------------------------------------------- /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 | [dependencies] 13 | bumpalo.workspace = true 14 | iddqd = { workspace = true, features = ["allocator-api2", "default-hasher"] } 15 | 16 | [package.metadata.release] 17 | release = false 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-extended-examples/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Dummy lib.rs to satisfy `cargo test --doc`. 2 | -------------------------------------------------------------------------------- /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, 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-test-utils/README.md: -------------------------------------------------------------------------------- 1 | # iddqd-test-utils 2 | 3 | Test helpers for the iddqd crate. 4 | -------------------------------------------------------------------------------- /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/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod eq_props; 2 | pub mod naive_map; 3 | #[cfg(feature = "serde")] 4 | pub mod serde_utils; 5 | pub mod test_item; 6 | pub mod unwind; 7 | 8 | /// Re-exports the `bumpalo` crate if the `allocator-api2` feature is enabled -- 9 | /// used by doctests. 10 | #[cfg(feature = "allocator-api2")] 11 | pub use bumpalo; 12 | /// Re-exports `serde_json` if the `serde` feature is enabled -- used by 13 | /// doctests. 14 | #[cfg(feature = "serde")] 15 | pub use serde_json; 16 | -------------------------------------------------------------------------------- /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 | 110 | /// Which keys to check uniqueness against. 111 | #[derive(Clone, Copy, Debug)] 112 | enum UniqueConstraint { 113 | Key1, 114 | Key12, 115 | Key123, 116 | } 117 | 118 | impl UniqueConstraint { 119 | fn matches(&self, item: &TestItem, other: &TestItem) -> bool { 120 | match self { 121 | UniqueConstraint::Key1 => item.key1 == other.key1, 122 | UniqueConstraint::Key12 => { 123 | item.key1 == other.key1 || item.key2 == other.key2 124 | } 125 | UniqueConstraint::Key123 => { 126 | item.key1 == other.key1 127 | || item.key2 == other.key2 128 | || item.key3 == other.key3 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /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 | 7 | pub fn assert_serialize_roundtrip(values: Vec) 8 | where 9 | M: ItemMap + Serialize, 10 | { 11 | let mut map = M::make_new(); 12 | let mut first_error = None; 13 | for value in values.clone() { 14 | // Ignore errors from duplicates which are quite possible to occur 15 | // here, since we're just testing serialization. But store the 16 | // first error to ensure that deserialization returns errors. 17 | if let Err(error) = map.insert_unique(value) { 18 | if first_error.is_none() { 19 | first_error = Some(error.into_owned()); 20 | } 21 | } 22 | } 23 | 24 | let serialized = serde_json::to_string(&map).unwrap(); 25 | let deserialized: M = M::make_deserialize_in( 26 | &mut serde_json::Deserializer::from_str(&serialized), 27 | ) 28 | .unwrap(); 29 | deserialized 30 | .validate_(ValidateCompact::Compact) 31 | .expect("deserialized map is valid"); 32 | 33 | let mut map_items = map.iter().collect::>(); 34 | let mut deserialized_items = deserialized.iter().collect::>(); 35 | 36 | match M::map_kind() { 37 | MapKind::Ord => { 38 | // No sorting required -- we expect the items to be in order. 39 | } 40 | MapKind::Hash => { 41 | // Sort the items, since we don't care about the order. 42 | map_items.sort(); 43 | deserialized_items.sort(); 44 | } 45 | } 46 | assert_eq!(map_items, deserialized_items, "items match"); 47 | 48 | // Try deserializing the full list of values directly, and see that the 49 | // error reported is the same as first_error. 50 | // 51 | // Here, we rely on the fact that the map is serialized as just a vector. 52 | let serialized = serde_json::to_string(&values).unwrap(); 53 | let res: Result = M::make_deserialize_in( 54 | &mut serde_json::Deserializer::from_str(&serialized), 55 | ); 56 | match (first_error, res) { 57 | (None, Ok(_)) => {} // No error, should be fine 58 | (Some(first_error), Ok(_)) => { 59 | panic!( 60 | "expected error ({first_error}), but deserialization succeeded" 61 | ) 62 | } 63 | (None, Err(error)) => { 64 | panic!( 65 | "unexpected error: {error}, deserialization should have succeeded" 66 | ) 67 | } 68 | (Some(first_error), Err(error)) => { 69 | // first_error is the error from the map, and error is the 70 | // deserialization error (which should always be a custom error, 71 | // stored as a string). 72 | let expected = first_error.to_string(); 73 | let actual = error.to_string(); 74 | 75 | // Ensure that line and column numbers are reported. 76 | let Some((actual_prefix, _)) = actual.rsplit_once(" at line ") 77 | else { 78 | panic!( 79 | "error does not contain line number at the end: {actual}" 80 | ); 81 | }; 82 | assert_eq!(actual_prefix, expected, "error matches"); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /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/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iddqd" 3 | version = "0.3.3" 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 = ["id_map", "bijective", "hashmap", "btreemap", "no_std"] 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 | serde = { workspace = true, optional = true } 34 | 35 | [dev-dependencies] 36 | iddqd-test-utils.workspace = true 37 | proptest.workspace = true 38 | test-strategy.workspace = true 39 | 40 | [features] 41 | allocator-api2 = ["iddqd-test-utils/allocator-api2"] 42 | daft = ["dep:daft", "dep:ref-cast"] 43 | default = ["allocator-api2", "std", "default-hasher"] 44 | default-hasher = ["dep:foldhash", "iddqd-test-utils/default-hasher"] 45 | std = ["dep:foldhash", "iddqd-test-utils/std", "rustc-hash/std"] 46 | serde = ["dep:serde", "iddqd-test-utils/serde"] 47 | 48 | [package.metadata.cargo-sync-rdme.badge.badges] 49 | license = true 50 | crates-io = true 51 | docs-rs = true 52 | rust-version = true 53 | 54 | [[example]] 55 | name = "id-complex" 56 | required-features = ["default-hasher", "std"] 57 | 58 | [[example]] 59 | name = "bi-complex" 60 | required-features = ["default-hasher"] 61 | 62 | [[example]] 63 | name = "tri-complex" 64 | required-features = ["default-hasher"] 65 | -------------------------------------------------------------------------------- /crates/iddqd/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../../LICENSE-APACHE -------------------------------------------------------------------------------- /crates/iddqd/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../../LICENSE-MIT -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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(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/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 | mod ref_mut; 12 | #[cfg(feature = "serde")] 13 | mod serde_impls; 14 | mod tables; 15 | pub(crate) mod trait_defs; 16 | 17 | #[cfg(feature = "daft")] 18 | pub use daft_impls::{ByK1, ByK2, Diff, MapLeaf}; 19 | pub use entry::{ 20 | Entry, OccupiedEntry, OccupiedEntryMut, OccupiedEntryRef, VacantEntry, 21 | }; 22 | pub use imp::BiHashMap; 23 | pub use iter::{IntoIter, Iter, IterMut}; 24 | pub use ref_mut::RefMut; 25 | pub use trait_defs::BiHashItem; 26 | -------------------------------------------------------------------------------- /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(hashes: [MapHash; 2], borrowed: &'a mut T) -> Self { 55 | Self { inner: Some(RefMutInner { hashes, borrowed }) } 56 | } 57 | 58 | /// Borrows self into a shorter-lived `RefMut`. 59 | /// 60 | /// This `RefMut` will also check hash equality on drop. 61 | pub fn reborrow(&mut self) -> RefMut<'_, T, S> { 62 | let inner = self.inner.as_mut().unwrap(); 63 | let borrowed = &mut *inner.borrowed; 64 | RefMut::new(inner.hashes.clone(), borrowed) 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 Drop for RefMut<'_, T, S> { 75 | fn drop(&mut self) { 76 | if let Some(inner) = self.inner.take() { 77 | inner.into_ref(); 78 | } 79 | } 80 | } 81 | 82 | impl Deref for RefMut<'_, T, S> { 83 | type Target = T; 84 | 85 | fn deref(&self) -> &Self::Target { 86 | self.inner.as_ref().unwrap().borrowed 87 | } 88 | } 89 | 90 | impl DerefMut for RefMut<'_, T, S> { 91 | fn deref_mut(&mut self) -> &mut Self::Target { 92 | self.inner.as_mut().unwrap().borrowed 93 | } 94 | } 95 | 96 | impl fmt::Debug 97 | for RefMut<'_, T, S> 98 | { 99 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 100 | match self.inner { 101 | Some(ref inner) => inner.fmt(f), 102 | None => { 103 | f.debug_struct("RefMut").field("borrowed", &"missing").finish() 104 | } 105 | } 106 | } 107 | } 108 | 109 | struct RefMutInner<'a, T: BiHashItem, S> { 110 | hashes: [MapHash; 2], 111 | borrowed: &'a mut T, 112 | } 113 | 114 | impl<'a, T: BiHashItem, S: BuildHasher> RefMutInner<'a, T, S> { 115 | fn into_ref(self) -> &'a T { 116 | if !self.hashes[0].is_same_hash(self.borrowed.key1()) { 117 | panic!("key1 changed during RefMut borrow"); 118 | } 119 | if !self.hashes[1].is_same_hash(self.borrowed.key2()) { 120 | panic!("key2 changed during RefMut borrow"); 121 | } 122 | 123 | self.borrowed 124 | } 125 | } 126 | 127 | impl fmt::Debug for RefMutInner<'_, T, S> { 128 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 129 | self.borrowed.fmt(f) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /crates/iddqd/src/bi_hash_map/serde_impls.rs: -------------------------------------------------------------------------------- 1 | use crate::{BiHashItem, BiHashMap, support::alloc::Allocator}; 2 | use core::{fmt, hash::BuildHasher, marker::PhantomData}; 3 | use serde::{ 4 | Deserialize, Serialize, Serializer, 5 | de::{SeqAccess, Visitor}, 6 | }; 7 | 8 | /// A `BiHashMap` serializes to the list of items. Items are serialized in 9 | /// arbitrary order. 10 | /// 11 | /// Serializing as a list of items rather than as a map works around the lack of 12 | /// non-string keys in formats like JSON. 13 | /// 14 | /// # Examples 15 | /// 16 | /// ``` 17 | /// # #[cfg(feature = "default-hasher")] { 18 | /// use iddqd::{BiHashItem, BiHashMap, bi_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 | /// value: usize, 28 | /// } 29 | /// 30 | /// // This is a complex key, so it can't be a JSON map key. 31 | /// #[derive(Eq, Hash, PartialEq)] 32 | /// struct ComplexKey<'a> { 33 | /// name: &'a str, 34 | /// email: &'a str, 35 | /// } 36 | /// 37 | /// impl BiHashItem for Item { 38 | /// type K1<'a> = u32; 39 | /// type K2<'a> = ComplexKey<'a>; 40 | /// fn key1(&self) -> Self::K1<'_> { 41 | /// self.id 42 | /// } 43 | /// fn key2(&self) -> Self::K2<'_> { 44 | /// ComplexKey { name: &self.name, email: &self.email } 45 | /// } 46 | /// bi_upcast!(); 47 | /// } 48 | /// 49 | /// let mut map = BiHashMap::::new(); 50 | /// map.insert_unique(Item { 51 | /// id: 1, 52 | /// name: "Alice".to_string(), 53 | /// email: "alice@example.com".to_string(), 54 | /// value: 42, 55 | /// }) 56 | /// .unwrap(); 57 | /// 58 | /// // The map is serialized as a list of items. 59 | /// let serialized = serde_json::to_string(&map).unwrap(); 60 | /// assert_eq!( 61 | /// serialized, 62 | /// r#"[{"id":1,"name":"Alice","email":"alice@example.com","value":42}]"#, 63 | /// ); 64 | /// # } 65 | /// ``` 66 | impl Serialize 67 | for BiHashMap 68 | where 69 | T: Serialize, 70 | { 71 | fn serialize( 72 | &self, 73 | serializer: Ser, 74 | ) -> Result { 75 | // Serialize just the items -- don't serialize the indexes. We'll 76 | // rebuild the indexes on deserialization. 77 | self.items.serialize(serializer) 78 | } 79 | } 80 | 81 | /// The `Deserialize` impl for `BiHashMap` deserializes the list of items while 82 | /// rebuilding the indexes, producing an error if there are any duplicates. 83 | /// 84 | /// The `fmt::Debug` bound on `T` ensures better error reporting. 85 | impl< 86 | 'de, 87 | T: BiHashItem + fmt::Debug, 88 | S: Clone + BuildHasher + Default, 89 | A: Default + Allocator + Clone, 90 | > Deserialize<'de> for BiHashMap 91 | where 92 | T: Deserialize<'de>, 93 | { 94 | fn deserialize>( 95 | deserializer: D, 96 | ) -> Result { 97 | deserializer.deserialize_seq(SeqVisitor { 98 | _marker: PhantomData, 99 | hasher: S::default(), 100 | alloc: A::default(), 101 | }) 102 | } 103 | } 104 | 105 | impl< 106 | 'de, 107 | T: BiHashItem + fmt::Debug + Deserialize<'de>, 108 | S: Clone + BuildHasher, 109 | A: Clone + Allocator, 110 | > BiHashMap 111 | { 112 | /// Deserializes from a list of items, allocating new storage within the 113 | /// provided allocator. 114 | pub fn deserialize_in>( 115 | deserializer: D, 116 | alloc: A, 117 | ) -> Result 118 | where 119 | S: Default, 120 | { 121 | deserializer.deserialize_seq(SeqVisitor { 122 | _marker: PhantomData, 123 | hasher: S::default(), 124 | alloc, 125 | }) 126 | } 127 | 128 | /// Deserializes from a list of items, with the given hasher, using the 129 | /// default allocator. 130 | pub fn deserialize_with_hasher>( 131 | deserializer: D, 132 | hasher: S, 133 | ) -> Result 134 | where 135 | A: Default, 136 | { 137 | deserializer.deserialize_seq(SeqVisitor { 138 | _marker: PhantomData, 139 | hasher, 140 | alloc: A::default(), 141 | }) 142 | } 143 | 144 | /// Deserializes from a list of items, with the given hasher, and allocating 145 | /// new storage within the provided allocator. 146 | pub fn deserialize_with_hasher_in>( 147 | deserializer: D, 148 | hasher: S, 149 | alloc: A, 150 | ) -> Result { 151 | deserializer.deserialize_seq(SeqVisitor { 152 | _marker: PhantomData, 153 | hasher, 154 | alloc, 155 | }) 156 | } 157 | } 158 | 159 | struct SeqVisitor { 160 | _marker: PhantomData T>, 161 | hasher: S, 162 | alloc: A, 163 | } 164 | 165 | impl<'de, T, S, A> Visitor<'de> for SeqVisitor 166 | where 167 | T: BiHashItem + Deserialize<'de> + fmt::Debug, 168 | S: Clone + BuildHasher, 169 | A: Clone + Allocator, 170 | { 171 | type Value = BiHashMap; 172 | 173 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 174 | formatter.write_str("a sequence of items representing a BiHashMap") 175 | } 176 | 177 | fn visit_seq( 178 | self, 179 | mut seq: Access, 180 | ) -> Result 181 | where 182 | Access: SeqAccess<'de>, 183 | { 184 | let mut map = match seq.size_hint() { 185 | Some(size) => BiHashMap::with_capacity_and_hasher_in( 186 | size, 187 | self.hasher, 188 | self.alloc, 189 | ), 190 | None => BiHashMap::with_hasher_in(self.hasher, self.alloc), 191 | }; 192 | 193 | while let Some(element) = seq.next_element()? { 194 | map.insert_unique(element).map_err(serde::de::Error::custom)?; 195 | } 196 | 197 | Ok(map) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /crates/iddqd/src/bi_hash_map/tables.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | BiHashItem, 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 BiHashMapTables { 10 | pub(super) k1_to_item: MapHashTable, 11 | pub(super) k2_to_item: MapHashTable, 12 | } 13 | 14 | impl BiHashMapTables { 15 | pub(super) fn with_capacity_and_hasher_in( 16 | capacity: usize, 17 | hasher: S, 18 | alloc: A, 19 | ) -> Self { 20 | Self { 21 | k1_to_item: MapHashTable::with_capacity_and_hasher_in( 22 | capacity, 23 | hasher.clone(), 24 | alloc.clone(), 25 | ), 26 | k2_to_item: MapHashTable::with_capacity_and_hasher_in( 27 | capacity, 28 | hasher.clone(), 29 | alloc, 30 | ), 31 | } 32 | } 33 | } 34 | 35 | impl BiHashMapTables { 36 | #[cfg(feature = "daft")] 37 | pub(super) fn hasher(&self) -> &S { 38 | self.k1_to_item.state() 39 | } 40 | 41 | pub(super) fn validate( 42 | &self, 43 | expected_len: usize, 44 | compactness: ValidateCompact, 45 | ) -> Result<(), ValidationError> { 46 | // Check that all the maps are of the right size. 47 | self.k1_to_item.validate(expected_len, compactness).map_err( 48 | |error| ValidationError::Table { name: "k1_to_table", error }, 49 | )?; 50 | self.k2_to_item.validate(expected_len, compactness).map_err( 51 | |error| ValidationError::Table { name: "k2_to_table", error }, 52 | )?; 53 | 54 | Ok(()) 55 | } 56 | 57 | pub(super) fn make_hashes( 58 | &self, 59 | k1: &T::K1<'_>, 60 | k2: &T::K2<'_>, 61 | ) -> [MapHash; 2] { 62 | let h1 = self.k1_to_item.compute_hash(k1); 63 | let h2 = self.k2_to_item.compute_hash(k2); 64 | 65 | [h1, h2] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_hash_map/daft_impls.rs: -------------------------------------------------------------------------------- 1 | //! `Diffable` implementation. 2 | 3 | use super::{IdHashItem, IdHashMap}; 4 | use crate::{ 5 | DefaultHashBuilder, 6 | support::{ 7 | alloc::{Allocator, Global}, 8 | daft_utils::IdLeaf, 9 | }, 10 | }; 11 | use core::hash::{BuildHasher, Hash}; 12 | use daft::Diffable; 13 | use equivalent::Equivalent; 14 | 15 | impl Diffable 16 | for IdHashMap 17 | { 18 | type Diff<'a> 19 | = Diff<'a, T, S, A> 20 | where 21 | T: 'a, 22 | S: 'a, 23 | A: 'a; 24 | 25 | fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> { 26 | let mut diff = Diff::with_hasher_in( 27 | self.hasher().clone(), 28 | self.allocator().clone(), 29 | ); 30 | for item in self { 31 | if let Some(other_item) = other.get(&item.key()) { 32 | diff.common.insert_overwrite(IdLeaf::new(item, other_item)); 33 | } else { 34 | diff.removed.insert_overwrite(item); 35 | } 36 | } 37 | for item in other { 38 | if !self.contains_key(&item.key()) { 39 | diff.added.insert_overwrite(item); 40 | } 41 | } 42 | diff 43 | } 44 | } 45 | 46 | /// A diff of two [`IdHashMap`]s. 47 | /// 48 | /// Generated by the [`Diffable`] implementation for [`IdHashMap`]. 49 | /// 50 | /// # Examples 51 | /// 52 | /// ``` 53 | /// # #[cfg(feature = "default-hasher")] { 54 | /// use daft::Diffable; 55 | /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; 56 | /// 57 | /// #[derive(Eq, PartialEq)] 58 | /// struct Item { 59 | /// id: String, 60 | /// value: u32, 61 | /// } 62 | /// 63 | /// impl IdHashItem for Item { 64 | /// type Key<'a> = &'a str; 65 | /// fn key(&self) -> Self::Key<'_> { 66 | /// &self.id 67 | /// } 68 | /// id_upcast!(); 69 | /// } 70 | /// 71 | /// // Create two IdHashMaps with overlapping items. 72 | /// let mut map1 = IdHashMap::new(); 73 | /// map1.insert_unique(Item { id: "a".to_string(), value: 1 }); 74 | /// map1.insert_unique(Item { id: "b".to_string(), value: 2 }); 75 | /// 76 | /// let mut map2 = IdHashMap::new(); 77 | /// map2.insert_unique(Item { id: "b".to_string(), value: 3 }); 78 | /// map2.insert_unique(Item { id: "c".to_string(), value: 4 }); 79 | /// 80 | /// // Compute the diff between the two maps. 81 | /// let diff = map1.diff(&map2); 82 | /// 83 | /// // "a" is removed. 84 | /// assert!(diff.removed.contains_key("a")); 85 | /// // "b" is modified (value changed from 2 to 3). 86 | /// assert!(diff.is_modified("b")); 87 | /// // "c" is added. 88 | /// assert!(diff.added.contains_key("c")); 89 | /// # } 90 | /// ``` 91 | /// 92 | /// [`Diffable`]: daft::Diffable 93 | pub struct Diff< 94 | 'daft, 95 | T: ?Sized + IdHashItem, 96 | S = DefaultHashBuilder, 97 | A: Allocator = Global, 98 | > { 99 | /// Entries common to both maps. 100 | /// 101 | /// Items are stored as [`IdLeaf`]s to references. 102 | pub common: IdHashMap, S, A>, 103 | 104 | /// Added entries. 105 | pub added: IdHashMap<&'daft T, S, A>, 106 | 107 | /// Removed entries. 108 | pub removed: IdHashMap<&'daft T, S, A>, 109 | } 110 | 111 | impl<'daft, T: ?Sized + IdHashItem, S: Default, A: Allocator + Default> Default 112 | for Diff<'daft, T, S, A> 113 | { 114 | fn default() -> Self { 115 | Self { 116 | common: IdHashMap::default(), 117 | added: IdHashMap::default(), 118 | removed: IdHashMap::default(), 119 | } 120 | } 121 | } 122 | 123 | #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] 124 | impl<'daft, T: ?Sized + IdHashItem> Diff<'daft, T> { 125 | /// Creates a new, empty `IdHashMapDiff` 126 | pub fn new() -> Self { 127 | Self { 128 | common: IdHashMap::new(), 129 | added: IdHashMap::new(), 130 | removed: IdHashMap::new(), 131 | } 132 | } 133 | } 134 | 135 | #[cfg(feature = "allocator-api2")] 136 | impl<'daft, T: ?Sized + IdHashItem, S: Clone + BuildHasher> Diff<'daft, T, S> { 137 | /// Creates a new `IdHashMapDiff` with the given hasher. 138 | pub fn with_hasher(hasher: S) -> Self { 139 | Self { 140 | common: IdHashMap::with_hasher(hasher.clone()), 141 | added: IdHashMap::with_hasher(hasher.clone()), 142 | removed: IdHashMap::with_hasher(hasher), 143 | } 144 | } 145 | } 146 | 147 | impl< 148 | 'daft, 149 | T: ?Sized + IdHashItem, 150 | S: Clone + BuildHasher, 151 | A: Clone + Allocator, 152 | > Diff<'daft, T, S, A> 153 | { 154 | /// Creates a new `IdHashMapDiff` with the given hasher and allocator. 155 | pub fn with_hasher_in(hasher: S, alloc: A) -> Self { 156 | Self { 157 | common: IdHashMap::with_hasher_in(hasher.clone(), alloc.clone()), 158 | added: IdHashMap::with_hasher_in(hasher.clone(), alloc.clone()), 159 | removed: IdHashMap::with_hasher_in(hasher, alloc), 160 | } 161 | } 162 | } 163 | 164 | impl<'daft, T: ?Sized + IdHashItem + Eq, S: Clone + BuildHasher, A: Allocator> 165 | Diff<'daft, T, S, A> 166 | { 167 | /// Returns an iterator over unchanged keys and values. 168 | pub fn unchanged(&self) -> impl Iterator + '_ { 169 | self.common 170 | .iter() 171 | .filter_map(|leaf| leaf.is_unchanged().then_some(*leaf.before())) 172 | } 173 | 174 | /// Returns true if the item corresponding to the key is unchanged. 175 | pub fn is_unchanged<'a, Q>(&'a self, key: &Q) -> bool 176 | where 177 | Q: ?Sized + Hash + Equivalent>, 178 | { 179 | self.common.get(key).is_some_and(|leaf| leaf.is_unchanged()) 180 | } 181 | 182 | /// Returns the value associated with the key if it is unchanged, 183 | /// otherwise `None`. 184 | pub fn get_unchanged<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> 185 | where 186 | Q: ?Sized + Hash + Equivalent>, 187 | { 188 | self.common 189 | .get(key) 190 | .and_then(|leaf| leaf.is_unchanged().then_some(*leaf.before())) 191 | } 192 | 193 | /// Returns an iterator over modified keys and values. 194 | pub fn modified(&self) -> impl Iterator> + '_ { 195 | self.common 196 | .iter() 197 | .filter_map(|leaf| leaf.is_modified().then_some(*leaf)) 198 | } 199 | 200 | /// Returns true if the value corresponding to the key is 201 | /// modified. 202 | pub fn is_modified<'a, Q>(&'a self, key: &Q) -> bool 203 | where 204 | Q: ?Sized + Hash + Equivalent>, 205 | { 206 | self.common.get(key).is_some_and(|leaf| leaf.is_modified()) 207 | } 208 | 209 | /// Returns the [`IdLeaf`] associated with the key if it is modified, 210 | /// otherwise `None`. 211 | pub fn get_modified<'a, Q>(&'a self, key: &Q) -> Option> 212 | where 213 | Q: ?Sized + Hash + Equivalent>, 214 | { 215 | self.common 216 | .get(key) 217 | .and_then(|leaf| leaf.is_modified().then_some(*leaf)) 218 | } 219 | 220 | /// Returns an iterator over modified keys and values, performing 221 | /// a diff on the values. 222 | /// 223 | /// This is useful when `T::Diff` is a complex type, not just a 224 | /// [`daft::Leaf`]. 225 | pub fn modified_diff(&self) -> impl Iterator> + '_ 226 | where 227 | T: Diffable, 228 | { 229 | self.modified().map(|leaf| leaf.diff_pair()) 230 | } 231 | } 232 | 233 | impl IdHashItem for IdLeaf { 234 | type Key<'a> 235 | = T::Key<'a> 236 | where 237 | T: 'a; 238 | 239 | fn key(&self) -> Self::Key<'_> { 240 | let before_key = self.before().key(); 241 | if before_key != self.after().key() { 242 | panic!("key is different between before and after"); 243 | } 244 | self.before().key() 245 | } 246 | 247 | #[inline] 248 | fn upcast_key<'short, 'long: 'short>( 249 | long: Self::Key<'long>, 250 | ) -> Self::Key<'short> { 251 | T::upcast_key(long) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_hash_map/entry.rs: -------------------------------------------------------------------------------- 1 | use super::{IdHashItem, IdHashMap, RefMut}; 2 | use crate::{ 3 | DefaultHashBuilder, 4 | support::{ 5 | alloc::{Allocator, Global}, 6 | borrow::DormantMutRef, 7 | map_hash::MapHash, 8 | }, 9 | }; 10 | use core::{fmt, hash::BuildHasher}; 11 | 12 | /// An implementation of the Entry API for [`IdHashMap`]. 13 | pub enum Entry<'a, T: IdHashItem, S = DefaultHashBuilder, A: Allocator = Global> 14 | { 15 | /// A vacant entry. 16 | Vacant(VacantEntry<'a, T, S, A>), 17 | /// An occupied entry. 18 | Occupied(OccupiedEntry<'a, T, S, A>), 19 | } 20 | 21 | impl<'a, T: IdHashItem, S, A: Allocator> fmt::Debug for Entry<'a, T, S, A> { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | match self { 24 | Entry::Vacant(entry) => { 25 | f.debug_tuple("Vacant").field(entry).finish() 26 | } 27 | Entry::Occupied(entry) => { 28 | f.debug_tuple("Occupied").field(entry).finish() 29 | } 30 | } 31 | } 32 | } 33 | 34 | impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> 35 | Entry<'a, T, S, A> 36 | { 37 | /// Ensures a value is in the entry by inserting the default if empty, and 38 | /// returns a mutable reference to the value in the entry. 39 | /// 40 | /// # Panics 41 | /// 42 | /// Panics if the key hashes to a different value than the one passed 43 | /// into [`IdHashMap::entry`]. 44 | #[inline] 45 | pub fn or_insert(self, default: T) -> RefMut<'a, T, S> { 46 | match self { 47 | Entry::Occupied(entry) => entry.into_mut(), 48 | Entry::Vacant(entry) => entry.insert(default), 49 | } 50 | } 51 | 52 | /// Ensures a value is in the entry by inserting the result of the default 53 | /// function if empty, and returns a mutable reference to the value in the 54 | /// entry. 55 | /// 56 | /// # Panics 57 | /// 58 | /// Panics if the key hashes to a different value than the one passed 59 | /// into [`IdHashMap::entry`]. 60 | #[inline] 61 | pub fn or_insert_with T>( 62 | self, 63 | default: F, 64 | ) -> RefMut<'a, T, S> { 65 | match self { 66 | Entry::Occupied(entry) => entry.into_mut(), 67 | Entry::Vacant(entry) => entry.insert(default()), 68 | } 69 | } 70 | 71 | /// Provides in-place mutable access to an occupied entry before any 72 | /// potential inserts into the map. 73 | #[inline] 74 | pub fn and_modify(self, f: F) -> Self 75 | where 76 | F: FnOnce(RefMut<'_, T, S>), 77 | { 78 | match self { 79 | Entry::Occupied(mut entry) => { 80 | f(entry.get_mut()); 81 | Entry::Occupied(entry) 82 | } 83 | Entry::Vacant(entry) => Entry::Vacant(entry), 84 | } 85 | } 86 | } 87 | 88 | /// A vacant entry. 89 | pub struct VacantEntry< 90 | 'a, 91 | T: IdHashItem, 92 | S = DefaultHashBuilder, 93 | A: Allocator = Global, 94 | > { 95 | map: DormantMutRef<'a, IdHashMap>, 96 | hash: MapHash, 97 | } 98 | 99 | impl<'a, T: IdHashItem, S, A: Allocator> fmt::Debug 100 | for VacantEntry<'a, T, S, A> 101 | { 102 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 103 | f.debug_struct("VacantEntry") 104 | .field("hash", &self.hash) 105 | .finish_non_exhaustive() 106 | } 107 | } 108 | 109 | impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> 110 | VacantEntry<'a, T, S, A> 111 | { 112 | pub(super) unsafe fn new( 113 | map: DormantMutRef<'a, IdHashMap>, 114 | hash: MapHash, 115 | ) -> Self { 116 | VacantEntry { map, hash } 117 | } 118 | 119 | /// Sets the entry to a new value, returning a mutable reference to the 120 | /// value. 121 | pub fn insert(self, value: T) -> RefMut<'a, T, S> { 122 | if !self.hash.is_same_hash(value.key()) { 123 | panic!("key hashes do not match"); 124 | } 125 | 126 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 127 | // original reference to the map is not used at this point. 128 | let map = unsafe { self.map.awaken() }; 129 | let Ok(index) = map.insert_unique_impl(value) else { 130 | panic!("key already present in map"); 131 | }; 132 | map.get_by_index_mut(index).expect("index is known to be valid") 133 | } 134 | 135 | /// Sets the value of the entry, and returns an `OccupiedEntry`. 136 | #[inline] 137 | pub fn insert_entry(mut self, value: T) -> OccupiedEntry<'a, T, S, A> { 138 | if !self.hash.is_same_hash(value.key()) { 139 | panic!("key hashes do not match"); 140 | } 141 | 142 | let index = { 143 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 144 | // original reference to the map is not used at this point. 145 | let map = unsafe { self.map.reborrow() }; 146 | let Ok(index) = map.insert_unique_impl(value) else { 147 | panic!("key already present in map"); 148 | }; 149 | index 150 | }; 151 | 152 | // SAFETY: map, as well as anything that was borrowed from it, is 153 | // dropped once the above block exits. 154 | unsafe { OccupiedEntry::new(self.map, index) } 155 | } 156 | } 157 | 158 | /// A view into an occupied entry in an [`IdHashMap`]. Part of the [`Entry`] 159 | /// enum. 160 | pub struct OccupiedEntry< 161 | 'a, 162 | T: IdHashItem, 163 | S = DefaultHashBuilder, 164 | A: Allocator = Global, 165 | > { 166 | map: DormantMutRef<'a, IdHashMap>, 167 | // index is a valid index into the map's internal hash table. 168 | index: usize, 169 | } 170 | 171 | impl<'a, T: IdHashItem, S, A: Allocator> fmt::Debug 172 | for OccupiedEntry<'a, T, S, A> 173 | { 174 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 175 | f.debug_struct("OccupiedEntry") 176 | .field("index", &self.index) 177 | .finish_non_exhaustive() 178 | } 179 | } 180 | 181 | impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> 182 | OccupiedEntry<'a, T, S, A> 183 | { 184 | /// # Safety 185 | /// 186 | /// After self is created, the original reference created by 187 | /// `DormantMutRef::new` must not be used. 188 | pub(super) unsafe fn new( 189 | map: DormantMutRef<'a, IdHashMap>, 190 | index: usize, 191 | ) -> Self { 192 | OccupiedEntry { map, index } 193 | } 194 | 195 | /// Gets a reference to the value. 196 | /// 197 | /// If you need a reference to `T` that may outlive the destruction of the 198 | /// `Entry` value, see [`into_ref`](Self::into_ref). 199 | pub fn get(&self) -> &T { 200 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 201 | // original reference to the map is not used at this point. 202 | unsafe { self.map.reborrow_shared() } 203 | .get_by_index(self.index) 204 | .expect("index is known to be valid") 205 | } 206 | 207 | /// Gets a mutable reference to the value. 208 | /// 209 | /// If you need a reference to `T` that may outlive the destruction of the 210 | /// `Entry` value, see [`into_mut`](Self::into_mut). 211 | pub fn get_mut(&mut self) -> RefMut<'_, T, S> { 212 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 213 | // original reference to the map is not used at this point. 214 | unsafe { self.map.reborrow() } 215 | .get_by_index_mut(self.index) 216 | .expect("index is known to be valid") 217 | } 218 | 219 | /// Converts self into a reference to the value. 220 | /// 221 | /// If you need multiple references to the `OccupiedEntry`, see 222 | /// [`get`](Self::get). 223 | pub fn into_ref(self) -> &'a T { 224 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 225 | // original reference to the map is not used at this point. 226 | unsafe { self.map.awaken() } 227 | .get_by_index(self.index) 228 | .expect("index is known to be valid") 229 | } 230 | 231 | /// Converts self into a mutable reference to the value. 232 | /// 233 | /// If you need multiple references to the `OccupiedEntry`, see 234 | /// [`get_mut`](Self::get_mut). 235 | pub fn into_mut(self) -> RefMut<'a, T, S> { 236 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 237 | // original reference to the map is not used at this point. 238 | unsafe { self.map.awaken() } 239 | .get_by_index_mut(self.index) 240 | .expect("index is known to be valid") 241 | } 242 | 243 | /// Sets the entry to a new value, returning the old value. 244 | /// 245 | /// # Panics 246 | /// 247 | /// Panics if `value.key()` is different from the key of the entry. 248 | pub fn insert(&mut self, value: T) -> T { 249 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 250 | // original reference to the map is not used at this point. 251 | // 252 | // Note that `replace_at_index` panics if the keys don't match. 253 | unsafe { self.map.reborrow() }.replace_at_index(self.index, value) 254 | } 255 | 256 | /// Takes ownership of the value from the map. 257 | pub fn remove(mut self) -> T { 258 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 259 | // original reference to the map is not used at this point. 260 | unsafe { self.map.reborrow() } 261 | .remove_by_index(self.index) 262 | .expect("index is known to be valid") 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /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: Clone + BuildHasher, A: Allocator> 74 | IterMut<'a, T, S, A> 75 | { 76 | pub(super) fn new( 77 | tables: &'a IdHashMapTables, 78 | items: &'a mut ItemSet, 79 | ) -> Self { 80 | Self { tables, inner: items.values_mut() } 81 | } 82 | } 83 | 84 | impl<'a, T: IdHashItem, 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_hash(next); 93 | Some(RefMut::new(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 [`IdHashMap`] by ownership. Created by 113 | /// [`IdHashMap::into_iter`]. 114 | /// 115 | /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed 116 | /// to be stable. 117 | /// 118 | /// [`IdHashMap`]: crate::IdHashMap 119 | /// [`IdHashMap::into_iter`]: crate::IdHashMap::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/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 | mod ref_mut; 11 | #[cfg(feature = "serde")] 12 | mod serde_impls; 13 | mod tables; 14 | pub(crate) mod trait_defs; 15 | 16 | #[cfg(feature = "daft")] 17 | pub use daft_impls::Diff; 18 | pub use entry::{Entry, OccupiedEntry, VacantEntry}; 19 | pub use imp::IdHashMap; 20 | pub use iter::{IntoIter, Iter, IterMut}; 21 | pub use ref_mut::RefMut; 22 | pub use trait_defs::IdHashItem; 23 | -------------------------------------------------------------------------------- /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(hash: MapHash, borrowed: &'a mut T) -> Self { 56 | Self { inner: Some(RefMutInner { 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.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 | hash: MapHash, 112 | borrowed: &'a mut T, 113 | } 114 | 115 | impl<'a, T: IdHashItem, S: BuildHasher> RefMutInner<'a, T, S> { 116 | fn into_ref(self) -> &'a T { 117 | if !self.hash.is_same_hash(self.borrowed.key()) { 118 | panic!("key changed during RefMut borrow"); 119 | } 120 | 121 | self.borrowed 122 | } 123 | } 124 | 125 | impl fmt::Debug for RefMutInner<'_, T, S> { 126 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 127 | self.borrowed.fmt(f) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_hash_map/serde_impls.rs: -------------------------------------------------------------------------------- 1 | use crate::{IdHashItem, IdHashMap, support::alloc::Allocator}; 2 | use core::{fmt, hash::BuildHasher, marker::PhantomData}; 3 | use serde::{ 4 | Deserialize, Serialize, Serializer, 5 | de::{SeqAccess, Visitor}, 6 | }; 7 | 8 | /// An `IdHashMap` serializes to the list of items. Items are serialized in 9 | /// arbitrary order. 10 | /// 11 | /// Serializing as a list of items rather than as a map works around the lack of 12 | /// non-string keys in formats like JSON. 13 | /// 14 | /// # Examples 15 | /// 16 | /// ``` 17 | /// # #[cfg(feature = "default-hasher")] { 18 | /// use iddqd::{IdHashItem, IdHashMap, 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, Hash, PartialEq)] 31 | /// struct ComplexKey<'a> { 32 | /// id: u32, 33 | /// email: &'a str, 34 | /// } 35 | /// 36 | /// impl IdHashItem 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 = IdHashMap::::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. 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 | /// ``` 60 | impl Serialize 61 | for IdHashMap 62 | where 63 | T: Serialize, 64 | { 65 | fn serialize( 66 | &self, 67 | serializer: Ser, 68 | ) -> Result { 69 | // Serialize just the items -- don't serialize the indexes. We'll 70 | // rebuild the indexes on deserialization. 71 | self.items.serialize(serializer) 72 | } 73 | } 74 | 75 | /// The `Deserialize` impl for `IdHashMap` deserializes the list of items and 76 | /// then rebuilds the indexes, producing an error if there are any duplicates. 77 | /// 78 | /// The `fmt::Debug` bound on `T` ensures better error reporting. 79 | impl< 80 | 'de, 81 | T: IdHashItem + fmt::Debug, 82 | S: Clone + BuildHasher + Default, 83 | A: Default + Clone + Allocator, 84 | > Deserialize<'de> for IdHashMap 85 | where 86 | T: Deserialize<'de>, 87 | { 88 | fn deserialize>( 89 | deserializer: D, 90 | ) -> Result { 91 | deserializer.deserialize_seq(SeqVisitor { 92 | _marker: PhantomData, 93 | hasher: S::default(), 94 | alloc: A::default(), 95 | }) 96 | } 97 | } 98 | 99 | impl< 100 | 'de, 101 | T: IdHashItem + fmt::Debug + Deserialize<'de>, 102 | S: Clone + BuildHasher, 103 | A: Clone + Allocator, 104 | > IdHashMap 105 | { 106 | /// Deserializes from a list of items, allocating new storage within the 107 | /// provided allocator. 108 | pub fn deserialize_in>( 109 | deserializer: D, 110 | alloc: A, 111 | ) -> Result 112 | where 113 | S: Default, 114 | { 115 | deserializer.deserialize_seq(SeqVisitor { 116 | _marker: PhantomData, 117 | hasher: S::default(), 118 | alloc, 119 | }) 120 | } 121 | 122 | /// Deserializes from a list of items, with the given hasher, using the 123 | /// default allocator. 124 | pub fn deserialize_with_hasher>( 125 | deserializer: D, 126 | hasher: S, 127 | ) -> Result 128 | where 129 | A: Default, 130 | { 131 | deserializer.deserialize_seq(SeqVisitor { 132 | _marker: PhantomData, 133 | hasher, 134 | alloc: A::default(), 135 | }) 136 | } 137 | 138 | /// Deserializes from a list of items, with the given hasher, and allocating 139 | /// new storage within the provided allocator. 140 | pub fn deserialize_with_hasher_in>( 141 | deserializer: D, 142 | hasher: S, 143 | alloc: A, 144 | ) -> Result { 145 | // First, deserialize the items. 146 | deserializer.deserialize_seq(SeqVisitor { 147 | _marker: PhantomData, 148 | hasher, 149 | alloc, 150 | }) 151 | } 152 | } 153 | 154 | struct SeqVisitor { 155 | _marker: PhantomData T>, 156 | hasher: S, 157 | alloc: A, 158 | } 159 | 160 | impl<'de, T, S, A> Visitor<'de> for SeqVisitor 161 | where 162 | T: IdHashItem + Deserialize<'de> + fmt::Debug, 163 | S: Clone + BuildHasher, 164 | A: Clone + Allocator, 165 | { 166 | type Value = IdHashMap; 167 | 168 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 169 | formatter.write_str("a sequence of items representing an IdHashMap") 170 | } 171 | 172 | fn visit_seq( 173 | self, 174 | mut seq: Access, 175 | ) -> Result 176 | where 177 | Access: SeqAccess<'de>, 178 | { 179 | let mut map = match seq.size_hint() { 180 | Some(size) => IdHashMap::with_capacity_and_hasher_in( 181 | size, 182 | self.hasher, 183 | self.alloc, 184 | ), 185 | None => IdHashMap::with_hasher_in(self.hasher, self.alloc), 186 | }; 187 | 188 | while let Some(element) = seq.next_element()? { 189 | map.insert_unique(element).map_err(serde::de::Error::custom)?; 190 | } 191 | 192 | Ok(map) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /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) key_to_item: MapHashTable, 11 | } 12 | 13 | impl IdHashMapTables { 14 | #[cfg(feature = "daft")] 15 | pub(crate) fn hasher(&self) -> &S { 16 | // TODO: store hasher here 17 | self.key_to_item.state() 18 | } 19 | 20 | pub(super) fn with_capacity_and_hasher_in( 21 | capacity: usize, 22 | hasher: S, 23 | alloc: A, 24 | ) -> Self { 25 | Self { 26 | key_to_item: MapHashTable::with_capacity_and_hasher_in( 27 | capacity, hasher, alloc, 28 | ), 29 | } 30 | } 31 | 32 | pub(super) fn validate( 33 | &self, 34 | expected_len: usize, 35 | compactness: ValidateCompact, 36 | ) -> Result<(), ValidationError> { 37 | self.key_to_item.validate(expected_len, compactness).map_err( 38 | |error| ValidationError::Table { name: "key_to_table", error }, 39 | )?; 40 | 41 | Ok(()) 42 | } 43 | 44 | pub(super) fn make_hash(&self, item: &T) -> MapHash { 45 | let k1 = item.key(); 46 | self.key_to_item.compute_hash(k1) 47 | } 48 | 49 | pub(super) fn make_key_hash( 50 | &self, 51 | key: &T::Key<'_>, 52 | ) -> MapHash { 53 | self.key_to_item.compute_hash(key) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /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/daft_impls.rs: -------------------------------------------------------------------------------- 1 | //! `Diffable` implementation. 2 | 3 | use super::{IdOrdItem, IdOrdMap}; 4 | use crate::support::daft_utils::IdLeaf; 5 | use daft::Diffable; 6 | use equivalent::Comparable; 7 | 8 | impl Diffable for IdOrdMap { 9 | type Diff<'a> 10 | = Diff<'a, T> 11 | where 12 | T: 'a; 13 | 14 | fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> { 15 | let mut diff = Diff::new(); 16 | for item in self { 17 | if let Some(other_item) = other.get(&item.key()) { 18 | diff.common.insert_overwrite(IdLeaf::new(item, other_item)); 19 | } else { 20 | diff.removed.insert_overwrite(item); 21 | } 22 | } 23 | for item in other { 24 | if !self.contains_key(&item.key()) { 25 | diff.added.insert_overwrite(item); 26 | } 27 | } 28 | diff 29 | } 30 | } 31 | 32 | /// A diff of two [`IdOrdMap`]s. 33 | /// 34 | /// Generated by the [`Diffable`] implementation for [`IdOrdMap`]. 35 | /// 36 | /// # Examples 37 | /// 38 | /// ``` 39 | /// use daft::Diffable; 40 | /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; 41 | /// 42 | /// #[derive(Eq, PartialEq, PartialOrd, Ord)] 43 | /// struct Item { 44 | /// id: String, 45 | /// value: u32, 46 | /// } 47 | /// 48 | /// impl IdOrdItem for Item { 49 | /// type Key<'a> = &'a str; 50 | /// fn key(&self) -> Self::Key<'_> { 51 | /// &self.id 52 | /// } 53 | /// id_upcast!(); 54 | /// } 55 | /// 56 | /// // Create two IdOrdMaps with overlapping items. 57 | /// let mut map1 = IdOrdMap::new(); 58 | /// map1.insert_unique(Item { id: "a".to_string(), value: 1 }); 59 | /// map1.insert_unique(Item { id: "b".to_string(), value: 2 }); 60 | /// 61 | /// let mut map2 = IdOrdMap::new(); 62 | /// map2.insert_unique(Item { id: "b".to_string(), value: 3 }); 63 | /// map2.insert_unique(Item { id: "c".to_string(), value: 4 }); 64 | /// 65 | /// // Compute the diff between the two maps. 66 | /// let diff = map1.diff(&map2); 67 | /// 68 | /// // "a" is removed. 69 | /// assert!(diff.removed.contains_key("a")); 70 | /// // "b" is modified (value changed from 2 to 3). 71 | /// assert!(diff.is_modified("b")); 72 | /// // "c" is added. 73 | /// assert!(diff.added.contains_key("c")); 74 | /// ``` 75 | /// 76 | /// [`Diffable`]: daft::Diffable 77 | pub struct Diff<'daft, T: ?Sized + IdOrdItem> { 78 | /// Entries common to both maps. 79 | /// 80 | /// Items are stored as [`IdLeaf`]s to references. 81 | pub common: IdOrdMap>, 82 | 83 | /// Added entries. 84 | pub added: IdOrdMap<&'daft T>, 85 | 86 | /// Removed entries. 87 | pub removed: IdOrdMap<&'daft T>, 88 | } 89 | 90 | impl<'daft, T: ?Sized + IdOrdItem> Diff<'daft, T> { 91 | /// Creates a new `IdOrdMapDiff` from two maps. 92 | pub fn new() -> Self { 93 | Self { 94 | common: IdOrdMap::new(), 95 | added: IdOrdMap::new(), 96 | removed: IdOrdMap::new(), 97 | } 98 | } 99 | } 100 | 101 | impl<'daft, T: ?Sized + IdOrdItem + Eq> Diff<'daft, T> { 102 | /// Returns an iterator over unchanged keys and values. 103 | pub fn unchanged(&self) -> impl Iterator + '_ { 104 | self.common 105 | .iter() 106 | .filter_map(|leaf| leaf.is_unchanged().then_some(*leaf.before())) 107 | } 108 | 109 | /// Returns true if the item corresponding to the key is unchanged. 110 | pub fn is_unchanged<'a, Q>(&'a self, key: &Q) -> bool 111 | where 112 | Q: ?Sized + Comparable>, 113 | { 114 | self.common.get(key).is_some_and(|leaf| leaf.is_unchanged()) 115 | } 116 | 117 | /// Returns the value associated with the key if it is unchanged, 118 | /// otherwise `None`. 119 | pub fn get_unchanged<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> 120 | where 121 | Q: ?Sized + Comparable>, 122 | { 123 | self.common 124 | .get(key) 125 | .and_then(|leaf| leaf.is_unchanged().then_some(*leaf.before())) 126 | } 127 | 128 | /// Returns an iterator over modified keys and values. 129 | pub fn modified(&self) -> impl Iterator> + '_ { 130 | self.common 131 | .iter() 132 | .filter_map(|leaf| leaf.is_modified().then_some(*leaf)) 133 | } 134 | 135 | /// Returns true if the value corresponding to the key is 136 | /// modified. 137 | pub fn is_modified<'a, Q>(&'a self, key: &Q) -> bool 138 | where 139 | Q: ?Sized + Comparable>, 140 | { 141 | self.common.get(key).is_some_and(|leaf| leaf.is_modified()) 142 | } 143 | 144 | /// Returns the [`IdLeaf`] associated with the key if it is modified, 145 | /// otherwise `None`. 146 | pub fn get_modified<'a, Q>(&'a self, key: &Q) -> Option> 147 | where 148 | Q: ?Sized + Comparable>, 149 | { 150 | self.common 151 | .get(key) 152 | .and_then(|leaf| leaf.is_modified().then_some(*leaf)) 153 | } 154 | 155 | /// Returns an iterator over modified keys and values, performing 156 | /// a diff on the values. 157 | /// 158 | /// This is useful when `T::Diff` is a complex type, not just a 159 | /// [`daft::Leaf`]. 160 | pub fn modified_diff(&self) -> impl Iterator> + '_ 161 | where 162 | T: Diffable, 163 | { 164 | self.modified().map(|leaf| leaf.diff_pair()) 165 | } 166 | } 167 | 168 | // Note: not deriving Default here because we don't want to require 169 | // T to be Default. 170 | impl<'daft, T: IdOrdItem> Default for Diff<'daft, T> { 171 | fn default() -> Self { 172 | Self::new() 173 | } 174 | } 175 | 176 | impl IdOrdItem for IdLeaf { 177 | type Key<'a> 178 | = T::Key<'a> 179 | where 180 | T: 'a; 181 | 182 | fn key(&self) -> Self::Key<'_> { 183 | let before_key = self.before().key(); 184 | if before_key != self.after().key() { 185 | panic!("key is different between before and after"); 186 | } 187 | self.before().key() 188 | } 189 | 190 | #[inline] 191 | fn upcast_key<'short, 'long: 'short>( 192 | long: Self::Key<'long>, 193 | ) -> Self::Key<'short> { 194 | T::upcast_key(long) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/entry.rs: -------------------------------------------------------------------------------- 1 | use super::{IdOrdItem, IdOrdMap, RefMut}; 2 | use crate::support::borrow::DormantMutRef; 3 | use core::{fmt, hash::Hash}; 4 | 5 | /// An implementation of the Entry API for [`IdOrdMap`]. 6 | pub enum Entry<'a, T: IdOrdItem> { 7 | /// A vacant entry. 8 | Vacant(VacantEntry<'a, T>), 9 | /// An occupied entry. 10 | Occupied(OccupiedEntry<'a, T>), 11 | } 12 | 13 | impl<'a, T: IdOrdItem> fmt::Debug for Entry<'a, T> { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | match self { 16 | Entry::Vacant(entry) => { 17 | f.debug_tuple("Vacant").field(entry).finish() 18 | } 19 | Entry::Occupied(entry) => { 20 | f.debug_tuple("Occupied").field(entry).finish() 21 | } 22 | } 23 | } 24 | } 25 | 26 | impl<'a, T: IdOrdItem> Entry<'a, T> { 27 | /// Ensures a value is in the entry by inserting the default if empty, and 28 | /// returns a shared reference to the value in the entry. 29 | /// 30 | /// # Panics 31 | /// 32 | /// Panics if the key is already present in the map. (The intention is that 33 | /// the key should be what was passed into [`IdOrdMap::entry`], but that 34 | /// isn't checked in this API due to borrow checker limitations.) 35 | #[inline] 36 | pub fn or_insert_ref(self, default: T) -> &'a T { 37 | match self { 38 | Entry::Occupied(entry) => entry.into_ref(), 39 | Entry::Vacant(entry) => entry.insert_ref(default), 40 | } 41 | } 42 | 43 | /// Ensures a value is in the entry by inserting the default if empty, and 44 | /// returns a mutable reference to the value in the entry. 45 | /// 46 | /// # Panics 47 | /// 48 | /// Panics if the key is already present in the map. (The intention is that 49 | /// the key should be what was passed into [`IdOrdMap::entry`], but that 50 | /// isn't checked in this API due to borrow checker limitations.) 51 | #[inline] 52 | pub fn or_insert(self, default: T) -> RefMut<'a, T> 53 | where 54 | for<'k> T::Key<'k>: Hash, 55 | { 56 | match self { 57 | Entry::Occupied(entry) => entry.into_mut(), 58 | Entry::Vacant(entry) => entry.insert(default), 59 | } 60 | } 61 | 62 | /// Ensures a value is in the entry by inserting the result of the default 63 | /// function if empty, and returns a shared reference to the value in the 64 | /// entry. 65 | /// 66 | /// # Panics 67 | /// 68 | /// Panics if the key is already present in the map. (The intention is that 69 | /// the key should be what was passed into [`IdOrdMap::entry`], but that 70 | /// isn't checked in this API due to borrow checker limitations.) 71 | #[inline] 72 | pub fn or_insert_with_ref T>(self, default: F) -> &'a T { 73 | match self { 74 | Entry::Occupied(entry) => entry.into_ref(), 75 | Entry::Vacant(entry) => entry.insert_ref(default()), 76 | } 77 | } 78 | 79 | /// Ensures a value is in the entry by inserting the result of the default 80 | /// function if empty, and returns a mutable reference to the value in the 81 | /// entry. 82 | /// 83 | /// # Panics 84 | /// 85 | /// Panics if the key is already present in the map. (The intention is that 86 | /// the key should be what was passed into [`IdOrdMap::entry`], but that 87 | /// isn't checked in this API due to borrow checker limitations.) 88 | #[inline] 89 | pub fn or_insert_with T>(self, default: F) -> RefMut<'a, T> 90 | where 91 | for<'k> T::Key<'k>: Hash, 92 | { 93 | match self { 94 | Entry::Occupied(entry) => entry.into_mut(), 95 | Entry::Vacant(entry) => entry.insert(default()), 96 | } 97 | } 98 | 99 | /// Provides in-place mutable access to an occupied entry before any 100 | /// potential inserts into the map. 101 | #[inline] 102 | pub fn and_modify(self, f: F) -> Self 103 | where 104 | F: FnOnce(RefMut<'_, T>), 105 | for<'k> T::Key<'k>: Hash, 106 | { 107 | match self { 108 | Entry::Occupied(mut entry) => { 109 | f(entry.get_mut()); 110 | Entry::Occupied(entry) 111 | } 112 | Entry::Vacant(entry) => Entry::Vacant(entry), 113 | } 114 | } 115 | } 116 | 117 | /// A vacant entry. 118 | pub struct VacantEntry<'a, T: IdOrdItem> { 119 | map: DormantMutRef<'a, IdOrdMap>, 120 | } 121 | 122 | impl<'a, T: IdOrdItem> fmt::Debug for VacantEntry<'a, T> { 123 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 124 | f.debug_struct("VacantEntry").finish_non_exhaustive() 125 | } 126 | } 127 | 128 | impl<'a, T: IdOrdItem> VacantEntry<'a, T> { 129 | pub(super) unsafe fn new(map: DormantMutRef<'a, IdOrdMap>) -> Self { 130 | VacantEntry { map } 131 | } 132 | 133 | /// Sets the entry to a new value, returning a shared reference to the 134 | /// value. 135 | /// 136 | /// # Panics 137 | /// 138 | /// Panics if the key is already present in the map. (The intention is that 139 | /// the key should be what was passed into [`IdOrdMap::entry`], but that 140 | /// isn't checked in this API due to borrow checker limitations.) 141 | pub fn insert_ref(self, value: T) -> &'a T { 142 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 143 | // original reference to the map is not used at this point. 144 | let map = unsafe { self.map.awaken() }; 145 | let Ok(index) = map.insert_unique_impl(value) else { 146 | panic!("key already present in map"); 147 | }; 148 | map.get_by_index(index).expect("index is known to be valid") 149 | } 150 | 151 | /// Sets the entry to a new value, returning a mutable reference to the 152 | /// value. 153 | pub fn insert(self, value: T) -> RefMut<'a, T> 154 | where 155 | for<'k> T::Key<'k>: Hash, 156 | { 157 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 158 | // original reference to the map is not used at this point. 159 | let map = unsafe { self.map.awaken() }; 160 | let Ok(index) = map.insert_unique_impl(value) else { 161 | panic!("key already present in map"); 162 | }; 163 | map.get_by_index_mut(index).expect("index is known to be valid") 164 | } 165 | 166 | /// Sets the value of the entry, and returns an `OccupiedEntry`. 167 | #[inline] 168 | pub fn insert_entry(mut self, value: T) -> OccupiedEntry<'a, T> { 169 | let index = { 170 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 171 | // original reference to the map is not used at this point. 172 | let map = unsafe { self.map.reborrow() }; 173 | let Ok(index) = map.insert_unique_impl(value) else { 174 | panic!("key already present in map"); 175 | }; 176 | index 177 | }; 178 | 179 | // SAFETY: map, as well as anything that was borrowed from it, is 180 | // dropped once the above block exits. 181 | unsafe { OccupiedEntry::new(self.map, index) } 182 | } 183 | } 184 | 185 | /// A view into an occupied entry in an [`IdOrdMap`]. Part of the [`Entry`] 186 | /// enum. 187 | pub struct OccupiedEntry<'a, T: IdOrdItem> { 188 | map: DormantMutRef<'a, IdOrdMap>, 189 | // index is a valid index into the map's internal hash table. 190 | index: usize, 191 | } 192 | 193 | impl<'a, T: IdOrdItem> fmt::Debug for OccupiedEntry<'a, T> { 194 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 195 | f.debug_struct("OccupiedEntry") 196 | .field("index", &self.index) 197 | .finish_non_exhaustive() 198 | } 199 | } 200 | 201 | impl<'a, T: IdOrdItem> OccupiedEntry<'a, T> { 202 | /// # Safety 203 | /// 204 | /// After self is created, the original reference created by 205 | /// `DormantMutRef::new` must not be used. 206 | pub(super) unsafe fn new( 207 | map: DormantMutRef<'a, IdOrdMap>, 208 | index: usize, 209 | ) -> Self { 210 | OccupiedEntry { map, index } 211 | } 212 | 213 | /// Gets a reference to the value. 214 | /// 215 | /// If you need a reference to `T` that may outlive the destruction of the 216 | /// `Entry` value, see [`into_ref`](Self::into_ref). 217 | pub fn get(&self) -> &T { 218 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 219 | // original reference to the map is not used at this point. 220 | unsafe { self.map.reborrow_shared() } 221 | .get_by_index(self.index) 222 | .expect("index is known to be valid") 223 | } 224 | 225 | /// Gets a mutable reference to the value. 226 | /// 227 | /// If you need a reference to `T` that may outlive the destruction of the 228 | /// `Entry` value, see [`into_mut`](Self::into_mut). 229 | pub fn get_mut(&mut self) -> RefMut<'_, T> 230 | where 231 | for<'k> T::Key<'k>: Hash, 232 | { 233 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 234 | // original reference to the map is not used at this point. 235 | unsafe { self.map.reborrow() } 236 | .get_by_index_mut(self.index) 237 | .expect("index is known to be valid") 238 | } 239 | 240 | /// Converts self into a reference to the value. 241 | /// 242 | /// If you need multiple references to the `OccupiedEntry`, see 243 | /// [`get`](Self::get). 244 | pub fn into_ref(self) -> &'a T { 245 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 246 | // original reference to the map is not used at this point. 247 | unsafe { self.map.awaken() } 248 | .get_by_index(self.index) 249 | .expect("index is known to be valid") 250 | } 251 | 252 | /// Converts self into a mutable reference to the value. 253 | /// 254 | /// If you need multiple references to the `OccupiedEntry`, see 255 | /// [`get_mut`](Self::get_mut). 256 | pub fn into_mut(self) -> RefMut<'a, T> 257 | where 258 | for<'k> T::Key<'k>: Hash, 259 | { 260 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 261 | // original reference to the map is not used at this point. 262 | unsafe { self.map.awaken() } 263 | .get_by_index_mut(self.index) 264 | .expect("index is known to be valid") 265 | } 266 | 267 | /// Sets the entry to a new value, returning the old value. 268 | /// 269 | /// # Panics 270 | /// 271 | /// Panics if `value.key()` is different from the key of the entry. 272 | pub fn insert(&mut self, value: T) -> T { 273 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 274 | // original reference to the map is not used at this point. 275 | // 276 | // Note that `replace_at_index` panics if the keys don't match. 277 | unsafe { self.map.reborrow() }.replace_at_index(self.index, value) 278 | } 279 | 280 | /// Takes ownership of the value from the map. 281 | pub fn remove(mut self) -> T { 282 | // SAFETY: The safety assumption behind `Self::new` guarantees that the 283 | // original reference to the map is not used at this point. 284 | unsafe { self.map.reborrow() } 285 | .remove_by_index(self.index) 286 | .expect("index is known to be valid") 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/iter.rs: -------------------------------------------------------------------------------- 1 | use super::{IdOrdItem, RefMut, tables::IdOrdMapTables}; 2 | use crate::support::{alloc::Global, btree_table, item_set::ItemSet}; 3 | use core::{hash::Hash, iter::FusedIterator}; 4 | 5 | /// An iterator over the elements of an [`IdOrdMap`] by shared reference. 6 | /// 7 | /// Created by [`IdOrdMap::iter`], and ordered by keys. 8 | /// 9 | /// [`IdOrdMap`]: crate::IdOrdMap 10 | /// [`IdOrdMap::iter`]: crate::IdOrdMap::iter 11 | #[derive(Clone, Debug)] 12 | pub struct Iter<'a, T: IdOrdItem> { 13 | items: &'a ItemSet, 14 | iter: btree_table::Iter<'a>, 15 | } 16 | 17 | impl<'a, T: IdOrdItem> Iter<'a, T> { 18 | pub(super) fn new( 19 | items: &'a ItemSet, 20 | tables: &'a IdOrdMapTables, 21 | ) -> Self { 22 | Self { items, iter: tables.key_to_item.iter() } 23 | } 24 | } 25 | 26 | impl<'a, T: IdOrdItem> Iterator for Iter<'a, T> { 27 | type Item = &'a T; 28 | 29 | #[inline] 30 | fn next(&mut self) -> Option { 31 | let index = self.iter.next()?; 32 | Some(&self.items[index]) 33 | } 34 | } 35 | 36 | impl ExactSizeIterator for Iter<'_, T> { 37 | #[inline] 38 | fn len(&self) -> usize { 39 | self.iter.len() 40 | } 41 | } 42 | 43 | // btree_set::Iter is a FusedIterator, so Iter is as well. 44 | impl FusedIterator for Iter<'_, T> {} 45 | 46 | /// An iterator over the elements of a [`IdOrdMap`] by mutable reference. 47 | /// 48 | /// This iterator returns [`RefMut`] instances. 49 | /// 50 | /// Created by [`IdOrdMap::iter_mut`], and ordered by keys. 51 | /// 52 | /// [`IdOrdMap`]: crate::IdOrdMap 53 | /// [`IdOrdMap::iter_mut`]: crate::IdOrdMap::iter_mut 54 | #[derive(Debug)] 55 | pub struct IterMut<'a, T: IdOrdItem> 56 | where 57 | for<'k> T::Key<'k>: Hash, 58 | { 59 | items: &'a mut ItemSet, 60 | tables: &'a IdOrdMapTables, 61 | iter: btree_table::Iter<'a>, 62 | } 63 | 64 | impl<'a, T: IdOrdItem> IterMut<'a, T> 65 | where 66 | for<'k> T::Key<'k>: Hash, 67 | { 68 | pub(super) fn new( 69 | items: &'a mut ItemSet, 70 | tables: &'a IdOrdMapTables, 71 | ) -> Self { 72 | Self { items, tables, iter: tables.key_to_item.iter() } 73 | } 74 | } 75 | 76 | impl<'a, T: IdOrdItem + 'a> Iterator for IterMut<'a, T> 77 | where 78 | for<'k> T::Key<'k>: Hash, 79 | { 80 | type Item = RefMut<'a, T>; 81 | 82 | #[inline] 83 | fn next(&mut self) -> Option { 84 | let index = self.iter.next()?; 85 | let item = &mut self.items[index]; 86 | let hash = self.tables.make_hash(item); 87 | 88 | // SAFETY: This lifetime extension from self to 'a is safe based on two 89 | // things: 90 | // 91 | // 1. We never repeat indexes, i.e. for an index i, once we've handed 92 | // out an item at i, creating `&mut T`, we'll never get the index i 93 | // again. (This is guaranteed from the set-based nature of the 94 | // iterator.) This means that we don't ever create a mutable alias to 95 | // the same memory. 96 | // 97 | // In particular, unlike all the other places we look up data from a 98 | // btree table, we don't pass a lookup function into 99 | // self.iter.next(). If we did, then it is possible the lookup 100 | // function would have been called with an old index i. But we don't 101 | // need to do that. 102 | // 103 | // 2. All mutable references to data within self.items are derived from 104 | // self.items. So, the rule described at [1] is upheld: 105 | // 106 | // > When creating a mutable reference, then while this reference 107 | // > exists, the memory it points to must not get accessed (read or 108 | // > written) through any other pointer or reference not derived from 109 | // > this reference. 110 | // 111 | // [1]: 112 | // https://doc.rust-lang.org/std/ptr/index.html#pointer-to-reference-conversion 113 | let item = unsafe { core::mem::transmute::<&mut T, &'a mut T>(item) }; 114 | Some(RefMut::new(hash, item)) 115 | } 116 | } 117 | 118 | impl<'a, T: IdOrdItem + 'a> ExactSizeIterator for IterMut<'a, T> 119 | where 120 | for<'k> T::Key<'k>: Hash, 121 | { 122 | #[inline] 123 | fn len(&self) -> usize { 124 | self.iter.len() 125 | } 126 | } 127 | 128 | // hash_map::IterMut is a FusedIterator, so IterMut is as well. 129 | impl<'a, T: IdOrdItem + 'a> FusedIterator for IterMut<'a, T> where 130 | for<'k> T::Key<'k>: Hash 131 | { 132 | } 133 | 134 | /// An iterator over the elements of a [`IdOrdMap`] by ownership. 135 | /// 136 | /// Created by [`IdOrdMap::into_iter`], and ordered by keys. 137 | /// 138 | /// [`IdOrdMap`]: crate::IdOrdMap 139 | /// [`IdOrdMap::into_iter`]: crate::IdOrdMap::into_iter 140 | #[derive(Debug)] 141 | pub struct IntoIter { 142 | items: ItemSet, 143 | iter: btree_table::IntoIter, 144 | } 145 | 146 | impl IntoIter { 147 | pub(super) fn new( 148 | items: ItemSet, 149 | tables: IdOrdMapTables, 150 | ) -> Self { 151 | Self { items, iter: tables.key_to_item.into_iter() } 152 | } 153 | } 154 | 155 | impl Iterator for IntoIter { 156 | type Item = T; 157 | 158 | #[inline] 159 | fn next(&mut self) -> Option { 160 | let index = self.iter.next()?; 161 | let next = self 162 | .items 163 | .remove(index) 164 | .unwrap_or_else(|| panic!("index {index} not found in items")); 165 | Some(next) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /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 | mod ref_mut; 11 | #[cfg(feature = "serde")] 12 | mod serde_impls; 13 | mod tables; 14 | pub(crate) mod trait_defs; 15 | 16 | #[cfg(feature = "daft")] 17 | pub use daft_impls::Diff; 18 | pub use entry::{Entry, OccupiedEntry, VacantEntry}; 19 | pub use imp::IdOrdMap; 20 | pub use iter::{IntoIter, Iter, IterMut}; 21 | pub use ref_mut::RefMut; 22 | pub use trait_defs::IdOrdItem; 23 | -------------------------------------------------------------------------------- /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 | for<'k> T::Key<'k>: Hash, 50 | { 51 | inner: Option>, 52 | } 53 | 54 | impl<'a, T: IdOrdItem> RefMut<'a, T> 55 | where 56 | for<'k> T::Key<'k>: Hash, 57 | { 58 | pub(super) fn new( 59 | hash: MapHash, 60 | borrowed: &'a mut T, 61 | ) -> Self { 62 | let inner = RefMutInner { hash, borrowed }; 63 | Self { inner: Some(inner) } 64 | } 65 | 66 | /// Borrows self into a shorter-lived `RefMut`. 67 | /// 68 | /// This `RefMut` will also check hash equality on drop. 69 | pub fn reborrow(&mut self) -> RefMut<'_, T> { 70 | let inner = self.inner.as_mut().unwrap(); 71 | let borrowed = &mut *inner.borrowed; 72 | RefMut::new(inner.hash.clone(), borrowed) 73 | } 74 | 75 | /// Converts this `RefMut` into a `&'a T`. 76 | pub fn into_ref(mut self) -> &'a T { 77 | let inner = self.inner.take().unwrap(); 78 | inner.into_ref() 79 | } 80 | } 81 | 82 | impl Drop for RefMut<'_, T> 83 | where 84 | for<'k> T::Key<'k>: Hash, 85 | { 86 | fn drop(&mut self) { 87 | if let Some(inner) = self.inner.take() { 88 | inner.into_ref(); 89 | } 90 | } 91 | } 92 | 93 | impl Deref for RefMut<'_, T> 94 | where 95 | for<'k> T::Key<'k>: Hash, 96 | { 97 | type Target = T; 98 | 99 | fn deref(&self) -> &Self::Target { 100 | self.inner.as_ref().unwrap().borrowed 101 | } 102 | } 103 | 104 | impl DerefMut for RefMut<'_, T> 105 | where 106 | for<'k> T::Key<'k>: Hash, 107 | { 108 | fn deref_mut(&mut self) -> &mut Self::Target { 109 | self.inner.as_mut().unwrap().borrowed 110 | } 111 | } 112 | 113 | impl fmt::Debug for RefMut<'_, T> 114 | where 115 | for<'k> T::Key<'k>: Hash, 116 | { 117 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 118 | match self.inner { 119 | Some(ref inner) => inner.fmt(f), 120 | None => { 121 | f.debug_struct("RefMut").field("borrowed", &"missing").finish() 122 | } 123 | } 124 | } 125 | } 126 | 127 | struct RefMutInner<'a, T: IdOrdItem> { 128 | hash: MapHash, 129 | borrowed: &'a mut T, 130 | } 131 | 132 | impl<'a, T: IdOrdItem> RefMutInner<'a, T> 133 | where 134 | for<'k> T::Key<'k>: Hash, 135 | { 136 | fn into_ref(self) -> &'a T { 137 | if !self.hash.is_same_hash(self.borrowed.key()) { 138 | panic!("key changed during RefMut borrow"); 139 | } 140 | 141 | self.borrowed 142 | } 143 | } 144 | 145 | impl fmt::Debug for RefMutInner<'_, T> { 146 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 147 | self.borrowed.fmt(f) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /crates/iddqd/src/id_ord_map/serde_impls.rs: -------------------------------------------------------------------------------- 1 | use super::{IdOrdItem, IdOrdMap}; 2 | use core::{fmt, marker::PhantomData}; 3 | use serde::{ 4 | Deserialize, Deserializer, Serialize, Serializer, 5 | de::{SeqAccess, Visitor}, 6 | ser::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 the list of items, rebuilding the 76 | /// 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_seq(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.write_str("a sequence of items representing an IdOrdMap") 103 | } 104 | 105 | fn visit_seq( 106 | self, 107 | mut seq: Access, 108 | ) -> Result 109 | where 110 | Access: SeqAccess<'de>, 111 | { 112 | let mut map = match seq.size_hint() { 113 | Some(size) => IdOrdMap::with_capacity(size), 114 | None => IdOrdMap::new(), 115 | }; 116 | 117 | while let Some(element) = seq.next_element()? { 118 | map.insert_unique(element).map_err(serde::de::Error::custom)?; 119 | } 120 | 121 | Ok(map) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /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) fn new() -> Self { 15 | Self::default() 16 | } 17 | 18 | #[doc(hidden)] 19 | pub(super) fn validate( 20 | &self, 21 | expected_len: usize, 22 | compactness: ValidateCompact, 23 | ) -> Result<(), ValidationError> { 24 | self.key_to_item.validate(expected_len, compactness).map_err( 25 | |error| ValidationError::Table { name: "key_to_item", error }, 26 | )?; 27 | 28 | Ok(()) 29 | } 30 | 31 | pub(super) fn make_hash( 32 | &self, 33 | item: &T, 34 | ) -> MapHash 35 | where 36 | for<'k> T::Key<'k>: Hash, 37 | { 38 | self.key_to_item.compute_hash(item.key()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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/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/src/macros.rs: -------------------------------------------------------------------------------- 1 | //! Macros for this crate. 2 | 3 | /// Implement upcasts for [`IdOrdMap`] or [`IdHashMap`]. 4 | /// 5 | /// The maps in this crate require that the key types' lifetimes are covariant. 6 | /// This macro assists with implementing this requirement. 7 | /// 8 | /// The macro is optional, and these implementations can be written by hand as 9 | /// well. 10 | /// 11 | /// [`IdOrdMap`]: crate::IdOrdMap 12 | /// [`IdHashMap`]: crate::IdHashMap 13 | #[macro_export] 14 | macro_rules! id_upcast { 15 | () => { 16 | #[inline] 17 | fn upcast_key<'short, 'long: 'short>( 18 | long: Self::Key<'long>, 19 | ) -> Self::Key<'short> 20 | where 21 | Self: 'long, 22 | { 23 | long 24 | } 25 | }; 26 | } 27 | 28 | /// Implement upcasts for [`BiHashMap`]. 29 | /// 30 | /// The maps in this crate require that the key types' lifetimes are covariant. 31 | /// This macro assists with implementing this requirement. 32 | /// 33 | /// The macro is optional, and these implementations can be written by hand as 34 | /// well. 35 | /// 36 | /// [`BiHashMap`]: crate::BiHashMap 37 | #[macro_export] 38 | macro_rules! bi_upcast { 39 | () => { 40 | #[inline] 41 | fn upcast_key1<'short, 'long: 'short>( 42 | long: Self::K1<'long>, 43 | ) -> Self::K1<'short> 44 | where 45 | Self: 'long, 46 | { 47 | long 48 | } 49 | 50 | #[inline] 51 | fn upcast_key2<'short, 'long: 'short>( 52 | long: Self::K2<'long>, 53 | ) -> Self::K2<'short> 54 | where 55 | Self: 'long, 56 | { 57 | long 58 | } 59 | }; 60 | } 61 | 62 | /// Implement upcasts for [`TriHashMap`]. 63 | /// 64 | /// The maps in this crate require that the key types' lifetimes are covariant. 65 | /// This macro assists with implementing this requirement. 66 | /// 67 | /// The macro is optional, and these implementations can be written by hand as 68 | /// well. 69 | /// 70 | /// [`TriHashMap`]: crate::TriHashMap 71 | #[macro_export] 72 | macro_rules! tri_upcast { 73 | () => { 74 | #[inline] 75 | fn upcast_key1<'short, 'long: 'short>( 76 | long: Self::K1<'long>, 77 | ) -> Self::K1<'short> 78 | where 79 | Self: 'long, 80 | { 81 | long 82 | } 83 | 84 | #[inline] 85 | fn upcast_key2<'short, 'long: 'short>( 86 | long: Self::K2<'long>, 87 | ) -> Self::K2<'short> 88 | where 89 | Self: 'long, 90 | { 91 | long 92 | } 93 | 94 | #[inline] 95 | fn upcast_key3<'short, 'long: 'short>( 96 | long: Self::K3<'long>, 97 | ) -> Self::K3<'short> 98 | where 99 | Self: 'long, 100 | { 101 | long 102 | } 103 | }; 104 | } 105 | 106 | // Internal macro to implement diffs. 107 | #[cfg(feature = "daft")] 108 | macro_rules! impl_diff_ref_cast { 109 | ($self: ident, $diff_ty: ty, $key_method: ident, $get_method: ident, $contains_method: ident, $ref_cast_ty: ty) => {{ 110 | let hasher = $self.before.hasher().clone(); 111 | let alloc = $self.before.allocator().clone(); 112 | let mut diff = <$diff_ty>::with_hasher_in(hasher, alloc); 113 | for before_item in $self.before { 114 | if let Some(after_item) = 115 | $self.after.$get_method(&before_item.$key_method()) 116 | { 117 | diff.common.insert_overwrite(IdLeaf::new( 118 | <$ref_cast_ty>::ref_cast(before_item), 119 | <$ref_cast_ty>::ref_cast(after_item), 120 | )); 121 | } else { 122 | diff.removed 123 | .insert_overwrite(<$ref_cast_ty>::ref_cast(before_item)); 124 | } 125 | } 126 | for after_item in $self.after { 127 | if !$self.before.$contains_method(&after_item.$key_method()) { 128 | diff.added 129 | .insert_overwrite(<$ref_cast_ty>::ref_cast(after_item)); 130 | } 131 | } 132 | diff 133 | }}; 134 | } 135 | -------------------------------------------------------------------------------- /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) fn global_alloc() -> Global { 19 | Global 20 | } 21 | 22 | #[derive(Clone, Copy, Default)] 23 | pub(crate) struct AllocWrapper(pub(crate) T); 24 | 25 | unsafe impl allocator_api2::alloc::Allocator for AllocWrapper { 26 | #[inline] 27 | fn allocate( 28 | &self, 29 | layout: Layout, 30 | ) -> Result, AllocError> { 31 | allocator_api2::alloc::Allocator::allocate(&self.0, layout) 32 | } 33 | 34 | #[inline] 35 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { 36 | allocator_api2::alloc::Allocator::deallocate(&self.0, ptr, layout); 37 | } 38 | } 39 | } 40 | 41 | // No-defaults case. 42 | #[cfg(not(feature = "allocator-api2"))] 43 | mod inner { 44 | use crate::alloc::alloc::Layout; 45 | use allocator_api2::alloc::AllocError; 46 | use core::ptr::NonNull; 47 | 48 | #[inline] 49 | pub(crate) fn global_alloc() -> Global { 50 | Global::default() 51 | } 52 | 53 | #[allow(clippy::missing_safety_doc)] // not exposed outside of this crate 54 | pub unsafe trait Allocator { 55 | fn allocate(&self, layout: Layout) 56 | -> Result, AllocError>; 57 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout); 58 | } 59 | 60 | #[derive(Copy, Clone, Default)] 61 | #[doc(hidden)] 62 | pub struct Global(allocator_api2::alloc::Global); 63 | 64 | unsafe impl Allocator for Global { 65 | #[inline] 66 | fn allocate( 67 | &self, 68 | layout: Layout, 69 | ) -> Result, AllocError> { 70 | allocator_api2::alloc::Allocator::allocate(&self.0, layout) 71 | } 72 | 73 | #[inline] 74 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { 75 | allocator_api2::alloc::Allocator::deallocate(&self.0, ptr, layout); 76 | } 77 | } 78 | 79 | #[derive(Clone, Copy, Default)] 80 | pub(crate) struct AllocWrapper(pub(crate) T); 81 | 82 | unsafe impl allocator_api2::alloc::Allocator for AllocWrapper { 83 | #[inline] 84 | fn allocate( 85 | &self, 86 | layout: Layout, 87 | ) -> Result, AllocError> { 88 | Allocator::allocate(&self.0, layout) 89 | } 90 | #[inline] 91 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { 92 | Allocator::deallocate(&self.0, ptr, layout); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /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 | unsafe impl<'a, T> Sync for DormantMutRef<'a, T> where &'a mut T: Sync {} 24 | unsafe impl<'a, T> Send for DormantMutRef<'a, T> where &'a mut T: Send {} 25 | 26 | impl<'a, T> DormantMutRef<'a, T> { 27 | /// Capture a unique borrow, and immediately reborrow it. For the compiler, 28 | /// the lifetime of the new reference is the same as the lifetime of the 29 | /// original reference, but you promise to use it for a shorter period. 30 | pub(crate) fn new(t: &'a mut T) -> (&'a mut T, Self) { 31 | let ptr = NonNull::from(t); 32 | // SAFETY: we hold the borrow throughout 'a via `_marker`, and we expose 33 | // only this reference, so it is unique. 34 | let new_ref = unsafe { &mut *ptr.as_ptr() }; 35 | (new_ref, Self { ptr, _marker: PhantomData }) 36 | } 37 | 38 | /// Revert to the unique borrow initially captured. 39 | /// 40 | /// # Safety 41 | /// 42 | /// The reborrow must have ended, i.e., the reference returned by `new` and 43 | /// all pointers and references derived from it, must not be used anymore. 44 | pub(crate) unsafe fn awaken(self) -> &'a mut T { 45 | // SAFETY: our own safety conditions imply this reference is again unique. 46 | unsafe { &mut *self.ptr.as_ptr() } 47 | } 48 | 49 | /// Borrows a new mutable reference from the unique borrow initially captured. 50 | /// 51 | /// # Safety 52 | /// 53 | /// The reborrow must have ended, i.e., the reference returned by `new` and 54 | /// all pointers and references derived from it, must not be used anymore. 55 | pub(crate) unsafe fn reborrow(&mut self) -> &'a mut T { 56 | // SAFETY: our own safety conditions imply this reference is again unique. 57 | unsafe { &mut *self.ptr.as_ptr() } 58 | } 59 | 60 | /// Borrows a new shared reference from the unique borrow initially captured. 61 | /// 62 | /// # Safety 63 | /// 64 | /// The reborrow must have ended, i.e., the reference returned by `new` and 65 | /// all pointers and references derived from it, must not be used anymore. 66 | pub(crate) unsafe fn reborrow_shared(&self) -> &'a T { 67 | // SAFETY: our own safety conditions imply this reference is again unique. 68 | unsafe { &*self.ptr.as_ptr() } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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) state: S, 23 | pub(super) items: HashTable>, 24 | } 25 | 26 | impl fmt::Debug for MapHashTable { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | f.debug_struct("MapHashTable") 29 | .field("state", &self.state) 30 | .field("items", &self.items) 31 | .finish() 32 | } 33 | } 34 | 35 | impl MapHashTable { 36 | pub(crate) fn with_capacity_and_hasher_in( 37 | capacity: usize, 38 | hasher: S, 39 | alloc: A, 40 | ) -> Self { 41 | Self { 42 | state: hasher, 43 | items: HashTable::with_capacity_in(capacity, AllocWrapper(alloc)), 44 | } 45 | } 46 | 47 | #[cfg(feature = "daft")] 48 | pub(crate) fn state(&self) -> &S { 49 | &self.state 50 | } 51 | 52 | pub(crate) fn len(&self) -> usize { 53 | self.items.len() 54 | } 55 | 56 | pub(crate) fn validate( 57 | &self, 58 | expected_len: usize, 59 | compactness: ValidateCompact, 60 | ) -> Result<(), TableValidationError> { 61 | if self.len() != expected_len { 62 | return Err(TableValidationError::new(format!( 63 | "expected length {expected_len}, was {}", 64 | self.len() 65 | ))); 66 | } 67 | 68 | match compactness { 69 | ValidateCompact::Compact => { 70 | // All items between 0 (inclusive) and self.len() (exclusive) 71 | // are expected to be present, and there are no duplicates. 72 | let mut values: Vec<_> = self.items.iter().copied().collect(); 73 | values.sort_unstable(); 74 | for (i, value) in values.iter().enumerate() { 75 | if *value != i { 76 | return Err(TableValidationError::new(format!( 77 | "expected value at index {i} to be {i}, was {value}" 78 | ))); 79 | } 80 | } 81 | } 82 | ValidateCompact::NonCompact => { 83 | // There should be no duplicates. 84 | let values: Vec<_> = self.items.iter().copied().collect(); 85 | let value_set: BTreeSet<_> = values.iter().copied().collect(); 86 | if value_set.len() != values.len() { 87 | return Err(TableValidationError::new(format!( 88 | "expected no duplicates, but found {} duplicates \ 89 | (values: {:?})", 90 | values.len() - value_set.len(), 91 | values, 92 | ))); 93 | } 94 | } 95 | } 96 | 97 | Ok(()) 98 | } 99 | 100 | pub(crate) fn compute_hash(&self, key: K) -> MapHash { 101 | MapHash { state: self.state.clone(), hash: self.state.hash_one(key) } 102 | } 103 | 104 | // Ensure that K has a consistent hash. 105 | pub(crate) fn find_index( 106 | &self, 107 | key: &Q, 108 | lookup: F, 109 | ) -> Option 110 | where 111 | F: Fn(usize) -> K, 112 | Q: ?Sized + Hash + Equivalent, 113 | { 114 | let hash = self.state.hash_one(key); 115 | self.items.find(hash, |index| key.equivalent(&lookup(*index))).copied() 116 | } 117 | 118 | pub(crate) fn entry( 119 | &mut self, 120 | key: K, 121 | lookup: F, 122 | ) -> Entry<'_, usize, AllocWrapper> 123 | where 124 | F: Fn(usize) -> K, 125 | { 126 | let hash = self.state.hash_one(&key); 127 | self.items.entry( 128 | hash, 129 | |index| lookup(*index) == key, 130 | |v| self.state.hash_one(lookup(*v)), 131 | ) 132 | } 133 | 134 | pub(crate) fn find_entry( 135 | &mut self, 136 | key: &Q, 137 | lookup: F, 138 | ) -> Result< 139 | OccupiedEntry<'_, usize, AllocWrapper>, 140 | AbsentEntry<'_, usize, AllocWrapper>, 141 | > 142 | where 143 | F: Fn(usize) -> K, 144 | K: Hash + Eq + Borrow, 145 | Q: ?Sized + Hash + Eq, 146 | { 147 | let hash = self.state.hash_one(key); 148 | self.items.find_entry(hash, |index| lookup(*index).borrow() == key) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/item_set.rs: -------------------------------------------------------------------------------- 1 | use super::alloc::AllocWrapper; 2 | use crate::{ 3 | internal::{ValidateCompact, ValidationError}, 4 | support::alloc::Allocator, 5 | }; 6 | use core::{ 7 | fmt, 8 | ops::{Index, IndexMut}, 9 | }; 10 | use hashbrown::{HashMap, hash_map}; 11 | use rustc_hash::FxBuildHasher; 12 | 13 | /// A map of items stored by integer index. 14 | #[derive(Clone)] 15 | pub(crate) struct ItemSet { 16 | // rustc-hash's FxHashMap is custom-designed for compact-ish integer keys. 17 | items: HashMap>, 18 | // The next index to use. This only ever goes up, not down. 19 | // 20 | // An alternative might be to use a free list of indexes, but that's 21 | // unnecessarily complex. 22 | next_index: usize, 23 | } 24 | 25 | impl Default for ItemSet { 26 | fn default() -> Self { 27 | Self::with_capacity_in(0, A::default()) 28 | } 29 | } 30 | 31 | impl fmt::Debug for ItemSet { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | f.debug_struct("ItemSet") 34 | .field("items", &self.items) 35 | .field("next_index", &self.next_index) 36 | .finish() 37 | } 38 | } 39 | 40 | impl ItemSet { 41 | pub(crate) fn with_capacity_in(capacity: usize, alloc: A) -> Self { 42 | Self { 43 | items: HashMap::with_capacity_and_hasher_in( 44 | capacity, 45 | Default::default(), 46 | AllocWrapper(alloc), 47 | ), 48 | next_index: 0, 49 | } 50 | } 51 | 52 | pub(crate) fn allocator(&self) -> &A { 53 | &self.items.allocator().0 54 | } 55 | 56 | /// Validates the item set. 57 | pub(crate) fn validate( 58 | &self, 59 | compactness: ValidateCompact, 60 | ) -> Result<(), ValidationError> { 61 | // If the map is expected to be compact, then ensure that all keys 62 | // between 0 and next_index are present. 63 | match compactness { 64 | ValidateCompact::Compact => { 65 | for i in 0..self.next_index { 66 | if !self.items.contains_key(&i) { 67 | return Err(ValidationError::General(format!( 68 | "ItemSet is not compact: missing index {i}" 69 | ))); 70 | } 71 | } 72 | } 73 | ValidateCompact::NonCompact => { 74 | // No real checks can be done in this case. 75 | } 76 | } 77 | 78 | Ok(()) 79 | } 80 | 81 | pub(crate) fn capacity(&self) -> usize { 82 | self.items.capacity() 83 | } 84 | 85 | #[inline] 86 | pub(crate) fn is_empty(&self) -> bool { 87 | self.items.is_empty() 88 | } 89 | 90 | #[inline] 91 | pub(crate) fn len(&self) -> usize { 92 | self.items.len() 93 | } 94 | 95 | #[inline] 96 | pub(crate) fn iter(&self) -> hash_map::Iter { 97 | self.items.iter() 98 | } 99 | 100 | #[inline] 101 | #[expect(dead_code)] 102 | pub(crate) fn iter_mut(&mut self) -> hash_map::IterMut { 103 | self.items.iter_mut() 104 | } 105 | 106 | #[inline] 107 | pub(crate) fn values(&self) -> hash_map::Values<'_, usize, T> { 108 | self.items.values() 109 | } 110 | 111 | #[inline] 112 | pub(crate) fn values_mut(&mut self) -> hash_map::ValuesMut<'_, usize, T> { 113 | self.items.values_mut() 114 | } 115 | 116 | #[inline] 117 | pub(crate) fn into_values( 118 | self, 119 | ) -> hash_map::IntoValues> { 120 | self.items.into_values() 121 | } 122 | 123 | #[inline] 124 | pub(crate) fn get(&self, index: usize) -> Option<&T> { 125 | self.items.get(&index) 126 | } 127 | 128 | #[inline] 129 | pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut T> { 130 | self.items.get_mut(&index) 131 | } 132 | 133 | #[inline] 134 | pub(crate) fn get_disjoint_mut( 135 | &mut self, 136 | indexes: [&usize; N], 137 | ) -> [Option<&mut T>; N] { 138 | self.items.get_many_mut(indexes) 139 | } 140 | 141 | // This is only used by IdOrdMap. 142 | #[cfg_attr(not(feature = "std"), expect(dead_code))] 143 | #[inline] 144 | pub(crate) fn next_index(&self) -> usize { 145 | self.next_index 146 | } 147 | 148 | #[inline] 149 | pub(crate) fn insert_at_next_index(&mut self, value: T) -> usize { 150 | let index = self.next_index; 151 | self.items.insert(index, value); 152 | self.next_index += 1; 153 | index 154 | } 155 | 156 | #[inline] 157 | pub(crate) fn remove(&mut self, index: usize) -> Option { 158 | let entry = self.items.remove(&index); 159 | if entry.is_some() && index == self.next_index - 1 { 160 | // If we removed the last entry, decrement next_index. Not strictly 161 | // necessary but a nice optimization. 162 | // 163 | // This does not guarantee compactness, since it's possible for the 164 | // following set of operations to occur: 165 | // 166 | // 0. start at next_index = 0 167 | // 1. insert 0, next_index = 1 168 | // 2. insert 1, next_index = 2 169 | // 3. remove 0, next_index = 2 170 | // 4. remove 1, next_index = 1 (not 0, even though the map is empty) 171 | // 172 | // Compactness would require a heap acting as a free list. But that 173 | // seems generally unnecessary. 174 | self.next_index -= 1; 175 | } 176 | entry 177 | } 178 | 179 | // This method assumes that value has the same ID. It also asserts that 180 | // `index` is valid (and panics if it isn't). 181 | #[inline] 182 | pub(crate) fn replace(&mut self, index: usize, value: T) -> T { 183 | self.items 184 | .insert(index, value) 185 | .unwrap_or_else(|| panic!("EntrySet index not found: {index}")) 186 | } 187 | } 188 | 189 | #[cfg(feature = "serde")] 190 | mod serde_impls { 191 | use super::ItemSet; 192 | use crate::support::alloc::Allocator; 193 | use serde::Serialize; 194 | 195 | impl Serialize for ItemSet { 196 | fn serialize(&self, serializer: S) -> Result 197 | where 198 | S: serde::Serializer, 199 | { 200 | // Serialize just the items -- don't serialize the map keys. We'll 201 | // rebuild the map keys on deserialization. 202 | serializer.collect_seq(self.items.values()) 203 | } 204 | } 205 | } 206 | 207 | impl Index for ItemSet { 208 | type Output = T; 209 | 210 | #[inline] 211 | fn index(&self, index: usize) -> &Self::Output { 212 | self.items 213 | .get(&index) 214 | .unwrap_or_else(|| panic!("ItemSet index not found: {index}")) 215 | } 216 | } 217 | 218 | impl IndexMut for ItemSet { 219 | #[inline] 220 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 221 | self.items 222 | .get_mut(&index) 223 | .unwrap_or_else(|| panic!("ItemSet index not found: {index}")) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /crates/iddqd/src/support/map_hash.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | fmt, 3 | hash::{BuildHasher, Hash}, 4 | }; 5 | 6 | /// Packages up a state and a hash for later validation. 7 | #[derive(Clone)] 8 | pub(crate) struct MapHash { 9 | pub(super) state: S, 10 | pub(super) hash: u64, 11 | } 12 | 13 | impl fmt::Debug for MapHash { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | f.debug_struct("MapHash") 16 | .field("hash", &self.hash) 17 | .finish_non_exhaustive() 18 | } 19 | } 20 | 21 | impl MapHash { 22 | pub(crate) fn is_same_hash(&self, key: K) -> bool { 23 | self.hash == self.state.hash_one(key) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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(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/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 | mod ref_mut; 10 | #[cfg(feature = "serde")] 11 | mod serde_impls; 12 | mod tables; 13 | pub(crate) mod trait_defs; 14 | 15 | #[cfg(feature = "daft")] 16 | pub use daft_impls::{ByK1, ByK2, ByK3, Diff, MapLeaf}; 17 | pub use imp::TriHashMap; 18 | pub use iter::{IntoIter, Iter, IterMut}; 19 | pub use ref_mut::RefMut; 20 | pub use trait_defs::TriHashItem; 21 | -------------------------------------------------------------------------------- /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(hashes: [MapHash; 3], borrowed: &'a mut T) -> Self { 56 | Self { inner: Some(RefMutInner { hashes, 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.hashes.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: TriHashItem, S> { 111 | hashes: [MapHash; 3], 112 | borrowed: &'a mut T, 113 | } 114 | 115 | impl<'a, T: TriHashItem, S: BuildHasher> RefMutInner<'a, T, S> { 116 | fn into_ref(self) -> &'a T { 117 | if !self.hashes[0].is_same_hash(self.borrowed.key1()) { 118 | panic!("key1 changed during RefMut borrow"); 119 | } 120 | if !self.hashes[1].is_same_hash(self.borrowed.key2()) { 121 | panic!("key2 changed during RefMut borrow"); 122 | } 123 | if !self.hashes[2].is_same_hash(self.borrowed.key3()) { 124 | panic!("key3 changed during RefMut borrow"); 125 | } 126 | 127 | self.borrowed 128 | } 129 | } 130 | 131 | impl fmt::Debug for RefMutInner<'_, T, S> { 132 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 133 | self.borrowed.fmt(f) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /crates/iddqd/src/tri_hash_map/serde_impls.rs: -------------------------------------------------------------------------------- 1 | use crate::{TriHashItem, TriHashMap, support::alloc::Allocator}; 2 | use core::{fmt, hash::BuildHasher, marker::PhantomData}; 3 | use serde::{ 4 | Deserialize, Serialize, Serializer, 5 | de::{SeqAccess, Visitor}, 6 | }; 7 | 8 | /// A `TriHashMap` serializes to the list of items. Items are serialized in 9 | /// arbitrary order. 10 | /// 11 | /// Serializing as a list of items rather than as a map works around the lack of 12 | /// non-string keys in formats like JSON. 13 | /// 14 | /// # Examples 15 | /// 16 | /// ``` 17 | /// # #[cfg(feature = "default-hasher")] { 18 | /// use iddqd::{TriHashItem, TriHashMap, tri_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 | /// value: usize, 28 | /// } 29 | /// 30 | /// // This is a complex key, so it can't be a JSON map key. 31 | /// #[derive(Eq, Hash, PartialEq)] 32 | /// struct ComplexKey<'a> { 33 | /// name: &'a str, 34 | /// email: &'a str, 35 | /// } 36 | /// 37 | /// impl TriHashItem for Item { 38 | /// type K1<'a> = u32; 39 | /// type K2<'a> = &'a str; 40 | /// type K3<'a> = ComplexKey<'a>; 41 | /// fn key1(&self) -> Self::K1<'_> { 42 | /// self.id 43 | /// } 44 | /// fn key2(&self) -> Self::K2<'_> { 45 | /// &self.name 46 | /// } 47 | /// fn key3(&self) -> Self::K3<'_> { 48 | /// ComplexKey { name: &self.name, email: &self.email } 49 | /// } 50 | /// tri_upcast!(); 51 | /// } 52 | /// 53 | /// let mut map = TriHashMap::::new(); 54 | /// map.insert_unique(Item { 55 | /// id: 1, 56 | /// name: "Alice".to_string(), 57 | /// email: "alice@example.com".to_string(), 58 | /// value: 42, 59 | /// }) 60 | /// .unwrap(); 61 | /// 62 | /// // The map is serialized as a list of items. 63 | /// let serialized = serde_json::to_string(&map).unwrap(); 64 | /// assert_eq!( 65 | /// serialized, 66 | /// r#"[{"id":1,"name":"Alice","email":"alice@example.com","value":42}]"#, 67 | /// ); 68 | /// # } 69 | /// ``` 70 | impl Serialize 71 | for TriHashMap 72 | where 73 | T: Serialize, 74 | { 75 | fn serialize( 76 | &self, 77 | serializer: Ser, 78 | ) -> Result { 79 | // Serialize just the items -- don't serialize the indexes. We'll 80 | // rebuild the indexes on deserialization. 81 | self.items.serialize(serializer) 82 | } 83 | } 84 | 85 | /// The `Deserialize` impl for `TriHashMap` deserializes the list of items and 86 | /// then rebuilds the indexes, producing an error if there are any duplicates. 87 | /// 88 | /// The `fmt::Debug` bound on `T` ensures better error reporting. 89 | impl< 90 | 'de, 91 | T: TriHashItem + fmt::Debug, 92 | S: Clone + BuildHasher + Default, 93 | A: Default + Clone + Allocator, 94 | > Deserialize<'de> for TriHashMap 95 | where 96 | T: Deserialize<'de>, 97 | { 98 | fn deserialize>( 99 | deserializer: D, 100 | ) -> Result { 101 | deserializer.deserialize_seq(SeqVisitor { 102 | _marker: PhantomData, 103 | hasher: S::default(), 104 | alloc: A::default(), 105 | }) 106 | } 107 | } 108 | impl< 109 | 'de, 110 | T: TriHashItem + fmt::Debug + Deserialize<'de>, 111 | S: Clone + BuildHasher, 112 | A: Clone + Allocator, 113 | > TriHashMap 114 | { 115 | /// Deserializes from a list of items, allocating new storage within the 116 | /// provided allocator. 117 | pub fn deserialize_in>( 118 | deserializer: D, 119 | alloc: A, 120 | ) -> Result 121 | where 122 | S: Default, 123 | { 124 | deserializer.deserialize_seq(SeqVisitor { 125 | _marker: PhantomData, 126 | hasher: S::default(), 127 | alloc, 128 | }) 129 | } 130 | 131 | /// Deserializes from a list of items, with the given hasher, using the 132 | /// default allocator. 133 | pub fn deserialize_with_hasher>( 134 | deserializer: D, 135 | hasher: S, 136 | ) -> Result 137 | where 138 | A: Default, 139 | { 140 | deserializer.deserialize_seq(SeqVisitor { 141 | _marker: PhantomData, 142 | hasher, 143 | alloc: A::default(), 144 | }) 145 | } 146 | 147 | /// Deserializes from a list of items, with the given hasher, and allocating 148 | /// new storage within the provided allocator. 149 | pub fn deserialize_with_hasher_in>( 150 | deserializer: D, 151 | hasher: S, 152 | alloc: A, 153 | ) -> Result { 154 | // First, deserialize the items. 155 | deserializer.deserialize_seq(SeqVisitor { 156 | _marker: PhantomData, 157 | hasher, 158 | alloc, 159 | }) 160 | } 161 | } 162 | 163 | struct SeqVisitor { 164 | _marker: PhantomData T>, 165 | hasher: S, 166 | alloc: A, 167 | } 168 | 169 | impl<'de, T, S, A> Visitor<'de> for SeqVisitor 170 | where 171 | T: TriHashItem + Deserialize<'de> + fmt::Debug, 172 | S: Clone + BuildHasher, 173 | A: Clone + Allocator, 174 | { 175 | type Value = TriHashMap; 176 | 177 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 178 | formatter.write_str("a sequence of items representing a TriHashMap") 179 | } 180 | 181 | fn visit_seq( 182 | self, 183 | mut seq: Access, 184 | ) -> Result 185 | where 186 | Access: SeqAccess<'de>, 187 | { 188 | let mut map = match seq.size_hint() { 189 | Some(size) => TriHashMap::with_capacity_and_hasher_in( 190 | size, 191 | self.hasher, 192 | self.alloc, 193 | ), 194 | None => TriHashMap::with_hasher_in(self.hasher, self.alloc), 195 | }; 196 | 197 | while let Some(element) = seq.next_element()? { 198 | map.insert_unique(element).map_err(serde::de::Error::custom)?; 199 | } 200 | 201 | Ok(map) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /crates/iddqd/src/tri_hash_map/tables.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | TriHashItem, 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 TriHashMapTables { 10 | pub(super) k1_to_item: MapHashTable, 11 | pub(super) k2_to_item: MapHashTable, 12 | pub(super) k3_to_item: MapHashTable, 13 | } 14 | 15 | impl TriHashMapTables { 16 | pub(super) fn with_capacity_and_hasher_in( 17 | capacity: usize, 18 | hasher: S, 19 | alloc: A, 20 | ) -> Self { 21 | Self { 22 | k1_to_item: MapHashTable::with_capacity_and_hasher_in( 23 | capacity, 24 | hasher.clone(), 25 | alloc.clone(), 26 | ), 27 | k2_to_item: MapHashTable::with_capacity_and_hasher_in( 28 | capacity, 29 | hasher.clone(), 30 | alloc.clone(), 31 | ), 32 | k3_to_item: MapHashTable::with_capacity_and_hasher_in( 33 | capacity, 34 | hasher.clone(), 35 | alloc, 36 | ), 37 | } 38 | } 39 | } 40 | 41 | impl TriHashMapTables { 42 | #[cfg(feature = "daft")] 43 | pub(super) fn hasher(&self) -> &S { 44 | self.k1_to_item.state() 45 | } 46 | 47 | pub(super) fn validate( 48 | &self, 49 | expected_len: usize, 50 | compactness: ValidateCompact, 51 | ) -> Result<(), ValidationError> { 52 | // Check that all the maps are of the right size. 53 | self.k1_to_item.validate(expected_len, compactness).map_err( 54 | |error| ValidationError::Table { name: "k1_to_table", error }, 55 | )?; 56 | self.k2_to_item.validate(expected_len, compactness).map_err( 57 | |error| ValidationError::Table { name: "k2_to_table", error }, 58 | )?; 59 | self.k3_to_item.validate(expected_len, compactness).map_err( 60 | |error| ValidationError::Table { name: "k3_to_table", error }, 61 | )?; 62 | 63 | Ok(()) 64 | } 65 | 66 | pub(super) fn make_hashes( 67 | &self, 68 | item: &T, 69 | ) -> [MapHash; 3] { 70 | let k1 = item.key1(); 71 | let k2 = item.key2(); 72 | let k3 = item.key3(); 73 | 74 | let h1 = self.k1_to_item.compute_hash(k1); 75 | let h2 = self.k2_to_item.compute_hash(k2); 76 | let h3 = self.k3_to_item.compute_hash(k3); 77 | 78 | [h1, h2, h3] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /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/tests/integration/main.rs: -------------------------------------------------------------------------------- 1 | mod bi_hash_map; 2 | mod id_hash_map; 3 | #[cfg(feature = "std")] 4 | mod id_ord_map; 5 | mod tri_hash_map; 6 | -------------------------------------------------------------------------------- /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 = "iddqd-{{version}}" 8 | publish = false 9 | dependent-version = "upgrade" 10 | pre-release-hook = ["just", "generate-readmes"] 11 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------