iddqd-0.3.17/.cargo_vcs_info.json0000644000000001520000000000100122070ustar { "git": { "sha1": "27307c21ab2554782af6bdc74ad1c5d6113294af" }, "path_in_vcs": "crates/iddqd" }iddqd-0.3.17/Cargo.lock0000644000000431400000000000100101660ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "atomicwrites" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ef1bb8d1b645fe38d51dfc331d720fb5fc2c94b440c76cc79c80ff265ca33e3" dependencies = [ "rustix 0.38.44", "tempfile", "windows-sys 0.52.0", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "console" version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", "once_cell", "unicode-width", "windows-sys 0.59.0", ] [[package]] name = "daft" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e09ff0315ed35eef47b90604921246c9eac0ace59aec99d46f7031eeea8cc0ec" dependencies = [ "paste", ] [[package]] name = "derive-ex" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bba95f299f6b9cd47f68a847eca2ae9060a2713af532dc35c342065544845407" dependencies = [ "proc-macro2", "quote", "structmeta", "syn", ] [[package]] name = "dyn-clone" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "expectorate" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cfe29c067b3dd398703f5cb05420a21c21079edfbcfa96c3ff2d9bde55cc8b3" dependencies = [ "atomicwrites", "console", "newline-converter", "similar", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" dependencies = [ "allocator-api2", ] [[package]] name = "iddqd" version = "0.3.17" dependencies = [ "allocator-api2", "daft", "equivalent", "expectorate", "foldhash", "hashbrown 0.16.0", "proptest", "ref-cast", "rustc-hash", "schemars", "serde", "serde_core", "serde_json", "test-strategy", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", "serde", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "newline-converter" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" dependencies = [ "unicode-segmentation", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bitflags", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", "regex-syntax", "unarray", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rand" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom", ] [[package]] name = "rand_xorshift" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ "rand_core", ] [[package]] name = "ref-cast" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] [[package]] name = "rustix" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schemars" version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ "dyn-clone", "indexmap", "schemars_derive", "serde", "serde_json", ] [[package]] name = "schemars_derive" version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", "syn", ] [[package]] name = "serde" version = "1.0.223" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.223" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.223" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_derive_internals" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "structmeta" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" dependencies = [ "proc-macro2", "quote", "structmeta-derive", "syn", ] [[package]] name = "structmeta-derive" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix 1.0.7", "windows-sys 0.59.0", ] [[package]] name = "test-strategy" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b12f9683de37f9980e485167ee624bfaa0b6b04da661e98e25ef9c2669bc1b" dependencies = [ "derive-ex", "proc-macro2", "quote", "structmeta", "syn", ] [[package]] name = "unarray" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "zerocopy" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", "syn", ] iddqd-0.3.17/Cargo.toml0000644000000071370000000000100102170ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.81" name = "iddqd" version = "0.3.17" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Maps where keys borrow from values, including bijective and trijective maps." documentation = "https://docs.rs/iddqd" readme = "README.md" keywords = [ "iddqd", "id_map", "bijective", "hashmap", "btreemap", ] categories = [ "data-structures", "no-std", ] license = "MIT OR Apache-2.0" repository = "https://github.com/oxidecomputer/iddqd" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg=doc_cfg"] [package.metadata.cargo-sync-rdme.badge.badges] license = true crates-io = true docs-rs = true rust-version = true [[package.metadata.release.pre-release-replacements]] file = "../iddqd-extended-examples/examples/typify-types.rs" search = 'iddqd" = ".*"' replace = 'iddqd" = "{{version}}"' exactly = 1 [features] allocator-api2 = [] daft = [ "dep:daft", "dep:ref-cast", ] default = [ "allocator-api2", "std", "default-hasher", ] default-hasher = ["dep:foldhash"] internal-schemars08-preserve-order = [ "schemars08", "schemars/preserve_order", ] proptest = ["dep:proptest"] schemars08 = [ "dep:schemars", "dep:serde_json", "serde", ] serde = ["dep:serde_core"] std = [ "dep:foldhash", "rustc-hash/std", ] [lib] name = "iddqd" path = "src/lib.rs" [[example]] name = "bi-complex" path = "examples/bi-complex.rs" required-features = ["default-hasher"] [[example]] name = "id-complex" path = "examples/id-complex.rs" required-features = [ "default-hasher", "std", ] [[example]] name = "schemars-examples" path = "examples/schemars-examples.rs" required-features = [ "default-hasher", "schemars08", ] [[example]] name = "tri-complex" path = "examples/tri-complex.rs" required-features = ["default-hasher"] [[test]] name = "integration" path = "tests/integration/main.rs" [dependencies.allocator-api2] version = "0.2.21" features = ["alloc"] default-features = false [dependencies.daft] version = "0.1.3" optional = true default-features = false [dependencies.equivalent] version = "1.0.2" [dependencies.foldhash] version = "0.2.0" optional = true [dependencies.hashbrown] version = "0.16.0" features = [ "allocator-api2", "inline-more", ] default-features = false [dependencies.proptest] version = "1.7.0" features = ["std"] optional = true default-features = false [dependencies.ref-cast] version = "1.0.24" optional = true [dependencies.rustc-hash] version = "2.1.1" default-features = false [dependencies.schemars] version = "0.8.22" optional = true [dependencies.serde_core] version = "1.0.223" optional = true [dependencies.serde_json] version = "1.0.145" optional = true [dev-dependencies.expectorate] version = "1.2.0" [dev-dependencies.proptest] version = "1.7.0" features = ["std"] default-features = false [dev-dependencies.serde] version = "1.0.223" [dev-dependencies.test-strategy] version = "0.4.3" [lints.clippy.undocumented_unsafe_blocks] level = "warn" priority = 0 [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = ["cfg(doc_cfg)"] iddqd-0.3.17/Cargo.toml.orig000064400000000000000000000051411046102023000136710ustar 00000000000000[package] name = "iddqd" version = "0.3.17" description = "Maps where keys borrow from values, including bijective and trijective maps." readme = "README.md" documentation = "https://docs.rs/iddqd" repository = "https://github.com/oxidecomputer/iddqd" keywords = ["iddqd", "id_map", "bijective", "hashmap", "btreemap"] categories = ["data-structures", "no-std"] edition.workspace = true license.workspace = true rust-version.workspace = true [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg=doc_cfg"] [lints] workspace = true [dependencies] # We have to turn on allocator-api2 here even if we don't expose it in our # public API. Even if the allocator-api2 feature is not enabled, we still rely # on being able to implement it for our Global type, so we can pass it into # hashbrown. allocator-api2 = { workspace = true } daft = { workspace = true, optional = true } equivalent.workspace = true foldhash = { workspace = true, optional = true } hashbrown.workspace = true ref-cast = { workspace = true, optional = true } rustc-hash.workspace = true schemars = { workspace = true, optional = true } serde_core = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } proptest = { workspace = true, optional = true } [dev-dependencies] expectorate.workspace = true iddqd-test-utils.workspace = true proptest.workspace = true serde.workspace = true test-strategy.workspace = true [features] allocator-api2 = ["iddqd-test-utils/allocator-api2"] daft = ["dep:daft", "dep:ref-cast"] default = ["allocator-api2", "std", "default-hasher"] default-hasher = ["dep:foldhash", "iddqd-test-utils/default-hasher"] proptest = ["dep:proptest"] schemars08 = ["dep:schemars", "dep:serde_json", "serde"] serde = ["dep:serde_core", "iddqd-test-utils/serde"] std = ["dep:foldhash", "iddqd-test-utils/std", "rustc-hash/std"] # Internal-only feature for testing that schemars/preserve_order works. internal-schemars08-preserve-order = ["schemars08", "schemars/preserve_order"] [package.metadata.cargo-sync-rdme.badge.badges] license = true crates-io = true docs-rs = true rust-version = true [[example]] name = "id-complex" required-features = ["default-hasher", "std"] [[example]] name = "bi-complex" required-features = ["default-hasher"] [[example]] name = "tri-complex" required-features = ["default-hasher"] [[example]] name = "schemars-examples" required-features = ["default-hasher", "schemars08"] [package.metadata.release] pre-release-replacements = [ { file = "../iddqd-extended-examples/examples/typify-types.rs", search = "iddqd\" = \".*\"", replace = "iddqd\" = \"{{version}}\"", exactly = 1 }, ] iddqd-0.3.17/LICENSE-APACHE000064400000000000000000000251721046102023000127340ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright (c) 2016 Alex Crichton Copyright (c) 2017 The Tokio Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. iddqd-0.3.17/LICENSE-MIT000064400000000000000000000020521046102023000124340ustar 00000000000000Copyright (c) 2025 Oxide Computer Company Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. iddqd-0.3.17/README.md000064400000000000000000000337761046102023000123000ustar 00000000000000 # iddqd ![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/iddqd.svg?) [![crates.io](https://img.shields.io/crates/v/iddqd.svg?logo=rust)](https://crates.io/crates/iddqd) [![docs.rs](https://img.shields.io/docsrs/iddqd.svg?logo=docs.rs)](https://docs.rs/iddqd) [![Rust: ^1.81.0](https://img.shields.io/badge/rust-^1.81.0-93450a.svg?logo=rust)](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) Maps where keys are borrowed from values. This crate consists of several map types, collectively called **ID maps**: * [`IdOrdMap`](https://docs.rs/iddqd/0.3.17/iddqd/id_ord_map/imp/struct.IdOrdMap.html): A B-Tree based map where keys are borrowed from values. * [`IdHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/id_hash_map/imp/struct.IdHashMap.html): A hash map where keys are borrowed from values. * [`BiHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/bi_hash_map/imp/struct.BiHashMap.html): A bijective (1:1) hash map with two keys, borrowed from values. * [`TriHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/tri_hash_map/imp/struct.TriHashMap.html): A trijective (1:1:1) hash map with three keys, borrowed from values. ## Usage * Pick your ID map type. * Depending on the ID map type, implement [`IdOrdItem`](https://docs.rs/iddqd/0.3.17/iddqd/id_ord_map/trait_defs/trait.IdOrdItem.html), [`IdHashItem`](https://docs.rs/iddqd/0.3.17/iddqd/id_hash_map/trait_defs/trait.IdHashItem.html), [`BiHashItem`](https://docs.rs/iddqd/0.3.17/iddqd/bi_hash_map/trait_defs/trait.BiHashItem.html), or [`TriHashItem`](https://docs.rs/iddqd/0.3.17/iddqd/tri_hash_map/trait_defs/trait.TriHashItem.html) for your value type. * Store values in the ID map type. ### Features This crate was built out a practical need for map types, and addresses issues encountered using Rust’s default map types in practice at Oxide. * Keys are retrieved from values, not stored separately from them. Separate storage has been a recurring pain point in our codebases: if keys are duplicated within values, it’s proven to be hard to maintain consistency between keys and values. This crate addresses that need. * Keys may be borrowed from values, which allows for more flexible implementations. (They don’t have to be borrowed, but they can be.) * There’s no `insert` method; insertion must be through either `insert_overwrite` or `insert_unique`. You must pick an insertion behavior. * For hash maps, the default hasher is [`foldhash`](https://docs.rs/foldhash/0.2.0/foldhash/index.html), which is much faster than SipHash. However, foldhash does *not provide the same level of HashDoS resistance* as SipHash. If that is important to you, you can use a different hasher. (Disable the `default-hasher` feature to require a hash builder type parameter to be passed in.) * The serde implementations reject duplicate keys. We’ve also sometimes needed to index a set of data by more than one key, or perhaps map one key to another. For that purpose, this crate provides [`BiHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/bi_hash_map/imp/struct.BiHashMap.html) and [`TriHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/tri_hash_map/imp/struct.TriHashMap.html). * [`BiHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/bi_hash_map/imp/struct.BiHashMap.html) has two keys, and provides a bijection (1:1 relationship) between the keys. * [`TriHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/tri_hash_map/imp/struct.TriHashMap.html) has three keys, and provides a trijection (1:1:1 relationship) between the keys. As a consequence of the general API structure, maps can have arbitrary non-key data associated with them as well. ### Examples An example for [`IdOrdMap`](https://docs.rs/iddqd/0.3.17/iddqd/id_ord_map/imp/struct.IdOrdMap.html): ````rust use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; #[derive(Debug)] struct User { name: String, age: u8, } // Implement IdOrdItem so the map knows how to get the key from the value. impl IdOrdItem for User { // The key type can borrow from the value. type Key<'a> = &'a str; fn key(&self) -> Self::Key<'_> { &self.name } id_upcast!(); } let mut users = IdOrdMap::::new(); // You must pick an insertion behavior. insert_unique returns an error if // the key already exists. users.insert_unique(User { name: "Alice".to_string(), age: 30 }).unwrap(); users.insert_unique(User { name: "Bob".to_string(), age: 35 }).unwrap(); // Lookup by name: assert_eq!(users.get("Alice").unwrap().age, 30); assert_eq!(users.get("Bob").unwrap().age, 35); // Iterate over users: for user in &users { println!("User {}: {}", user.name, user.age); } ```` Keys don’t have to be borrowed from the value. For smaller `Copy` types, it’s recommended that you use owned keys. Here’s an example of using [`IdOrdMap`](https://docs.rs/iddqd/0.3.17/iddqd/id_ord_map/imp/struct.IdOrdMap.html) with a small integer key: ````rust struct Record { id: u32, data: String, } impl IdOrdItem for Record { // The key type is small, so an owned key is preferred. type Key<'a> = u32; fn key(&self) -> Self::Key<'_> { self.id } id_upcast!(); } // ... ```` An example for [`IdHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/id_hash_map/imp/struct.IdHashMap.html), showing a complex borrowed key. Here, “complex” means that the key is not a reference itself, but a struct that returns references to more than one field from the value. ````rust use iddqd::{IdHashItem, id_hash_map, id_upcast}; #[derive(Debug)] struct Artifact { name: String, version: String, data: Vec, } // The key type is a borrowed form of the name and version. It needs to // implement `Eq + Hash`. #[derive(Eq, Hash, PartialEq)] struct ArtifactKey<'a> { name: &'a str, version: &'a str, } impl IdHashItem for Artifact { // The key type can borrow from the value. type Key<'a> = ArtifactKey<'a>; fn key(&self) -> Self::Key<'_> { ArtifactKey { name: &self.name, version: &self.version } } id_upcast!(); } // Create a new `IdHashMap` with the given artifacts. This uses the // `id_hash_map!` macro that comes with iddqd. let artifacts = id_hash_map! { Artifact { name: "artifact1".to_owned(), version: "1.0".to_owned(), data: b"data1".to_vec(), }, Artifact { name: "artifact2".to_owned(), version: "1.0".to_owned(), data: b"data2".to_vec(), }, }; // Look up artifacts by name and version. assert_eq!( artifacts .get(&ArtifactKey { name: "artifact1", version: "1.0" }) .unwrap() .data, b"data1", ); ```` For more examples, see the [examples](https://github.com/oxidecomputer/iddqd/tree/main/crates/iddqd/examples) and [extended examples](https://github.com/oxidecomputer/iddqd/tree/main/crates/iddqd-extended-examples/examples) directories. #### `Equivalent` and `Comparable` An important feature of the standard library’s maps is that they allow any borrowed form of the key type to be used for lookups; for example, a `HashMap` type can be looked up with a `&str` key. This is done through the [`Borrow`] trait. But the [`Borrow`] trait is a bit too restrictive for complex keys such as `ArtifactKey` above, requiring workarounds such as [dynamic dispatch](https://github.com/sunshowers-code/borrow-complex-key-example). To address this, the crates.io ecosystem has standardized on the [`Equivalent`](https://docs.rs/equivalent/1.0.2/equivalent/trait.Equivalent.html) and [`Comparable`](https://docs.rs/equivalent/1.0.2/equivalent/trait.Comparable.html) traits as generalizations of `Borrow`. The map types in this crate require these traits. For a key type `T::Key<'_>` and a lookup type `L`: * The hash map types require `L: Hash + Equivalent>`. Also, `L` must hash in the same way as `T::Key<'_>`. Typically, this is done by ensuring that enum variants and struct fields are in the same order[^proptest]. * [`IdOrdMap`](https://docs.rs/iddqd/0.3.17/iddqd/id_ord_map/imp/struct.IdOrdMap.html) requires `L: Comparable>`, which in turn requires `Equivalent>`. (There’s no need for `L` to implement `Ord` or `Eq` itself.) [^proptest]: We recommend that you test this with e.g. a property-based test: see [this example](https://github.com/sunshowers-code/borrow-complex-key-example/blob/a6f17699/src/lib.rs#L233). Continuing the `ArtifactKey` example from above, we can perform a lookup using a key of this owned form: ````rust use equivalent::Equivalent; // This is an owned form of ArtifactKey. The fields are in the same // order as ArtifactKey's fields, so it hashes the same way. #[derive(Hash)] struct OwnedArtifactKey { name: String, version: String, } impl Equivalent> for OwnedArtifactKey { fn equivalent(&self, other: &ArtifactKey<'_>) -> bool { self.name == other.name && self.version == other.version } } // Now you can use OwnedArtifactKey to look up the artifact. let owned_key = OwnedArtifactKey { name: "artifact1".to_owned(), version: "1.0".to_owned(), }; assert_eq!(artifacts.get(&owned_key).unwrap().data, b"data1",); ```` There’s a blanket implementation of [`Equivalent`](https://docs.rs/equivalent/1.0.2/equivalent/trait.Equivalent.html) and [`Comparable`](https://docs.rs/equivalent/1.0.2/equivalent/trait.Comparable.html) for [`Borrow`], so if your type already implements [`Borrow`], there aren’t any extra steps to take. ## Testing This crate is validated through a combination of: * Unit tests * Property-based tests using a naive map as an oracle * Chaos tests for several kinds of buggy `Eq` and `Ord` implementations * Miri tests for unsafe code If you see a gap in testing, new tests are welcome. Thank you! ## No-std compatibility Most of this crate is no-std compatible, though [`alloc`](https://doc.rust-lang.org/nightly/alloc/index.html) is required. The [`IdOrdMap`](https://docs.rs/iddqd/0.3.17/iddqd/id_ord_map/imp/struct.IdOrdMap.html) type is not currently no-std compatible due to its use of a thread-local. This thread-local is just a way to work around a limitation in std’s `BTreeMap` API, though. Either a custom B-Tree implementation, or a platform-specific notion of thread locals, would suffice to make [`IdOrdMap`](https://docs.rs/iddqd/0.3.17/iddqd/id_ord_map/imp/struct.IdOrdMap.html) no-std compatible. ## Optional features * `allocator-api2`: Enables support for custom allocators via the [`allocator_api2`](https://docs.rs/allocator-api2/0.2.21/allocator_api2/index.html) crate. Both global and scoped/arena allocators (such as `bumpalo`) are supported. Custom allocators are not currently supported by `IdOrdMap`. * `daft`: Enables [`daft`](https://docs.rs/daft/0.1.3/daft/index.html) support for all ID map types. *Not enabled by default.* * `default-hasher`: Enables the `DefaultHashBuilder` type. Disable this feature to require a hash builder type parameter to be passed into [`IdHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/id_hash_map/imp/struct.IdHashMap.html), [`BiHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/bi_hash_map/imp/struct.BiHashMap.html), and [`TriHashMap`](https://docs.rs/iddqd/0.3.17/iddqd/tri_hash_map/imp/struct.TriHashMap.html). *Enabled by default.* * `proptest`: Enables [`proptest`](https://docs.rs/proptest/1.7.0/proptest/index.html) support for all ID map types, providing [`Arbitrary`] implementations and strategies for property-based testing. *Not enabled by default.* * `schemars08`: Enables [`schemars`] support for all ID map types, including support for [automatic replacement] through [`typify`] or [`dropshot`]. *Not enabled by default.* * `serde`: Enables serde support for all ID map types. *Not enabled by default.* * `std`: Enables std support. *Enabled by default.* ## Related work * [`bimap`](https://docs.rs/bimap) provides a bijective map, but does not have a way to associate arbitrary values with each pair of keys. However, it does support an ordered map type without the need for std. * [`multi_index_map`](https://crates.io/crates/multi_index_map) provides maps with arbitrary indexes on fields, and is more flexible than this crate. However, it doesn’t expose generic traits for map types, and it requires key types to be `Clone`. In `iddqd`, we pick a somewhat different point in the design space, but we think `multi_index_map` is also great. * In general, this is similar to relational database records with indexes. For sufficiently complex use cases, consider an embedded database like [SQLite](https://www.sqlite.org/), or even a networked database like [PostgreSQL](https://www.postgresql.org/). `iddqd` is a good fit for simple in-memory caches of data stored in these databases. ## Minimum supported Rust version (MSRV) This crate’s MSRV is **Rust 1.81**. In general we aim for 6 months of Rust compatibility. ## What does iddqd mean? The name `iddqd` is a reference to [a cheat code](https://doomwiki.org/wiki/Doom_cheat_codes) in the classic video game *Doom*. It has `id` in the name, and is short and memorable. [`Borrow`]: https://doc.rust-lang.org/nightly/core/borrow/trait.Borrow.html [`Arbitrary`]: https://docs.rs/proptest/1.7.0/proptest/arbitrary/traits/trait.Arbitrary.html [`schemars`]: https://crates.io/crates/schemars [automatic replacement]: https://github.com/oxidecomputer/iddqd/blob/main/crates/iddqd-extended-examples/examples/typify-types.rs [`typify`]: https://crates.io/crates/typify [`dropshot`]: https://crates.io/crates/dropshot ## License This project is available under the terms of either the [Apache 2.0 license](LICENSE-APACHE) or the [MIT license](LICENSE-MIT). Portions adapted from [The Rust Programming Language](https://github.com/rust-lang/rust) and used under the MIT and Apache 2.0 licenses. The Rust Programming Language is (c) The Rust Project Contributors. Portions adapted from [hashbrown](https://github.com/rust-lang/hashbrown) and used under the MIT and Apache 2.0 licenses. hashbrown is (c) 2016-2025 Amanieu d'Antras and others. iddqd-0.3.17/examples/README.md000064400000000000000000000004121046102023000140730ustar 00000000000000# iddqd examples This directory contains some basic examples of iddqd use. For more examples including integrations with other crates, see the [extended examples](https://github.com/oxidecomputer/iddqd/tree/main/crates/iddqd-extended-examples/examples) directory. iddqd-0.3.17/examples/bi-complex.rs000064400000000000000000000057701046102023000152350ustar 00000000000000//! An example demonstrating `BiHashMap` use with complex borrowed keys. use iddqd::{BiHashItem, BiHashMap, bi_hash_map::Entry, bi_upcast}; use std::path::{Path, PathBuf}; /// These are the items we'll store in the `BiHashMap`. #[derive(Clone, Debug, PartialEq, Eq)] struct MyStruct { a: String, b: usize, c: PathBuf, d: Vec, } /// The map will be indexed uniquely by (b, c). Note that this is a /// borrowed key that can be constructed efficiently. #[derive(Clone, Debug, Hash, Eq, PartialEq)] struct MyKey1<'a> { b: usize, c: &'a Path, } /// The map will also be indexed uniquely by (&Path, &[usize]). #[derive(Clone, Debug, Hash, Eq, PartialEq)] struct MyKey2<'a> { c: &'a Path, d: &'a [usize], } impl BiHashItem for MyStruct { type K1<'a> = MyKey1<'a>; type K2<'a> = MyKey2<'a>; fn key1(&self) -> Self::K1<'_> { MyKey1 { b: self.b, c: &self.c } } fn key2(&self) -> Self::K2<'_> { MyKey2 { c: &self.c, d: &self.d } } bi_upcast!(); } fn main() { // Make a `TriHashMap` with the keys we defined above. let mut map = BiHashMap::new(); let item = MyStruct { a: "example".to_owned(), b: 20, c: PathBuf::from("/"), d: Vec::new(), }; // Add an item to the map. map.insert_unique(item.clone()).unwrap(); // This item will conflict with the previous one due to b and c matching. map.insert_unique(MyStruct { a: "something-else".to_owned(), b: 20, c: PathBuf::from("/"), d: vec![1], }) .unwrap_err(); // Add another item to the map. Note that this item has the same b and d // but a different c. let item2 = MyStruct { a: "example".to_owned(), b: 10, c: PathBuf::from("/home"), d: Vec::new(), }; map.insert_unique(item2.clone()).unwrap(); // Lookups can happen based on a borrowed key. For example: assert_eq!(map.get1(&MyKey1 { b: 20, c: Path::new("/") }), Some(&item)); // While iterating over the map, items will be returned in arbitrary order. for item in map.iter() { println!("{item:?}"); } // This matches by key1 but not key2. let item3 = MyStruct { a: "example".to_owned(), b: 20, c: PathBuf::from("/"), d: vec![1, 2, 3], }; // This matches by neither key1 nor key2. let item4 = MyStruct { a: "example".to_owned(), b: 20, c: PathBuf::from("/home"), d: vec![1, 2, 3], }; for item in [item, item2, item3, item4] { let entry = map.entry(item.key1(), item.key2()); match entry { Entry::Occupied(entry) => { // Get the entry's item. let item = entry.get(); println!("occupied: {item:?}"); } Entry::Vacant(entry) => { // Insert a new item. let item_ref = entry.insert(item); println!("inserted: {item_ref:?}"); } } } } iddqd-0.3.17/examples/id-complex.rs000064400000000000000000000074551046102023000152410ustar 00000000000000//! An example demonstrating `IdOrdMap` use with complex borrowed keys. use iddqd::{ Comparable, Equivalent, IdOrdItem, IdOrdMap, id_ord_map::Entry, id_upcast, }; use std::path::{Path, PathBuf}; /// These are the items we'll store in the `IdOrdMap`. #[derive(Clone, Debug, PartialEq, Eq)] struct MyStruct { a: String, b: usize, c: PathBuf, d: Vec, } /// The map will be indexed uniquely by (b, c, d). Note that this is a /// borrowed key that can be constructed efficiently. #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] struct MyKey<'a> { b: usize, c: &'a Path, d: &'a [usize], } impl IdOrdItem for MyStruct { type Key<'a> = MyKey<'a>; fn key(&self) -> Self::Key<'_> { MyKey { b: self.b, c: &self.c, d: &self.d } } id_upcast!(); } fn main() { // Make a `TriHashMap` with the keys we defined above. let mut map = IdOrdMap::new(); let item = MyStruct { a: "example".to_owned(), b: 20, c: PathBuf::from("/"), d: Vec::new(), }; // Add an item to the map. map.insert_unique(item.clone()).unwrap(); // This item will conflict with the previous one due to b, c and d // matching. map.insert_unique(MyStruct { a: "something-else".to_owned(), b: 20, c: PathBuf::from("/"), d: Vec::new(), }) .unwrap_err(); // Add another item to the map. Note that this item has the same c and d // but a different b. let item2 = MyStruct { a: "example".to_owned(), b: 10, c: PathBuf::from("/"), d: Vec::new(), }; map.insert_unique(item2.clone()).unwrap(); // Lookups can happen based on a borrowed key. For example: assert_eq!( map.get(&MyKey { b: 20, c: Path::new("/"), d: &[] }), Some(&item) ); // Values can also be mutated in place, as long as the key type implements // `Hash`. For example: { let mut item = map.get_mut(&MyKey { b: 20, c: Path::new("/"), d: &[] }).unwrap(); item.a = "changed".to_owned(); // Key changes will be checked when the item is dropped. } // While iterating over the map, items will be sorted by their key. for item in map.iter() { println!("{item:?}"); } let item3 = MyStruct { a: "example".to_owned(), b: 20, c: PathBuf::from("/"), d: vec![1, 2, 3], }; for item in [item, item2, item3.clone()] { let entry = map.entry(item.key()); match entry { Entry::Occupied(entry) => { // Get the entry's item. let item = entry.get(); println!("occupied: {item:?}"); } Entry::Vacant(entry) => { // Insert a new item. let item_ref = entry.insert_ref(item); println!("inserted: {item_ref:?}"); } } } // Lookups can be done with any key type that implements `Comparable`. This // is strictly more general than the Borrow you might be used to. For // example, lookups against an owned key: struct MyKeyOwned { b: usize, c: PathBuf, d: Vec, } impl Equivalent> for MyKeyOwned { fn equivalent(&self, other: &MyKey<'_>) -> bool { self.b == other.b && self.c == other.c && self.d == other.d } } impl Comparable> for MyKeyOwned { fn compare(&self, other: &MyKey<'_>) -> std::cmp::Ordering { self.b .cmp(&other.b) .then_with(|| self.c.as_path().cmp(other.c)) .then_with(|| self.d.as_slice().cmp(other.d)) } } let key = MyKeyOwned { b: 20, c: PathBuf::from("/"), d: vec![1, 2, 3] }; assert_eq!(map.get(&key), Some(&item3)); } iddqd-0.3.17/examples/schemars-examples.rs000064400000000000000000000057301046102023000166130ustar 00000000000000//! Basic usage example for iddqd with schemars. use iddqd::{ BiHashItem, BiHashMap, IdHashItem, IdHashMap, TriHashItem, TriHashMap, bi_upcast, id_upcast, tri_upcast, }; use schemars::{JsonSchema, schema_for}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Debug, Serialize, Deserialize, JsonSchema)] struct User { name: String, email: String, age: u32, } impl IdHashItem for User { type Key<'a> = &'a str; fn key(&self) -> Self::Key<'_> { &self.name } id_upcast!(); } impl BiHashItem for User { type K1<'a> = &'a str; // name type K2<'a> = &'a str; // email fn key1(&self) -> Self::K1<'_> { &self.name } fn key2(&self) -> Self::K2<'_> { &self.email } bi_upcast!(); } impl TriHashItem for User { type K1<'a> = &'a str; // name type K2<'a> = &'a str; // email type K3<'a> = u32; // age fn key1(&self) -> Self::K1<'_> { &self.name } fn key2(&self) -> Self::K2<'_> { &self.email } fn key3(&self) -> Self::K3<'_> { self.age } tri_upcast!(); } // Example container struct that uses iddqd maps with schema generation #[derive(Serialize, Deserialize, JsonSchema)] struct UserDatabase { /// Users indexed by name users_by_name: IdHashMap, /// Users with bidirectional lookup by name and email users_bi: BiHashMap, /// Users with three-way lookup by name, email, and age users_tri: TriHashMap, /// Regular HashMap for comparison metadata: HashMap, } fn main() { println!("* schemas:\n"); // Generate schemas for individual map types. println!("*** schema for IdHashMap:"); let id_hash_schema = schema_for!(IdHashMap); println!("{}\n", serde_json::to_string_pretty(&id_hash_schema).unwrap()); println!("*** schema for BiHashMap:"); let bi_hash_schema = schema_for!(BiHashMap); println!("{}\n", serde_json::to_string_pretty(&bi_hash_schema).unwrap()); println!("*** schema for TriHashMap:"); let tri_hash_schema = schema_for!(TriHashMap); println!("{}\n", serde_json::to_string_pretty(&tri_hash_schema).unwrap()); // Generate a schema for the container struct. println!("schema for UserDatabase (container with multiple map types):"); let database_schema = schema_for!(UserDatabase); println!("{}\n", serde_json::to_string_pretty(&database_schema).unwrap()); let mut users = IdHashMap::new(); users .insert_unique(User { name: "Alice".to_string(), email: "alice@example.com".to_string(), age: 30, }) .unwrap(); users .insert_unique(User { name: "Bob".to_string(), email: "bob@example.com".to_string(), age: 25, }) .unwrap(); println!("IdHashMap serializes as:"); println!("{}", serde_json::to_string_pretty(&users).unwrap()); } iddqd-0.3.17/examples/tri-complex.rs000064400000000000000000000046151046102023000154360ustar 00000000000000//! An example demonstrating `TriHashMap` use with complex borrowed keys. use iddqd::{TriHashItem, TriHashMap, tri_upcast}; use std::path::{Path, PathBuf}; /// These are the items we'll store in the `TriHashMap`. #[derive(Clone, Debug, PartialEq, Eq)] struct MyStruct { a: String, b: usize, c: PathBuf, d: Vec, } /// The map will be indexed uniquely by (usize, &Path). Note that this is a /// borrowed key that can be constructed efficiently. #[derive(Clone, Debug, Hash, Eq, PartialEq)] struct MyKey1<'a> { b: usize, c: &'a Path, } /// The map will also be indexed uniquely by (&Path, &[usize]). #[derive(Clone, Debug, Hash, Eq, PartialEq)] struct MyKey2<'a> { c: &'a Path, d: &'a [usize], } impl TriHashItem for MyStruct { type K1<'a> = MyKey1<'a>; type K2<'a> = MyKey2<'a>; // And finally, the map will be indexed uniquely by the `a` field, i.e. // String. (This could also be a borrowed key like `&'a str`, but we're // using String for this example to demonstrate the use of the `Borrow` // trait below.) type K3<'a> = String; fn key1(&self) -> Self::K1<'_> { MyKey1 { b: self.b, c: &self.c } } fn key2(&self) -> Self::K2<'_> { MyKey2 { c: &self.c, d: &self.d } } fn key3(&self) -> Self::K3<'_> { self.a.clone() } tri_upcast!(); } fn main() { // Make a `TriHashMap` with the keys we defined above. let mut map = TriHashMap::new(); let item = MyStruct { a: "example".to_owned(), b: 20, c: PathBuf::from("/"), d: Vec::new(), }; // Add an item to the map. map.insert_unique(item.clone()).unwrap(); // This item will conflict with the previous one due to the `a` field // matching. map.insert_unique(MyStruct { a: "example".to_owned(), b: 30, c: PathBuf::from("/xyz"), d: vec![0], }) .unwrap_err(); // Lookups can happen based on any of the keys. For example, we can look up // an item by the first key. assert_eq!(map.get1(&MyKey1 { b: 20, c: Path::new("/") }), Some(&item)); // We can also look up an item by anything that implements `Borrow`. For // example, &str for the third key. assert_eq!(map.get3("example"), Some(&item)); // For hash-based maps, iteration yields the items in an arbitrary order. for item in &map { println!("item: {item:?}"); } } iddqd-0.3.17/src/bi_hash_map/daft_impls.rs000064400000000000000000000350631046102023000165210ustar 00000000000000//! `Diffable` implementation. use super::{BiHashItem, BiHashMap}; use crate::{ DefaultHashBuilder, IdHashItem, id_hash_map, support::{ alloc::{Allocator, Global}, daft_utils::IdLeaf, }, }; use core::{ fmt, hash::{BuildHasher, Hash}, }; use daft::Diffable; use equivalent::Equivalent; use ref_cast::RefCast; impl Diffable for BiHashMap { type Diff<'a> = MapLeaf<'a, T, S, A> where T: 'a, S: 'a, A: 'a; fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> { MapLeaf { before: self, after: other } } } /// A leaf diff of two [`BiHashMap`]s. /// /// This diff is lazy and has not been evaluated yet. To evaluate the diff, /// call: /// /// * [`Self::by_key1`] to get a diff indexed by `key1`. /// * [`Self::by_key2`] to get a diff indexed by `key2`. /// * [`Self::by_unique`] to get a diff indexed by `key1` and `key2`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use daft::Diffable; /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Eq, PartialEq)] /// struct Item { /// id: u32, /// name: String, /// value: u32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// /// bi_upcast!(); /// } /// /// // Create two BiHashMaps with overlapping items. /// let mut map1 = BiHashMap::new(); /// map1.insert_unique(Item { id: 1, name: "alice".to_string(), value: 10 }); /// map1.insert_unique(Item { id: 2, name: "bob".to_string(), value: 20 }); /// /// let mut map2 = BiHashMap::new(); /// map2.insert_unique(Item { id: 2, name: "bob".to_string(), value: 30 }); /// map2.insert_unique(Item { id: 3, name: "charlie".to_string(), value: 40 }); /// /// // Compute the diff between the two maps. /// let map_leaf = map1.diff(&map2); /// /// // Get diff by key1 (id). /// let diff_by_id = map_leaf.by_key1(); /// assert!(diff_by_id.removed.contains_key(&1)); // alice removed /// assert!(diff_by_id.is_modified(&2)); // bob modified /// assert!(diff_by_id.added.contains_key(&3)); // charlie added /// /// // Get diff by key2 (name). /// let diff_by_name = map_leaf.by_key2(); /// assert!(diff_by_name.removed.contains_key("alice")); // alice removed /// assert!(diff_by_name.is_modified("bob")); // bob modified /// assert!(diff_by_name.added.contains_key("charlie")); // charlie added /// /// // Get diff by unique combination of both keys. /// let diff_unique = map_leaf.by_unique(); /// assert!(diff_unique.removed.contains_key1(&1)); // alice removed (by id) /// assert!(diff_unique.removed.contains_key2("alice")); // alice removed (by name) /// assert!(diff_unique.is_modified1(&2)); // bob modified (by id) /// assert!(diff_unique.is_modified2("bob")); // bob modified (by name) /// assert!(diff_unique.added.contains_key1(&3)); // charlie added (by id) /// assert!(diff_unique.added.contains_key2("charlie")); // charlie added (by name) /// # } /// ``` pub struct MapLeaf< 'daft, T: BiHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { /// The before map. pub before: &'daft BiHashMap, /// The after map. pub after: &'daft BiHashMap, } impl<'a, 'daft, T: BiHashItem + fmt::Debug, S, A: Allocator> fmt::Debug for MapLeaf<'daft, T, S, A> where T::K1<'a>: fmt::Debug, T::K2<'a>: fmt::Debug, T: 'a, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MapLeaf") .field("before", &self.before) .field("after", &self.after) .finish() } } impl<'daft, T: BiHashItem, S, A: Allocator> Clone for MapLeaf<'daft, T, S, A> { fn clone(&self) -> Self { *self } } impl<'daft, T: BiHashItem, S, A: Allocator> Copy for MapLeaf<'daft, T, S, A> {} impl<'daft, T: BiHashItem + PartialEq, S: Clone + BuildHasher, A: Allocator> PartialEq for MapLeaf<'daft, T, S, A> { fn eq(&self, other: &Self) -> bool { self.before == other.before && self.after == other.after } } impl<'daft, T: BiHashItem + Eq, S: Clone + BuildHasher, A: Allocator> Eq for MapLeaf<'daft, T, S, A> { } impl<'daft, T: BiHashItem, S: Clone + BuildHasher, A: Clone + Allocator> MapLeaf<'daft, T, S, A> { /// Returns a diff of two [`BiHashMap`]s, indexed by `key1`. /// /// Note that the return type is a [`id_hash_map::Diff`]. pub fn by_key1(self) -> id_hash_map::Diff<'daft, ByK1, S, A> { impl_diff_ref_cast!( self, id_hash_map::Diff::<'daft, ByK1, S, A>, key1, get1, contains_key1, ByK1 ) } /// Returns a diff of two [`BiHashMap`]s, indexed by `key2`. /// /// Note that the return type is a [`id_hash_map::Diff`]. pub fn by_key2(self) -> id_hash_map::Diff<'daft, ByK2, S, A> { impl_diff_ref_cast!( self, id_hash_map::Diff::<'daft, ByK2, S, A>, key2, get2, contains_key2, ByK2 ) } /// Returns a diff of two [`BiHashMap`]s, indexed by `key1` and `key2`. /// /// The return type is a [`Diff`]. pub fn by_unique(self) -> Diff<'daft, T, S, A> { let mut diff = Diff::with_hasher_in( self.before.hasher().clone(), self.before.allocator().clone(), ); for item in self.before { if let Some(after_item) = self.after.get_unique(&item.key1(), &item.key2()) { diff.common.insert_overwrite(IdLeaf::new(item, after_item)); } else { diff.removed.insert_overwrite(item); } } for item in self.after { if !self.before.contains_key_unique(&item.key1(), &item.key2()) { diff.added.insert_overwrite(item); } } diff } } /// A diff of two [`BiHashMap`]s, indexed by both `key1` and `key2`. pub struct Diff< 'daft, T: ?Sized + BiHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { /// Entries common to both maps. /// /// Items are stored as [`IdLeaf`]s to references. pub common: BiHashMap, S, A>, /// Added entries. pub added: BiHashMap<&'daft T, S, A>, /// Removed entries. pub removed: BiHashMap<&'daft T, S, A>, } impl<'daft, T: ?Sized + BiHashItem, S: Default, A: Allocator + Default> Default for Diff<'daft, T, S, A> { fn default() -> Self { Self { common: BiHashMap::default(), added: BiHashMap::default(), removed: BiHashMap::default(), } } } impl<'a, 'daft, T, S, A> fmt::Debug for Diff<'daft, T, S, A> where T: ?Sized + BiHashItem + fmt::Debug, T::K1<'a>: fmt::Debug, T::K2<'a>: fmt::Debug, T: 'a, 'daft: 'a, A: Allocator, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Diff") .field("common", &self.common) .field("added", &self.added) .field("removed", &self.removed) .finish() } } #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] impl<'daft, T: ?Sized + BiHashItem> Diff<'daft, T> { /// Creates a new, empty `Diff`. pub fn new() -> Self { Self { common: BiHashMap::new(), added: BiHashMap::new(), removed: BiHashMap::new(), } } } #[cfg(feature = "allocator-api2")] impl<'daft, T: ?Sized + BiHashItem, S: Clone + BuildHasher> Diff<'daft, T, S> { /// Creates a new, empty `Diff` with the given hasher. pub fn with_hasher(hasher: S) -> Self { Self { common: BiHashMap::with_hasher(hasher.clone()), added: BiHashMap::with_hasher(hasher.clone()), removed: BiHashMap::with_hasher(hasher), } } } impl< 'daft, T: ?Sized + BiHashItem, S: Clone + BuildHasher, A: Clone + Allocator, > Diff<'daft, T, S, A> { /// Creates a new, empty `Diff` with the given hasher and allocator. pub fn with_hasher_in(hasher: S, alloc: A) -> Self { Self { common: BiHashMap::with_hasher_in(hasher.clone(), alloc.clone()), added: BiHashMap::with_hasher_in(hasher.clone(), alloc.clone()), removed: BiHashMap::with_hasher_in(hasher, alloc), } } } impl<'daft, T: ?Sized + BiHashItem + Eq, S: Clone + BuildHasher, A: Allocator> Diff<'daft, T, S, A> { /// Returns an iterator over unchanged keys and values. pub fn unchanged(&self) -> impl Iterator + '_ { self.common .iter() .filter_map(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns true if the item corresponding to `key1` is unchanged. pub fn is_unchanged1<'a, Q>(&'a self, key1: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get1(key1).is_some_and(|leaf| leaf.is_unchanged()) } /// Returns true if the item corresponding to `key2` is unchanged. pub fn is_unchanged2<'a, Q>(&'a self, key2: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get2(key2).is_some_and(|leaf| leaf.is_unchanged()) } /// Returns the value associated with `key1` if it is unchanged, /// otherwise `None`. pub fn get_unchanged1<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> where Q: ?Sized + Hash + Equivalent>, { self.common .get1(key) .and_then(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns the value associated with `key2` if it is unchanged, /// otherwise `None`. pub fn get_unchanged2<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> where Q: ?Sized + Hash + Equivalent>, { self.common .get2(key) .and_then(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns an iterator over modified keys and values. pub fn modified(&self) -> impl Iterator> + '_ { self.common .iter() .filter_map(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns true if the value corresponding to `key1` is modified. pub fn is_modified1<'a, Q>(&'a self, key1: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get1(key1).is_some_and(|leaf| leaf.is_modified()) } /// Returns true if the value corresponding to `key2` is modified. pub fn is_modified2<'a, Q>(&'a self, key2: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get2(key2).is_some_and(|leaf| leaf.is_modified()) } /// Returns the [`IdLeaf`] associated with `key1` if it is modified, /// otherwise `None`. pub fn get_modified1<'a, Q>(&'a self, key: &Q) -> Option> where Q: ?Sized + Hash + Equivalent>, { self.common .get1(key) .and_then(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns the [`IdLeaf`] associated with `key2` if it is modified, /// otherwise `None`. pub fn get_modified2<'a, Q>(&'a self, key: &Q) -> Option> where Q: ?Sized + Hash + Equivalent>, { self.common .get2(key) .and_then(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns an iterator over modified keys and values, performing a diff on /// the values. /// /// This is useful when `T::Diff` is a complex type, not just a /// [`daft::Leaf`]. pub fn modified_diff(&self) -> impl Iterator> + '_ where T: Diffable, { self.modified().map(|leaf| leaf.diff_pair()) } } impl BiHashItem for IdLeaf { type K1<'a> = T::K1<'a> where T: 'a; type K2<'a> = T::K2<'a> where T: 'a; fn key1(&self) -> Self::K1<'_> { let before_key = self.before().key1(); if before_key != self.after().key1() { panic!("key is different between before and after"); } before_key } fn key2(&self) -> Self::K2<'_> { let before_key = self.before().key2(); if before_key != self.after().key2() { panic!("key is different between before and after"); } before_key } #[inline] fn upcast_key1<'short, 'long: 'short>( long: Self::K1<'long>, ) -> Self::K1<'short> { T::upcast_key1(long) } #[inline] fn upcast_key2<'short, 'long: 'short>( long: Self::K2<'long>, ) -> Self::K2<'short> { T::upcast_key2(long) } } /// Maps a [`BiHashItem`] to an [`IdHashItem`], indexed by `key1`. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, RefCast)] #[repr(transparent)] pub struct ByK1(pub T); impl ByK1 { /// Converts a `&T` to a `&ByK1`. #[inline] pub fn ref_cast(item: &T) -> &Self { RefCast::ref_cast(item) } /// Converts a `&mut T` to a `&mut ByK1`. #[inline] pub fn ref_cast_mut(item: &mut T) -> &mut Self { RefCast::ref_cast_mut(item) } } impl IdHashItem for ByK1 { type Key<'a> = T::K1<'a> where T: 'a; #[inline] fn key(&self) -> Self::Key<'_> { self.0.key1() } #[inline] fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> { T::upcast_key1(long) } } /// Maps a [`BiHashItem`] to an [`IdHashItem`], indexed by `key2`. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, RefCast)] #[repr(transparent)] pub struct ByK2(pub T); impl ByK2 { /// Converts a `&T` to a `&ByK1`. #[inline] pub fn ref_cast(item: &T) -> &Self { RefCast::ref_cast(item) } /// Converts a `&mut T` to a `&mut ByK1`. #[inline] pub fn ref_cast_mut(item: &mut T) -> &mut Self { RefCast::ref_cast_mut(item) } } impl IdHashItem for ByK2 { type Key<'a> = T::K2<'a> where T: 'a; #[inline] fn key(&self) -> Self::Key<'_> { self.0.key2() } #[inline] fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> { T::upcast_key2(long) } } iddqd-0.3.17/src/bi_hash_map/entry.rs000064400000000000000000000356001046102023000155350ustar 00000000000000use super::{BiHashItem, BiHashMap, RefMut, entry_indexes::EntryIndexes}; use crate::{ DefaultHashBuilder, support::{ alloc::{Allocator, Global}, borrow::DormantMutRef, map_hash::MapHash, }, }; use alloc::vec::Vec; use core::{fmt, hash::BuildHasher}; /// An implementation of the Entry API for [`BiHashMap`]. pub enum Entry<'a, T: BiHashItem, S = DefaultHashBuilder, A: Allocator = Global> { /// A vacant entry: none of the provided keys are present. Vacant(VacantEntry<'a, T, S, A>), /// An occupied entry where at least one of the keys is present in the map. Occupied(OccupiedEntry<'a, T, S, A>), } impl<'a, T: BiHashItem, S, A: Allocator> fmt::Debug for Entry<'a, T, S, A> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Entry::Vacant(entry) => { f.debug_tuple("Vacant").field(entry).finish() } Entry::Occupied(entry) => { f.debug_tuple("Occupied").field(entry).finish() } } } } impl<'a, T: BiHashItem, S: Clone + BuildHasher, A: Allocator> Entry<'a, T, S, A> { /// Ensures a value is in the entry by inserting the default if empty, and /// returns a mutable reference to the value in the entry. /// /// # Panics /// /// Panics if the key hashes to a different value than the one passed /// into [`BiHashMap::entry`]. #[inline] pub fn or_insert(self, default: T) -> OccupiedEntryMut<'a, T, S> { match self { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { OccupiedEntryMut::Unique(entry.insert(default)) } } } /// Ensures a value is in the entry by inserting the result of the default /// function if empty, and returns a mutable reference to the value in the /// entry. /// /// # Panics /// /// Panics if the key hashes to a different value than the one passed /// into [`BiHashMap::entry`]. #[inline] pub fn or_insert_with T>( self, default: F, ) -> OccupiedEntryMut<'a, T, S> { match self { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { OccupiedEntryMut::Unique(entry.insert(default())) } } } /// Provides in-place mutable access to occupied entries before any /// potential inserts into the map. /// /// `F` is called for each entry that matches the provided keys. #[inline] pub fn and_modify(self, f: F) -> Self where F: FnMut(RefMut<'_, T, S>), { match self { Entry::Occupied(mut entry) => { entry.get_mut().for_each(f); Entry::Occupied(entry) } Entry::Vacant(entry) => Entry::Vacant(entry), } } } /// A vacant entry. pub struct VacantEntry< 'a, T: BiHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { map: DormantMutRef<'a, BiHashMap>, hashes: [MapHash; 2], } impl<'a, T: BiHashItem, S, A: Allocator> fmt::Debug for VacantEntry<'a, T, S, A> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("VacantEntry") .field("hashes", &self.hashes) .finish_non_exhaustive() } } impl<'a, T: BiHashItem, S: Clone + BuildHasher, A: Allocator> VacantEntry<'a, T, S, A> { pub(super) unsafe fn new( map: DormantMutRef<'a, BiHashMap>, hashes: [MapHash; 2], ) -> Self { VacantEntry { map, hashes } } /// Sets the entry to a new value, returning a mutable reference to the /// value. pub fn insert(self, value: T) -> RefMut<'a, T, S> { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.awaken() }; let state = &map.tables.state; if !self.hashes[0].is_same_hash(state, value.key1()) { panic!("key1 hashes do not match"); } if !self.hashes[1].is_same_hash(state, value.key2()) { panic!("key2 hashes do not match"); } let Ok(index) = map.insert_unique_impl(value) else { panic!("key already present in map"); }; map.get_by_index_mut(index).expect("index is known to be valid") } /// Sets the value of the entry, and returns an `OccupiedEntry`. #[inline] pub fn insert_entry(mut self, value: T) -> OccupiedEntry<'a, T, S, A> { let index = { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.reborrow() }; let state = &map.tables.state; if !self.hashes[0].is_same_hash(state, value.key1()) { panic!("key1 hashes do not match"); } if !self.hashes[1].is_same_hash(state, value.key2()) { panic!("key2 hashes do not match"); } let Ok(index) = map.insert_unique_impl(value) else { panic!("key already present in map"); }; index }; // SAFETY: map, as well as anything that was borrowed from it, is // dropped once the above block exits. unsafe { OccupiedEntry::new(self.map, EntryIndexes::Unique(index)) } } } /// A view into an occupied entry in a [`BiHashMap`]. Part of the [`Entry`] /// enum. pub struct OccupiedEntry< 'a, T: BiHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { map: DormantMutRef<'a, BiHashMap>, indexes: EntryIndexes, } impl<'a, T: BiHashItem, S, A: Allocator> fmt::Debug for OccupiedEntry<'a, T, S, A> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("OccupiedEntry") .field("indexes", &self.indexes) .finish_non_exhaustive() } } impl<'a, T: BiHashItem, S: Clone + BuildHasher, A: Allocator> OccupiedEntry<'a, T, S, A> { /// # Safety /// /// After self is created, the original reference created by /// `DormantMutRef::new` must not be used. pub(super) unsafe fn new( map: DormantMutRef<'a, BiHashMap>, indexes: EntryIndexes, ) -> Self { OccupiedEntry { map, indexes } } /// Returns true if the entry is unique. /// /// Since [`BiHashMap`] is keyed by two keys, it's possible for /// `OccupiedEntry` to match up to two separate items. This function returns /// true if the entry is unique, meaning all keys point to exactly one item. pub fn is_unique(&self) -> bool { self.indexes.is_unique() } /// Returns true if the `OccupiedEntry` represents more than one item, or if /// some keys are not present. #[inline] pub fn is_non_unique(&self) -> bool { !self.is_unique() } /// Returns references to values that match the provided keys. /// /// If you need a reference to `T` that may outlive the destruction of the /// `Entry` value, see [`into_ref`](Self::into_ref). pub fn get(&self) -> OccupiedEntryRef<'_, T> { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.reborrow_shared() }; map.get_by_entry_index(self.indexes) } /// Returns mutable references to values that match the provided keys. /// /// If you need a reference to `T` that may outlive the destruction of the /// `Entry` value, see [`into_mut`](Self::into_mut). pub fn get_mut(&mut self) -> OccupiedEntryMut<'_, T, S> { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.reborrow() }; map.get_by_entry_index_mut(self.indexes) } /// Converts self into shared references to items that match the provided /// keys. /// /// If you need multiple references to the `OccupiedEntry`, see /// [`get`](Self::get). pub fn into_ref(self) -> OccupiedEntryRef<'a, T> { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.awaken() }; map.get_by_entry_index(self.indexes) } /// Converts self into mutable references to items that match the provided /// keys. /// /// If you need multiple references to the `OccupiedEntry`, see /// [`get_mut`](Self::get_mut). pub fn into_mut(self) -> OccupiedEntryMut<'a, T, S> { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.awaken() }; map.get_by_entry_index_mut(self.indexes) } /// Sets the entry to a new value, returning all values that conflict. /// /// # Panics /// /// Panics if the passed-in key is different from the key of the entry. pub fn insert(&mut self, value: T) -> Vec { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. // // Note that `replace_at_indexes` panics if the keys don't match. let map = unsafe { self.map.reborrow() }; let (index, old_items) = map.replace_at_indexes(self.indexes, value); self.indexes = EntryIndexes::Unique(index); old_items } /// Takes ownership of the values from the map. pub fn remove(mut self) -> Vec { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.reborrow() }; map.remove_by_entry_index(self.indexes) } } /// A view into an occupied entry in a [`BiHashMap`]. /// /// Returned by [`OccupiedEntry::get`]. #[derive(Debug)] pub enum OccupiedEntryRef<'a, T: BiHashItem> { /// All keys point to the same entry. Unique(&'a T), /// The keys point to different entries, or some keys are not present. /// /// At least one of `by_key1` and `by_key2` is `Some`. NonUnique { /// The value fetched by the first key. by_key1: Option<&'a T>, /// The value fetched by the second key. by_key2: Option<&'a T>, }, } impl<'a, T: BiHashItem> OccupiedEntryRef<'a, T> { /// Returns true if the entry is unique. /// /// Since [`BiHashMap`] is keyed by two keys, it's possible for /// `OccupiedEntry` to match up to two separate items. This function returns /// true if the entry is unique, meaning all keys point to exactly one item. #[inline] pub fn is_unique(&self) -> bool { matches!(self, Self::Unique(_)) } /// Returns true if the `OccupiedEntryRef` represents more than one item, or /// if some keys are not present. #[inline] pub fn is_non_unique(&self) -> bool { matches!(self, Self::NonUnique { .. }) } /// Returns a reference to the value if it is unique. #[inline] pub fn as_unique(&self) -> Option<&'a T> { match self { Self::Unique(v) => Some(v), Self::NonUnique { .. } => None, } } /// Returns a reference to the value fetched by the first key. #[inline] pub fn by_key1(&self) -> Option<&'a T> { match self { Self::Unique(v) => Some(v), Self::NonUnique { by_key1, .. } => *by_key1, } } /// Returns a reference to the value fetched by the second key. #[inline] pub fn by_key2(&self) -> Option<&'a T> { match self { Self::Unique(v) => Some(v), Self::NonUnique { by_key2, .. } => *by_key2, } } } /// A mutable view into an occupied entry in a [`BiHashMap`]. /// /// Returned by [`OccupiedEntry::get_mut`]. pub enum OccupiedEntryMut< 'a, T: BiHashItem, S: Clone + BuildHasher = DefaultHashBuilder, > { /// All keys point to the same entry. Unique(RefMut<'a, T, S>), /// The keys point to different entries, or some keys are not present. NonUnique { /// The value fetched by the first key. by_key1: Option>, /// The value fetched by the second key. by_key2: Option>, }, } impl<'a, T: BiHashItem + fmt::Debug, S: Clone + BuildHasher> fmt::Debug for OccupiedEntryMut<'a, T, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { OccupiedEntryMut::Unique(ref_mut) => { f.debug_tuple("Unique").field(ref_mut).finish() } OccupiedEntryMut::NonUnique { by_key1, by_key2 } => f .debug_struct("NonUnique") .field("by_key1", by_key1) .field("by_key2", by_key2) .finish(), } } } impl<'a, T: BiHashItem, S: Clone + BuildHasher> OccupiedEntryMut<'a, T, S> { /// Returns true if the entry is unique. #[inline] pub fn is_unique(&self) -> bool { matches!(self, Self::Unique(_)) } /// Returns true if the `OccupiedEntryMut` represents more than one item, or /// if some keys are not present. #[inline] pub fn is_non_unique(&self) -> bool { matches!(self, Self::NonUnique { .. }) } /// Returns a reference to the value if it is unique. #[inline] pub fn as_unique(&mut self) -> Option> { match self { Self::Unique(v) => Some(v.reborrow()), Self::NonUnique { .. } => None, } } /// Returns a mutable reference to the value fetched by the first key. #[inline] pub fn by_key1(&mut self) -> Option> { match self { Self::Unique(v) => Some(v.reborrow()), Self::NonUnique { by_key1, .. } => { by_key1.as_mut().map(|v| v.reborrow()) } } } /// Returns a mutable reference to the value fetched by the second key. #[inline] pub fn by_key2(&mut self) -> Option> { match self { Self::Unique(v) => Some(v.reborrow()), Self::NonUnique { by_key2, .. } => { by_key2.as_mut().map(|v| v.reborrow()) } } } /// Calls a callback for each value. pub fn for_each(&mut self, mut f: F) where F: FnMut(RefMut<'_, T, S>), { match self { Self::Unique(v) => f(v.reborrow()), Self::NonUnique { by_key1, by_key2 } => { if let Some(v) = by_key1 { f(v.reborrow()); } if let Some(v) = by_key2 { f(v.reborrow()); } } } } } iddqd-0.3.17/src/bi_hash_map/entry_indexes.rs000064400000000000000000000027071046102023000172560ustar 00000000000000#[derive(Clone, Copy, Debug)] pub(super) enum EntryIndexes { Unique(usize), NonUnique { // Invariant: at least one index is Some, and indexes are different from // each other. index1: Option, index2: Option, }, } impl EntryIndexes { #[inline] pub(super) fn is_unique(&self) -> bool { matches!(self, EntryIndexes::Unique(_)) } #[inline] pub(super) fn disjoint_keys(&self) -> DisjointKeys<'_> { match self { EntryIndexes::Unique(index) => DisjointKeys::Unique(*index), EntryIndexes::NonUnique { index1: Some(index1), index2: Some(index2), } => { debug_assert_ne!( index1, index2, "index1 and index2 shouldn't match" ); DisjointKeys::Key12([index1, index2]) } EntryIndexes::NonUnique { index1: Some(index), index2: None } => { DisjointKeys::Key1(*index) } EntryIndexes::NonUnique { index1: None, index2: Some(index) } => { DisjointKeys::Key2(*index) } EntryIndexes::NonUnique { index1: None, index2: None } => { unreachable!("At least one index must be Some") } } } } pub(super) enum DisjointKeys<'a> { Unique(usize), Key1(usize), Key2(usize), Key12([&'a usize; 2]), } iddqd-0.3.17/src/bi_hash_map/imp.rs000064400000000000000000002327061046102023000151670ustar 00000000000000use super::{ Entry, IntoIter, Iter, IterMut, OccupiedEntry, RefMut, VacantEntry, entry::OccupiedEntryRef, entry_indexes::{DisjointKeys, EntryIndexes}, tables::BiHashMapTables, }; use crate::{ BiHashItem, DefaultHashBuilder, bi_hash_map::entry::OccupiedEntryMut, errors::DuplicateItem, internal::{ValidateCompact, ValidationError}, support::{ alloc::{AllocWrapper, Allocator, Global, global_alloc}, borrow::DormantMutRef, fmt_utils::StrDisplayAsDebug, item_set::ItemSet, map_hash::MapHash, }, }; use alloc::{collections::BTreeSet, vec::Vec}; use core::{ fmt, hash::{BuildHasher, Hash}, }; use equivalent::Equivalent; use hashbrown::hash_table; /// A 1:1 (bijective) map for two keys and a value. /// /// The storage mechanism is a fast hash table of integer indexes to items, with /// these indexes stored in two hash tables. This allows for efficient lookups /// by either of the two keys and prevents duplicates. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// // Define a struct with two keys and a value. /// #[derive(Debug, PartialEq, Eq)] /// struct MyItem { /// id: u32, /// name: &'static str, /// value: i32, /// } /// /// // Implement BiHashItem for the struct. /// impl BiHashItem for MyItem { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// self.name /// } /// /// bi_upcast!(); /// } /// /// // Create a new BiHashMap and insert items. /// let mut map = BiHashMap::new(); /// map.insert_unique(MyItem { id: 1, name: "foo", value: 42 }).unwrap(); /// map.insert_unique(MyItem { id: 2, name: "bar", value: 99 }).unwrap(); /// /// // Look up by the first key. /// assert_eq!(map.get1(&1).unwrap().value, 42); /// assert_eq!(map.get1(&2).unwrap().value, 99); /// assert!(map.get1(&3).is_none()); /// /// // Look up by the second key. /// assert_eq!(map.get2(&"foo").unwrap().value, 42); /// assert_eq!(map.get2(&"bar").unwrap().value, 99); /// assert!(map.get2(&"baz").is_none()); /// # } /// ``` #[derive(Clone)] pub struct BiHashMap { pub(super) items: ItemSet, // Invariant: the values (usize) in these tables are valid indexes into // `items`, and are a 1:1 mapping. pub(super) tables: BiHashMapTables, } impl Default for BiHashMap { fn default() -> Self { Self { items: ItemSet::with_capacity_in(0, A::default()), tables: BiHashMapTables::default(), } } } #[cfg(feature = "default-hasher")] impl BiHashMap { /// Creates a new, empty `BiHashMap`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let map: BiHashMap = BiHashMap::new(); /// assert!(map.is_empty()); /// assert_eq!(map.len(), 0); /// # } /// ``` #[inline] pub fn new() -> Self { Self { items: ItemSet::new(), tables: BiHashMapTables::default() } } /// Creates a new `BiHashMap` with the given capacity. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let map: BiHashMap = BiHashMap::with_capacity(10); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_capacity(capacity: usize) -> Self { Self { items: ItemSet::with_capacity_in(capacity, global_alloc()), tables: BiHashMapTables::with_capacity_and_hasher_in( capacity, DefaultHashBuilder::default(), global_alloc(), ), } } } impl BiHashMap { /// Creates a new `BiHashMap` with the given hasher. /// /// # Examples /// /// ``` /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let hasher = RandomState::new(); /// let map: BiHashMap = BiHashMap::with_hasher(hasher); /// assert!(map.is_empty()); /// ``` pub const fn with_hasher(hasher: S) -> Self { Self { items: ItemSet::new(), tables: BiHashMapTables::with_hasher(hasher), } } /// Creates a new `BiHashMap` with the given capacity and hasher. /// /// # Examples /// /// ``` /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let hasher = RandomState::new(); /// let map: BiHashMap = /// BiHashMap::with_capacity_and_hasher(10, hasher); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// ``` pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self { Self { items: ItemSet::with_capacity_in(capacity, global_alloc()), tables: BiHashMapTables::with_capacity_and_hasher_in( capacity, hasher, global_alloc(), ), } } } #[cfg(feature = "default-hasher")] impl BiHashMap { /// Creates a new empty `BiHashMap` using the given allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] { /// use iddqd::{BiHashMap, BiHashItem, bi_upcast}; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// // Create a new BiHashMap using the allocator. /// let map: BiHashMap = BiHashMap::new_in(&bump); /// assert!(map.is_empty()); /// # } /// ``` pub fn new_in(alloc: A) -> Self { Self { items: ItemSet::with_capacity_in(0, alloc.clone()), tables: BiHashMapTables::with_capacity_and_hasher_in( 0, DefaultHashBuilder::default(), alloc, ), } } /// Creates an empty `BiHashMap` with the specified capacity using the given /// allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] { /// use iddqd::{BiHashMap, BiHashItem, bi_upcast}; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// // Create a new BiHashMap with capacity using the allocator. /// let map: BiHashMap = BiHashMap::with_capacity_in(10, &bump); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_capacity_in(capacity: usize, alloc: A) -> Self { Self { items: ItemSet::with_capacity_in(capacity, alloc.clone()), tables: BiHashMapTables::with_capacity_and_hasher_in( capacity, DefaultHashBuilder::default(), alloc, ), } } } impl BiHashMap { /// Creates a new, empty `BiHashMap` with the given hasher and allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(feature = "allocator-api2")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// use std::collections::hash_map::RandomState; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// let hasher = RandomState::new(); /// // Create a new BiHashMap with hasher using the allocator. /// let map: BiHashMap = /// BiHashMap::with_hasher_in(hasher, &bump); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_hasher_in(hasher: S, alloc: A) -> Self { Self { items: ItemSet::with_capacity_in(0, alloc.clone()), tables: BiHashMapTables::with_capacity_and_hasher_in( 0, hasher, alloc, ), } } /// Creates a new, empty `BiHashMap` with the given capacity, hasher, and /// allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(feature = "allocator-api2")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// use std::collections::hash_map::RandomState; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// let hasher = RandomState::new(); /// // Create a new BiHashMap with capacity and hasher using the allocator. /// let map: BiHashMap = /// BiHashMap::with_capacity_and_hasher_in(10, hasher, &bump); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_capacity_and_hasher_in( capacity: usize, hasher: S, alloc: A, ) -> Self { Self { items: ItemSet::with_capacity_in(capacity, alloc.clone()), tables: BiHashMapTables::with_capacity_and_hasher_in( capacity, hasher, alloc, ), } } } impl BiHashMap { /// Returns the hasher. #[cfg(feature = "daft")] #[inline] pub(crate) fn hasher(&self) -> &S { self.tables.hasher() } /// Returns the allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] { /// use iddqd::{BiHashMap, BiHashItem, bi_upcast}; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// // Create a new BiHashMap using the allocator. /// let map: BiHashMap = BiHashMap::new_in(&bump); /// let _allocator = map.allocator(); /// # } /// ``` #[inline] pub fn allocator(&self) -> &A { self.items.allocator() } /// Returns the currently allocated capacity of the map. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let map: BiHashMap = BiHashMap::with_capacity(10); /// assert!(map.capacity() >= 10); /// /// let empty_map: BiHashMap = BiHashMap::new(); /// assert!(empty_map.capacity() >= 0); /// # } /// ``` pub fn capacity(&self) -> usize { // items and tables.capacity might theoretically diverge: use // items.capacity. self.items.capacity() } /// Returns true if the map contains no items. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// assert!(map.is_empty()); /// /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// assert!(!map.is_empty()); /// # } /// ``` #[inline] pub fn is_empty(&self) -> bool { self.items.is_empty() } /// Returns the number of items in the map. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// assert_eq!(map.len(), 0); /// /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// assert_eq!(map.len(), 2); /// # } /// ``` #[inline] pub fn len(&self) -> usize { self.items.len() } /// Clears the map, removing all items. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// assert_eq!(map.len(), 2); /// /// map.clear(); /// assert!(map.is_empty()); /// assert_eq!(map.len(), 0); /// # } /// ``` pub fn clear(&mut self) { self.items.clear(); self.tables.k1_to_item.clear(); self.tables.k2_to_item.clear(); } /// Reserves capacity for at least `additional` more elements to be inserted /// in the `BiHashMap`. The collection may reserve more space to /// speculatively avoid frequent reallocations. After calling `reserve`, /// capacity will be greater than or equal to `self.len() + additional`. /// Does nothing if capacity is already sufficient. /// /// # Panics /// /// Panics if the new capacity overflows [`isize::MAX`] bytes, and /// [`abort`]s the program in case of an allocation error. Use /// [`try_reserve`](Self::try_reserve) instead if you want to handle memory /// allocation failure. /// /// [`isize::MAX`]: https://doc.rust-lang.org/std/primitive.isize.html /// [`abort`]: https://doc.rust-lang.org/alloc/alloc/fn.handle_alloc_error.html /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: u32, /// name: String, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map: BiHashMap = BiHashMap::new(); /// map.reserve(100); /// assert!(map.capacity() >= 100); /// # } /// ``` pub fn reserve(&mut self, additional: usize) { self.items.reserve(additional); self.tables.k1_to_item.reserve(additional); self.tables.k2_to_item.reserve(additional); } /// Tries to reserve capacity for at least `additional` more elements to be /// inserted in the `BiHashMap`. The collection may reserve more space to /// speculatively avoid frequent reallocations. After calling `try_reserve`, /// capacity will be greater than or equal to `self.len() + additional` if /// it returns `Ok(())`. Does nothing if capacity is already sufficient. /// /// # Errors /// /// If the capacity overflows, or the allocator reports a failure, then an /// error is returned. /// /// # Notes /// /// If reservation fails partway through, some internal structures may have /// already increased their capacity. The map remains in a valid state but /// may have uneven capacities across its internal structures. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: u32, /// name: String, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map: BiHashMap = BiHashMap::new(); /// map.try_reserve(100).expect("allocation should succeed"); /// assert!(map.capacity() >= 100); /// # } /// ``` pub fn try_reserve( &mut self, additional: usize, ) -> Result<(), crate::errors::TryReserveError> { self.items .try_reserve(additional) .map_err(crate::errors::TryReserveError::from_hashbrown)?; self.tables .k1_to_item .try_reserve(additional) .map_err(crate::errors::TryReserveError::from_hashbrown)?; self.tables .k2_to_item .try_reserve(additional) .map_err(crate::errors::TryReserveError::from_hashbrown)?; Ok(()) } /// Shrinks the capacity of the map as much as possible. It will drop /// down as much as possible while maintaining the internal rules /// and possibly leaving some space in accordance with the resize policy. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: u32, /// name: String, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map: BiHashMap = BiHashMap::with_capacity(100); /// map.insert_unique(Item { id: 1, name: "foo".to_string() }).unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string() }).unwrap(); /// assert!(map.capacity() >= 100); /// map.shrink_to_fit(); /// assert!(map.capacity() >= 2); /// # } /// ``` pub fn shrink_to_fit(&mut self) { self.items.shrink_to_fit(); self.tables.k1_to_item.shrink_to_fit(); self.tables.k2_to_item.shrink_to_fit(); } /// Shrinks the capacity of the map with a lower limit. It will drop /// down no lower than the supplied limit while maintaining the internal /// rules and possibly leaving some space in accordance with the resize /// policy. /// /// If the current capacity is less than the lower limit, this is a no-op. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: u32, /// name: String, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map: BiHashMap = BiHashMap::with_capacity(100); /// map.insert_unique(Item { id: 1, name: "foo".to_string() }).unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string() }).unwrap(); /// assert!(map.capacity() >= 100); /// map.shrink_to(10); /// assert!(map.capacity() >= 10); /// map.shrink_to(0); /// assert!(map.capacity() >= 2); /// # } /// ``` pub fn shrink_to(&mut self, min_capacity: usize) { self.items.shrink_to(min_capacity); self.tables.k1_to_item.shrink_to(min_capacity); self.tables.k2_to_item.shrink_to(min_capacity); } /// Returns an iterator over all items in the map. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not /// guaranteed to be stable. /// /// [`HashMap`]: std::collections::HashMap /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// /// let mut values: Vec = map.iter().map(|item| item.value).collect(); /// values.sort(); /// assert_eq!(values, vec![42, 99]); /// # } /// ``` #[inline] pub fn iter(&self) -> Iter<'_, T> { Iter::new(&self.items) } /// Iterates over the items in the map, allowing for mutation. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not /// guaranteed to be stable. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// /// for mut item in map.iter_mut() { /// item.value += 10; /// } /// /// assert_eq!(map.get1(&1).unwrap().value, 52); /// assert_eq!(map.get1(&2).unwrap().value, 109); /// # } /// ``` /// /// [`HashMap`]: std::collections::HashMap #[inline] pub fn iter_mut(&mut self) -> IterMut<'_, T, S, A> { IterMut::new(&self.tables, &mut self.items) } /// Checks general invariants of the map. /// /// The code below always upholds these invariants, but it's useful to have /// an explicit check for tests. #[doc(hidden)] pub fn validate( &self, compactness: ValidateCompact, ) -> Result<(), ValidationError> where T: fmt::Debug, { self.items.validate(compactness)?; self.tables.validate(self.len(), compactness)?; // Check that the indexes are all correct. for (&ix, item) in self.items.iter() { let key1 = item.key1(); let key2 = item.key2(); let Some(ix1) = self.find1_index(&key1) else { return Err(ValidationError::general(format!( "item at index {ix} has no key1 index" ))); }; let Some(ix2) = self.find2_index(&key2) else { return Err(ValidationError::general(format!( "item at index {ix} has no key2 index" ))); }; if ix1 != ix || ix2 != ix { return Err(ValidationError::general(format!( "item at index {ix} has inconsistent indexes: {ix1}/{ix2}" ))); } } Ok(()) } /// Inserts a value into the map, removing any conflicting items and /// returning a list of those items. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// /// // Insert an item with conflicting key1 /// let removed = map.insert_overwrite(Item { /// id: 1, /// name: "baz".to_string(), /// value: 100, /// }); /// assert_eq!(removed.len(), 1); /// assert_eq!(removed[0].name, "foo"); /// assert_eq!(removed[0].value, 42); /// /// assert_eq!(map.len(), 2); /// assert_eq!(map.get1(&1).unwrap().name, "baz"); /// # } /// ``` #[doc(alias = "insert")] pub fn insert_overwrite(&mut self, value: T) -> Vec { // Trying to write this function for maximal efficiency can get very // tricky, requiring delicate handling of indexes. We follow a very // simple approach instead: // // 1. Remove items corresponding to keys that are already in the map. // 2. Add the item to the map. let mut duplicates = Vec::new(); duplicates.extend(self.remove1(&value.key1())); duplicates.extend(self.remove2(&value.key2())); if self.insert_unique(value).is_err() { // We should never get here, because we just removed all the // duplicates. panic!("insert_unique failed after removing duplicates"); } duplicates } /// Inserts a value into the set, returning an error if any duplicates were /// added. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// /// // Successful insertion /// assert!( /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .is_ok() /// ); /// assert!( /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .is_ok() /// ); /// /// // Duplicate key1 /// assert!( /// map.insert_unique(Item { id: 1, name: "baz".to_string(), value: 100 }) /// .is_err() /// ); /// /// // Duplicate key2 /// assert!( /// map.insert_unique(Item { id: 3, name: "foo".to_string(), value: 200 }) /// .is_err() /// ); /// # } /// ``` pub fn insert_unique( &mut self, value: T, ) -> Result<(), DuplicateItem> { let _ = self.insert_unique_impl(value)?; Ok(()) } /// Returns true if the map contains a single item that matches both `key1` and `key2`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }).unwrap(); /// /// assert!(map.contains_key_unique(&1, &"foo")); /// assert!(map.contains_key_unique(&2, &"bar")); /// assert!(!map.contains_key_unique(&1, &"bar")); // key1 exists but key2 doesn't match /// assert!(!map.contains_key_unique(&3, &"baz")); // neither key exists /// # } /// ``` pub fn contains_key_unique<'a, Q1, Q2>( &'a self, key1: &Q1, key2: &Q2, ) -> bool where Q1: Hash + Equivalent> + ?Sized, Q2: Hash + Equivalent> + ?Sized, { self.get_unique(key1, key2).is_some() } /// Gets a reference to the unique item associated with the given `key1` and /// `key2`, if it exists. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }).unwrap(); /// /// assert_eq!(map.get_unique(&1, &"foo").unwrap().value, 42); /// assert_eq!(map.get_unique(&2, &"bar").unwrap().value, 99); /// assert!(map.get_unique(&1, &"bar").is_none()); // key1 exists but key2 doesn't match /// assert!(map.get_unique(&3, &"baz").is_none()); // neither key exists /// # } /// ``` pub fn get_unique<'a, Q1, Q2>( &'a self, key1: &Q1, key2: &Q2, ) -> Option<&'a T> where Q1: Hash + Equivalent> + ?Sized, Q2: Hash + Equivalent> + ?Sized, { let index = self.find1_index(key1)?; let item = &self.items[index]; if key2.equivalent(&item.key2()) { Some(item) } else { None } } /// Gets a mutable reference to the unique item associated with the given /// `key1` and `key2`, if it exists. pub fn get_mut_unique<'a, Q1, Q2>( &'a mut self, key1: &Q1, key2: &Q2, ) -> Option> where Q1: Hash + Equivalent> + ?Sized, Q2: Hash + Equivalent> + ?Sized, { let (dormant_map, index) = { let (map, dormant_map) = DormantMutRef::new(self); let index = map.find1_index(key1)?; // Check key2 match before proceeding if !key2.equivalent(&map.items[index].key2()) { return None; } (dormant_map, index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; let item = &mut awakened_map.items[index]; let state = awakened_map.tables.state.clone(); let hashes = awakened_map.tables.make_hashes::(&item.key1(), &item.key2()); Some(RefMut::new(state, hashes, item)) } /// Removes the item uniquely identified by `key1` and `key2`, if it exists. pub fn remove_unique<'a, Q1, Q2>( &'a mut self, key1: &Q1, key2: &Q2, ) -> Option where Q1: Hash + Equivalent> + ?Sized, Q2: Hash + Equivalent> + ?Sized, { let (dormant_map, remove_index) = { let (map, dormant_map) = DormantMutRef::new(self); let remove_index = map.find1_index(key1)?; if !key2.equivalent(&map.items[remove_index].key2()) { return None; } (dormant_map, remove_index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; awakened_map.remove_by_index(remove_index) } /// Returns true if the map contains the given `key1`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// /// assert!(map.contains_key1(&1)); /// assert!(map.contains_key1(&2)); /// assert!(!map.contains_key1(&3)); /// # } /// ``` pub fn contains_key1<'a, Q>(&'a self, key1: &Q) -> bool where Q: Hash + Equivalent> + ?Sized, { self.find1_index(key1).is_some() } /// Gets a reference to the value associated with the given `key1`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// /// assert_eq!(map.get1(&1).unwrap().value, 42); /// assert_eq!(map.get1(&2).unwrap().value, 99); /// assert!(map.get1(&3).is_none()); /// # } /// ``` pub fn get1<'a, Q>(&'a self, key1: &Q) -> Option<&'a T> where Q: Hash + Equivalent> + ?Sized, { self.find1(key1) } /// Gets a mutable reference to the value associated with the given `key1`. pub fn get1_mut<'a, Q>(&'a mut self, key1: &Q) -> Option> where Q: Hash + Equivalent> + ?Sized, { let (dormant_map, index) = { let (map, dormant_map) = DormantMutRef::new(self); let index = map.find1_index(key1)?; (dormant_map, index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; let item = &mut awakened_map.items[index]; let state = awakened_map.tables.state.clone(); let hashes = awakened_map.tables.make_hashes::(&item.key1(), &item.key2()); Some(RefMut::new(state, hashes, item)) } /// Removes an item from the map by its `key1`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// /// let removed = map.remove1(&1); /// assert_eq!(removed.unwrap().value, 42); /// assert_eq!(map.len(), 1); /// assert!(map.get1(&1).is_none()); /// assert!(map.remove1(&3).is_none()); /// # } /// ``` pub fn remove1<'a, Q>(&'a mut self, key1: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { let (dormant_map, remove_index) = { let (map, dormant_map) = DormantMutRef::new(self); let remove_index = map.find1_index(key1)?; (dormant_map, remove_index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; awakened_map.remove_by_index(remove_index) } /// Returns true if the map contains the given `key2`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// /// assert!(map.contains_key2(&"foo")); /// assert!(map.contains_key2(&"bar")); /// assert!(!map.contains_key2(&"baz")); /// # } /// ``` pub fn contains_key2<'a, Q>(&'a self, key2: &Q) -> bool where Q: Hash + Equivalent> + ?Sized, { self.find2_index(key2).is_some() } /// Gets a reference to the value associated with the given `key2`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// /// assert_eq!(map.get2(&"foo").unwrap().value, 42); /// assert_eq!(map.get2(&"bar").unwrap().value, 99); /// assert!(map.get2(&"baz").is_none()); /// # } /// ``` pub fn get2<'a, Q>(&'a self, key2: &Q) -> Option<&'a T> where Q: Hash + Equivalent> + ?Sized, { self.find2(key2) } /// Gets a mutable reference to the value associated with the given `key2`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// /// if let Some(mut item_ref) = map.get2_mut(&"foo") { /// item_ref.value = 100; /// } /// /// assert_eq!(map.get2(&"foo").unwrap().value, 100); /// # } /// ``` pub fn get2_mut<'a, Q>(&'a mut self, key2: &Q) -> Option> where Q: Hash + Equivalent> + ?Sized, { let (dormant_map, index) = { let (map, dormant_map) = DormantMutRef::new(self); let index = map.find2_index(key2)?; (dormant_map, index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; let item = &mut awakened_map.items[index]; let state = awakened_map.tables.state.clone(); let hashes = awakened_map.tables.make_hashes::(&item.key1(), &item.key2()); Some(RefMut::new(state, hashes, item)) } /// Removes an item from the map by its `key2`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// /// let removed = map.remove2(&"foo"); /// assert_eq!(removed.unwrap().value, 42); /// assert_eq!(map.len(), 1); /// assert!(map.get2(&"foo").is_none()); /// assert!(map.remove2(&"baz").is_none()); /// # } /// ``` pub fn remove2<'a, Q>(&'a mut self, key2: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { let (dormant_map, remove_index) = { let (map, dormant_map) = DormantMutRef::new(self); let remove_index = map.find2_index(key2)?; (dormant_map, remove_index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; awakened_map.remove_by_index(remove_index) } /// Retrieves an entry by its keys. /// /// Due to borrow checker limitations, this always accepts owned keys rather /// than a borrowed form of them. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_hash_map, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// /// // Get existing entry /// match map.entry(1, "foo") { /// bi_hash_map::Entry::Occupied(entry) => { /// assert_eq!(entry.get().as_unique().unwrap().value, 42); /// } /// bi_hash_map::Entry::Vacant(_) => panic!("Should be occupied"), /// } /// /// // Try to get a non-existing entry /// match map.entry(2, "bar") { /// bi_hash_map::Entry::Occupied(_) => panic!("Should be vacant"), /// bi_hash_map::Entry::Vacant(entry) => { /// entry.insert(Item { id: 2, name: "bar".to_string(), value: 99 }); /// } /// } /// /// assert_eq!(map.len(), 2); /// # } /// ``` pub fn entry<'a>( &'a mut self, key1: T::K1<'_>, key2: T::K2<'_>, ) -> Entry<'a, T, S, A> { // Why does this always take owned keys? Well, it would seem like we // should be able to pass in any Q1 and Q2 that are equivalent. That // results in *this* code compiling fine, but callers have trouble using // it because the borrow checker believes the keys are borrowed for the // full 'a rather than a shorter lifetime. // // By accepting owned keys, we can use the upcast functions to convert // them to a shorter lifetime (so this function accepts T::K1<'_> rather // than T::K1<'a>). // // Really, the solution here is to allow GATs to require covariant // parameters. If that were allowed, the borrow checker should be able // to figure out that keys don't need to be borrowed for the full 'a, // just for some shorter lifetime. let (map, dormant_map) = DormantMutRef::new(self); let key1 = T::upcast_key1(key1); let key2 = T::upcast_key2(key2); let (index1, index2) = { // index1 and index2 are explicitly typed to show that it has a // trivial Drop impl that doesn't capture anything from map. let index1: Option = map.tables.k1_to_item.find_index( &map.tables.state, &key1, |index| map.items[index].key1(), ); let index2: Option = map.tables.k2_to_item.find_index( &map.tables.state, &key2, |index| map.items[index].key2(), ); (index1, index2) }; match (index1, index2) { (Some(index1), Some(index2)) if index1 == index2 => { // The item is already in the map. drop(key1); Entry::Occupied( // SAFETY: `map` is not used after this point. unsafe { OccupiedEntry::new( dormant_map, EntryIndexes::Unique(index1), ) }, ) } (None, None) => { let hashes = map.tables.make_hashes::(&key1, &key2); Entry::Vacant( // SAFETY: `map` is not used after this point. unsafe { VacantEntry::new(dormant_map, hashes) }, ) } (index1, index2) => Entry::Occupied( // SAFETY: `map` is not used after this point. unsafe { OccupiedEntry::new( dormant_map, EntryIndexes::NonUnique { index1, index2 }, ) }, ), } } /// Retains only the elements specified by the predicate. /// /// In other words, remove all items `T` for which `f(RefMut)` returns /// false. The elements are visited in an arbitrary order. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: u32, /// name: String, /// value: u32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map.insert_unique(Item { id: 2, name: "bar".to_string(), value: 20 }) /// .unwrap(); /// map.insert_unique(Item { id: 3, name: "baz".to_string(), value: 99 }) /// .unwrap(); /// /// // Retain only items where value is greater than 30 /// map.retain(|item| item.value > 30); /// /// assert_eq!(map.len(), 2); /// assert_eq!(map.get1(&1).unwrap().value, 42); /// assert_eq!(map.get1(&3).unwrap().value, 99); /// assert!(map.get1(&2).is_none()); /// # } /// ``` pub fn retain<'a, F>(&'a mut self, mut f: F) where F: FnMut(RefMut<'a, T, S>) -> bool, { let hash_state = self.tables.state.clone(); let (_, mut dormant_items) = DormantMutRef::new(&mut self.items); self.tables.k1_to_item.retain(|index| { let (item, dormant_items) = { // SAFETY: All uses of `items` ended in the previous iteration. let items = unsafe { dormant_items.reborrow() }; let (items, dormant_items) = DormantMutRef::new(items); let item: &'a mut T = items .get_mut(index) .expect("all indexes are present in self.items"); (item, dormant_items) }; let (hashes, dormant_item) = { let (item, dormant_item): (&'a mut T, _) = DormantMutRef::new(item); // Use T::k1(item) rather than item.key() to force the key // trait function to be called for T rather than &mut T. let key1 = T::key1(item); let key2 = T::key2(item); let hash1 = hash_state.hash_one(key1); let hash2 = hash_state.hash_one(key2); ([MapHash::new(hash1), MapHash::new(hash2)], dormant_item) }; let hash2 = hashes[1].hash(); let retain = { // SAFETY: The original item is no longer used after the second // block above. dormant_items, from which item is derived, is // currently dormant. let item = unsafe { dormant_item.awaken() }; let ref_mut = RefMut::new(hash_state.clone(), hashes, item); f(ref_mut) }; if retain { true } else { // SAFETY: The original items is no longer used after the first // block above, and item + dormant_item have been dropped after // being used above. let items = unsafe { dormant_items.awaken() }; items.remove(index); let k2_entry = self .tables .k2_to_item .find_entry_by_hash(hash2, |map2_index| { map2_index == index }); match k2_entry { Ok(entry) => { entry.remove(); } Err(_) => { // This happening means there's an inconsistency between // the maps. panic!( "inconsistency between k1_to_item and k2_to_item" ); } } false } }); } fn find1<'a, Q>(&'a self, k: &Q) -> Option<&'a T> where Q: Hash + Equivalent> + ?Sized, { self.find1_index(k).map(|ix| &self.items[ix]) } fn find1_index<'a, Q>(&'a self, k: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { self.tables .k1_to_item .find_index(&self.tables.state, k, |index| self.items[index].key1()) } fn find2<'a, Q>(&'a self, k: &Q) -> Option<&'a T> where Q: Hash + Equivalent> + ?Sized, { self.find2_index(k).map(|ix| &self.items[ix]) } fn find2_index<'a, Q>(&'a self, k: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { self.tables .k2_to_item .find_index(&self.tables.state, k, |index| self.items[index].key2()) } pub(super) fn get_by_entry_index( &self, indexes: EntryIndexes, ) -> OccupiedEntryRef<'_, T> { match indexes { EntryIndexes::Unique(index) => OccupiedEntryRef::Unique( self.items.get(index).expect("index is valid"), ), EntryIndexes::NonUnique { index1, index2 } => { let by_key1 = index1 .map(|k| self.items.get(k).expect("key1 index is valid")); let by_key2 = index2 .map(|k| self.items.get(k).expect("key2 index is valid")); OccupiedEntryRef::NonUnique { by_key1, by_key2 } } } } pub(super) fn get_by_entry_index_mut( &mut self, indexes: EntryIndexes, ) -> OccupiedEntryMut<'_, T, S> { match indexes.disjoint_keys() { DisjointKeys::Unique(index) => { let item = self.items.get_mut(index).expect("index is valid"); let state = self.tables.state.clone(); let hashes = self.tables.make_hashes::(&item.key1(), &item.key2()); OccupiedEntryMut::Unique(RefMut::new(state, hashes, item)) } DisjointKeys::Key1(index1) => { let item = self.items.get_mut(index1).expect("key1 index is valid"); let state = self.tables.state.clone(); let hashes = self.tables.make_hashes::(&item.key1(), &item.key2()); OccupiedEntryMut::NonUnique { by_key1: Some(RefMut::new(state, hashes, item)), by_key2: None, } } DisjointKeys::Key2(index2) => { let item = self.items.get_mut(index2).expect("key2 index is valid"); let state = self.tables.state.clone(); let hashes = self.tables.make_hashes::(&item.key1(), &item.key2()); OccupiedEntryMut::NonUnique { by_key1: None, by_key2: Some(RefMut::new(state, hashes, item)), } } DisjointKeys::Key12(indexes) => { let state = self.tables.state.clone(); let mut items = self.items.get_disjoint_mut(indexes); let item1 = items[0].take().expect("key1 index is valid"); let item2 = items[1].take().expect("key2 index is valid"); let hashes1 = self.tables.make_hashes::(&item1.key1(), &item1.key2()); let hashes2 = self.tables.make_hashes::(&item2.key1(), &item2.key2()); OccupiedEntryMut::NonUnique { by_key1: Some(RefMut::new(state.clone(), hashes1, item1)), by_key2: Some(RefMut::new(state, hashes2, item2)), } } } } pub(super) fn get_by_index_mut( &mut self, index: usize, ) -> Option> { let borrowed = self.items.get_mut(index)?; let state = self.tables.state.clone(); let hashes = self.tables.make_hashes::(&borrowed.key1(), &borrowed.key2()); let item = &mut self.items[index]; Some(RefMut::new(state, hashes, item)) } pub(super) fn insert_unique_impl( &mut self, value: T, ) -> Result> { let mut duplicates = BTreeSet::new(); // Check for duplicates *before* inserting the new item, because we // don't want to partially insert the new item and then have to roll // back. let state = &self.tables.state; let (e1, e2) = { let k1 = value.key1(); let k2 = value.key2(); let e1 = detect_dup_or_insert( self.tables .k1_to_item .entry(state, k1, |index| self.items[index].key1()), &mut duplicates, ); let e2 = detect_dup_or_insert( self.tables .k2_to_item .entry(state, k2, |index| self.items[index].key2()), &mut duplicates, ); (e1, e2) }; if !duplicates.is_empty() { return Err(DuplicateItem::__internal_new( value, duplicates.iter().map(|ix| &self.items[*ix]).collect(), )); } let next_index = self.items.insert_at_next_index(value); // e1 and e2 are all Some because if they were None, duplicates // would be non-empty, and we'd have bailed out earlier. e1.unwrap().insert(next_index); e2.unwrap().insert(next_index); Ok(next_index) } pub(super) fn remove_by_entry_index( &mut self, indexes: EntryIndexes, ) -> Vec { match indexes { EntryIndexes::Unique(index) => { // Since all keys match, we can simply replace the item. let old_item = self.remove_by_index(index).expect("index is valid"); vec![old_item] } EntryIndexes::NonUnique { index1, index2 } => { let mut old_items = Vec::new(); if let Some(index1) = index1 { old_items.push( self.remove_by_index(index1).expect("index1 is valid"), ); } if let Some(index2) = index2 { old_items.push( self.remove_by_index(index2).expect("index2 is valid"), ); } old_items } } } pub(super) fn remove_by_index(&mut self, remove_index: usize) -> Option { let value = self.items.remove(remove_index)?; // Remove the value from the tables. let state = &self.tables.state; let Ok(item1) = self.tables.k1_to_item.find_entry(state, &value.key1(), |index| { if index == remove_index { value.key1() } else { self.items[index].key1() } }) else { // The item was not found. panic!("remove_index {remove_index} not found in k1_to_item"); }; let Ok(item2) = self.tables.k2_to_item.find_entry(state, &value.key2(), |index| { if index == remove_index { value.key2() } else { self.items[index].key2() } }) else { // The item was not found. panic!("remove_index {remove_index} not found in k2_to_item") }; item1.remove(); item2.remove(); Some(value) } pub(super) fn replace_at_indexes( &mut self, indexes: EntryIndexes, value: T, ) -> (usize, Vec) { match indexes { EntryIndexes::Unique(index) => { let old_item = &self.items[index]; if old_item.key1() != value.key1() { panic!("key1 mismatch"); } if old_item.key2() != value.key2() { panic!("key2 mismatch"); } // Since all keys match, we can simply replace the item. let old_item = self.items.replace(index, value); (index, vec![old_item]) } EntryIndexes::NonUnique { index1, index2 } => { let mut old_items = Vec::new(); if let Some(index1) = index1 { let old_item = &self.items[index1]; if old_item.key1() != value.key1() { panic!("key1 mismatch"); } old_items.push(self.remove_by_index(index1).unwrap()); } if let Some(index2) = index2 { let old_item = &self.items[index2]; if old_item.key2() != value.key2() { panic!("key2 mismatch"); } old_items.push(self.remove_by_index(index2).unwrap()); } // Insert the new item. let Ok(next_index) = self.insert_unique_impl(value) else { unreachable!( "insert_unique cannot fail after removing duplicates" ); }; (next_index, old_items) } } } } impl<'a, T, S, A> fmt::Debug for BiHashMap where T: BiHashItem + fmt::Debug, T::K1<'a>: fmt::Debug, T::K2<'a>: fmt::Debug, T: 'a, A: Allocator, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut map = f.debug_map(); for item in self.items.values() { let key: KeyMap<'_, T> = KeyMap { key1: item.key1(), key2: item.key2() }; // SAFETY: // // * Lifetime extension: for a type T and two lifetime params 'a and // 'b, T<'a> and T<'b> aren't guaranteed to have the same layout, // but (a) that is true today and (b) it would be shocking and // break half the Rust ecosystem if that were to change in the // future. // * We only use key within the scope of this block before immediately // dropping it. In particular, map.entry calls key.fmt() without // holding a reference to it. let key: KeyMap<'a, T> = unsafe { core::mem::transmute::, KeyMap<'a, T>>(key) }; map.entry(&key as &dyn fmt::Debug, item); } map.finish() } } struct KeyMap<'a, T: BiHashItem + 'a> { key1: T::K1<'a>, key2: T::K2<'a>, } impl<'a, T: BiHashItem + 'a> fmt::Debug for KeyMap<'a, T> where T::K1<'a>: fmt::Debug, T::K2<'a>: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // We don't want to show key1 and key2 as a tuple since it's // misleading (suggests maps of tuples). The best we can do // instead is to show "{k1: "abc", k2: "xyz"}" f.debug_map() .entry(&StrDisplayAsDebug("k1"), &self.key1) .entry(&StrDisplayAsDebug("k2"), &self.key2) .finish() } } /// The `PartialEq` implementation for `BiHashMap` checks that both maps have /// the same items, regardless of insertion order. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map1 = BiHashMap::new(); /// map1.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// map1.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// /// let mut map2 = BiHashMap::new(); /// map2.insert_unique(Item { id: 2, name: "bar".to_string(), value: 99 }) /// .unwrap(); /// map2.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }) /// .unwrap(); /// /// // Maps are equal even if items were inserted in different order /// assert_eq!(map1, map2); /// /// map2.insert_unique(Item { id: 3, name: "baz".to_string(), value: 200 }) /// .unwrap(); /// assert_ne!(map1, map2); /// # } /// ``` impl PartialEq for BiHashMap { fn eq(&self, other: &Self) -> bool { // Implementing PartialEq for BiHashMap is tricky because BiHashMap is // not semantically like an IndexMap: two maps are equivalent even if // their items are in a different order. In other words, any permutation // of items is equivalent. // // We also can't sort the items because they're not necessarily Ord. // // So we write a custom equality check that checks that each key in one // map points to the same item as in the other map. if self.items.len() != other.items.len() { return false; } // Walk over all the items in the first map and check that they point to // the same item in the second map. for item in self.items.values() { let k1 = item.key1(); let k2 = item.key2(); // Check that the indexes are the same in the other map. let Some(other_ix1) = other.find1_index(&k1) else { return false; }; let Some(other_ix2) = other.find2_index(&k2) else { return false; }; if other_ix1 != other_ix2 { // All the keys were present but they didn't point to the same // item. return false; } // Check that the other map's item is the same as this map's // item. (This is what we use the `PartialEq` bound on T for.) // // Because we've checked that other_ix1 and other_ix2 are // Some, we know that it is valid and points to the expected item. let other_item = &other.items[other_ix1]; if item != other_item { return false; } } true } } // The Eq bound on T ensures that the BiHashMap forms an equivalence class. impl Eq for BiHashMap { } fn detect_dup_or_insert<'a, A: Allocator>( item: hash_table::Entry<'a, usize, AllocWrapper>, duplicates: &mut BTreeSet, ) -> Option>> { match item { hash_table::Entry::Vacant(slot) => Some(slot), hash_table::Entry::Occupied(slot) => { duplicates.insert(*slot.get()); None } } } /// The `Extend` implementation overwrites duplicates. In the future, there will /// also be an `extend_unique` method that will return an error. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::new(); /// map.insert_unique(Item { id: 1, name: "foo".to_string(), value: 42 }).unwrap(); /// /// let new_items = vec![ /// Item { id: 2, name: "bar".to_string(), value: 99 }, /// Item { id: 1, name: "baz".to_string(), value: 100 }, // overwrites existing /// ]; /// /// map.extend(new_items); /// assert_eq!(map.len(), 2); /// assert_eq!(map.get1(&1).unwrap().name, "baz"); // overwritten /// assert_eq!(map.get1(&1).unwrap().value, 100); /// # } /// ``` impl Extend for BiHashMap { fn extend>(&mut self, iter: I) { // Keys may already be present in the map, or multiple times in the // iterator. Reserve the entire hint lower bound if the map is empty. // Otherwise reserve half the hint (rounded up), so the map will only // resize twice in the worst case. let iter = iter.into_iter(); let reserve = if self.is_empty() { iter.size_hint().0 } else { iter.size_hint().0.div_ceil(2) }; self.reserve(reserve); for item in iter { self.insert_overwrite(item); } } } impl<'a, T: BiHashItem, S: Clone + BuildHasher, A: Allocator> IntoIterator for &'a BiHashMap { type Item = &'a T; type IntoIter = Iter<'a, T>; #[inline] fn into_iter(self) -> Self::IntoIter { self.iter() } } impl<'a, T: BiHashItem, S: Clone + BuildHasher, A: Allocator> IntoIterator for &'a mut BiHashMap { type Item = RefMut<'a, T, S>; type IntoIter = IterMut<'a, T, S, A>; #[inline] fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl IntoIterator for BiHashMap { type Item = T; type IntoIter = IntoIter; #[inline] fn into_iter(self) -> Self::IntoIter { IntoIter::new(self.items) } } /// The `FromIterator` implementation for `BiHashMap` overwrites duplicate /// items. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Item { /// id: u32, /// name: String, /// value: i32, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let items = vec![ /// Item { id: 1, name: "foo".to_string(), value: 42 }, /// Item { id: 2, name: "bar".to_string(), value: 99 }, /// Item { id: 1, name: "baz".to_string(), value: 100 }, // overwrites first item /// ]; /// /// let map: BiHashMap = items.into_iter().collect(); /// assert_eq!(map.len(), 2); /// assert_eq!(map.get1(&1).unwrap().name, "baz"); // overwritten /// assert_eq!(map.get1(&1).unwrap().value, 100); /// assert_eq!(map.get1(&2).unwrap().value, 99); /// # } /// ``` impl FromIterator for BiHashMap { fn from_iter>(iter: I) -> Self { let mut map = BiHashMap::default(); for item in iter { map.insert_overwrite(item); } map } } iddqd-0.3.17/src/bi_hash_map/iter.rs000064400000000000000000000077271046102023000153500ustar 00000000000000use super::{RefMut, tables::BiHashMapTables}; use crate::{ BiHashItem, DefaultHashBuilder, support::{ alloc::{AllocWrapper, Allocator, Global}, item_set::ItemSet, }, }; use core::{hash::BuildHasher, iter::FusedIterator}; use hashbrown::hash_map; /// An iterator over the elements of a [`BiHashMap`] by shared reference. /// Created by [`BiHashMap::iter`]. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed /// to be stable. /// /// [`BiHashMap`]: crate::BiHashMap /// [`BiHashMap::iter`]: crate::BiHashMap::iter /// [`HashMap`]: std::collections::HashMap #[derive(Clone, Debug, Default)] pub struct Iter<'a, T: BiHashItem> { inner: hash_map::Values<'a, usize, T>, } impl<'a, T: BiHashItem> Iter<'a, T> { pub(crate) fn new(items: &'a ItemSet) -> Self { Self { inner: items.values() } } } impl<'a, T: BiHashItem> Iterator for Iter<'a, T> { type Item = &'a T; #[inline] fn next(&mut self) -> Option { self.inner.next() } } impl ExactSizeIterator for Iter<'_, T> { #[inline] fn len(&self) -> usize { self.inner.len() } } // hash_map::Iter is a FusedIterator, so Iter is as well. impl FusedIterator for Iter<'_, T> {} /// An iterator over the elements of a [`BiHashMap`] by mutable reference. /// Created by [`BiHashMap::iter_mut`]. /// /// This iterator returns [`RefMut`] instances. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed /// to be stable. /// /// [`BiHashMap`]: crate::BiHashMap /// [`BiHashMap::iter_mut`]: crate::BiHashMap::iter_mut /// [`HashMap`]: std::collections::HashMap #[derive(Debug)] pub struct IterMut< 'a, T: BiHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { tables: &'a BiHashMapTables, inner: hash_map::ValuesMut<'a, usize, T>, } impl<'a, T: BiHashItem, S: Clone + BuildHasher, A: Allocator> IterMut<'a, T, S, A> { pub(super) fn new( tables: &'a BiHashMapTables, items: &'a mut ItemSet, ) -> Self { Self { tables, inner: items.values_mut() } } } impl<'a, T: BiHashItem, S: Clone + BuildHasher, A: Allocator> Iterator for IterMut<'a, T, S, A> { type Item = RefMut<'a, T, S>; #[inline] fn next(&mut self) -> Option { let next = self.inner.next()?; let hashes = self.tables.make_hashes::(&next.key1(), &next.key2()); Some(RefMut::new(self.tables.state.clone(), hashes, next)) } } impl ExactSizeIterator for IterMut<'_, T, S, A> { #[inline] fn len(&self) -> usize { self.inner.len() } } // hash_map::IterMut is a FusedIterator, so IterMut is as well. impl FusedIterator for IterMut<'_, T, S, A> { } /// An iterator over the elements of a [`BiHashMap`] by ownership. Created by /// [`BiHashMap::into_iter`]. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed /// to be stable. /// /// [`BiHashMap`]: crate::BiHashMap /// [`BiHashMap::into_iter`]: crate::BiHashMap::into_iter /// [`HashMap`]: std::collections::HashMap #[derive(Debug)] pub struct IntoIter { inner: hash_map::IntoValues>, } impl IntoIter { pub(crate) fn new(items: ItemSet) -> Self { Self { inner: items.into_values() } } } impl Iterator for IntoIter { type Item = T; #[inline] fn next(&mut self) -> Option { self.inner.next() } } impl ExactSizeIterator for IntoIter { #[inline] fn len(&self) -> usize { self.inner.len() } } // hash_map::IterMut is a FusedIterator, so IterMut is as well. impl FusedIterator for IntoIter {} iddqd-0.3.17/src/bi_hash_map/mod.rs000064400000000000000000000020021046102023000151410ustar 00000000000000//! A hash map where values are uniquely indexed by two keys. //! //! For more information, see [`BiHashMap`]. #[cfg(feature = "daft")] mod daft_impls; mod entry; mod entry_indexes; pub(crate) mod imp; mod iter; #[cfg(feature = "proptest")] mod proptest_impls; mod ref_mut; #[cfg(feature = "schemars08")] mod schemars_impls; #[cfg(feature = "serde")] mod serde_impls; mod tables; pub(crate) mod trait_defs; #[cfg(feature = "daft")] pub use daft_impls::{ByK1, ByK2, Diff, MapLeaf}; pub use entry::{ Entry, OccupiedEntry, OccupiedEntryMut, OccupiedEntryRef, VacantEntry, }; pub use imp::BiHashMap; pub use iter::{IntoIter, Iter, IterMut}; #[cfg(all(feature = "proptest", feature = "default-hasher"))] pub use proptest_impls::prop_strategy; #[cfg(feature = "proptest")] pub use proptest_impls::{ BiHashMapStrategy, BiHashMapValueTree, prop_strategy_with_hasher, prop_strategy_with_hasher_in, }; pub use ref_mut::RefMut; #[cfg(feature = "serde")] pub use serde_impls::BiHashMapAsMap; pub use trait_defs::BiHashItem; iddqd-0.3.17/src/bi_hash_map/proptest_impls.rs000064400000000000000000000203331046102023000174550ustar 00000000000000//! Proptest strategies for generating [`BiHashMap`]s with random inputs. use crate::{ BiHashItem, bi_hash_map::BiHashMap, support::{ alloc::{Allocator, Global}, hash_builder::DefaultHashBuilder, }, }; use core::{fmt, hash::BuildHasher}; use proptest::{ arbitrary::{Arbitrary, StrategyFor, any_with}, collection::{SizeRange, VecStrategy, VecValueTree}, strategy::{NewTree, Strategy, ValueTree}, test_runner::TestRunner, }; /// Strategy to create [`BiHashMap`]s with a length in a certain range. /// /// Created by the [`prop_strategy()`] function. #[must_use = "strategies do nothing unless used"] #[derive(Clone)] pub struct BiHashMapStrategy where T: Strategy, { inner: VecStrategy, hasher: S, allocator: A, } impl fmt::Debug for BiHashMapStrategy where T: Strategy, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BiHashMapStrategy") .field("inner", &self.inner) .finish_non_exhaustive() } } /// Creates a strategy to generate [`BiHashMap`]s containing items drawn from /// `element` and with a size within the given range. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_hash_map, bi_upcast}; /// use proptest::{ /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, /// }; /// /// #[derive(Debug, Clone, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// } /// /// impl BiHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// bi_upcast!(); /// } /// /// // Create a strategy using a tuple and mapping it to Person. /// let strategy = bi_hash_map::prop_strategy( /// (any::(), any::()) /// .prop_map(|(id, email)| Person { id, email }), /// 0..=5, /// ); /// /// // The strategy can be used in proptest contexts. /// let mut runner = TestRunner::default(); /// let _tree = strategy.new_tree(&mut runner).unwrap(); /// # } /// ``` #[cfg(feature = "default-hasher")] pub fn prop_strategy( element: T, size: impl Into, ) -> BiHashMapStrategy { BiHashMapStrategy { inner: proptest::collection::vec(element, size), hasher: DefaultHashBuilder::default(), allocator: crate::support::alloc::global_alloc(), } } /// Creates a strategy to generate [`BiHashMap`]s with a custom hasher. /// /// # Examples /// /// ``` /// use iddqd::{BiHashItem, BiHashMap, bi_hash_map, bi_upcast}; /// use proptest::{ /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, /// }; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, Clone, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// } /// /// impl BiHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// bi_upcast!(); /// } /// /// // Create a strategy with a custom hasher. /// let hasher = RandomState::new(); /// let strategy = bi_hash_map::prop_strategy_with_hasher( /// (any::(), any::()) /// .prop_map(|(id, email)| Person { id, email }), /// 0..=3, /// hasher, /// ); /// /// // The strategy can be used in proptest contexts. /// let mut runner = TestRunner::default(); /// let _tree = strategy.new_tree(&mut runner).unwrap(); /// ``` pub fn prop_strategy_with_hasher( element: T, size: impl Into, hasher: S, ) -> BiHashMapStrategy { let size = size.into(); BiHashMapStrategy { inner: proptest::collection::vec(element, size), hasher, allocator: crate::support::alloc::global_alloc(), } } /// Creates a strategy to generate [`BiHashMap`]s with a custom hasher and /// allocator. /// /// # Examples /// /// ``` /// # #[cfg(feature = "allocator-api2")] { /// use allocator_api2::alloc::Global; /// use iddqd::{BiHashItem, BiHashMap, bi_hash_map, bi_upcast}; /// use proptest::{ /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, /// }; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, Clone, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// } /// /// impl BiHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// bi_upcast!(); /// } /// /// // Create a strategy with custom hasher and allocator. /// let hasher = RandomState::new(); /// let allocator = Global; /// let strategy = bi_hash_map::prop_strategy_with_hasher_in( /// (any::(), any::()) /// .prop_map(|(id, email)| Person { id, email }), /// 1..=4, /// hasher, /// allocator, /// ); /// /// // The strategy can be used in proptest contexts. /// let mut runner = TestRunner::default(); /// let _tree = strategy.new_tree(&mut runner).unwrap(); /// # } /// ``` pub fn prop_strategy_with_hasher_in( element: T, size: impl Into, hasher: S, allocator: A, ) -> BiHashMapStrategy { let size = size.into(); BiHashMapStrategy { inner: proptest::collection::vec(element, size), hasher, allocator, } } impl<'a, T, S, A> Strategy for BiHashMapStrategy where T: Strategy, T::Value: 'a + BiHashItem, ::K1<'a>: fmt::Debug, ::K2<'a>: fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Tree = BiHashMapValueTree; type Value = BiHashMap; fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let inner = self.inner.new_tree(runner)?; Ok(BiHashMapValueTree { inner, hasher: self.hasher.clone(), allocator: self.allocator.clone(), }) } } /// `ValueTree` corresponding to [`BiHashMapStrategy`]. #[derive(Clone)] pub struct BiHashMapValueTree where T: ValueTree, { inner: VecValueTree, hasher: S, allocator: A, } impl fmt::Debug for BiHashMapValueTree where T: ValueTree + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BiHashMapValueTree") .field("inner", &self.inner) .finish_non_exhaustive() } } impl<'a, T, S, A> ValueTree for BiHashMapValueTree where T: ValueTree, T::Value: 'a + BiHashItem, ::K1<'a>: fmt::Debug, ::K2<'a>: fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Value = BiHashMap; fn current(&self) -> Self::Value { let items = self.inner.current(); let mut map = BiHashMap::with_hasher_in( self.hasher.clone(), self.allocator.clone(), ); for item in items { // Use insert_overwrite to handle duplicate keys. map.insert_overwrite(item); } map } fn simplify(&mut self) -> bool { self.inner.simplify() } fn complicate(&mut self) -> bool { self.inner.complicate() } } impl<'a, T, S, A> Arbitrary for BiHashMap where T: 'a + BiHashItem + Arbitrary, ::K1<'a>: fmt::Debug, ::K2<'a>: fmt::Debug, S: Clone + BuildHasher + Default, A: Clone + Allocator + Default, { type Parameters = (SizeRange, T::Parameters); type Strategy = BiHashMapStrategy, S, A>; fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { let (size, element_args) = args; prop_strategy_with_hasher_in( any_with::(element_args), size, S::default(), A::default(), ) } } iddqd-0.3.17/src/bi_hash_map/ref_mut.rs000064400000000000000000000106531046102023000160360ustar 00000000000000use crate::{BiHashItem, DefaultHashBuilder, support::map_hash::MapHash}; use core::{ fmt, hash::BuildHasher, ops::{Deref, DerefMut}, }; /// A mutable reference to a [`BiHashMap`] item. /// /// This is a wrapper around a `&mut T` that panics when dropped, if the /// borrowed value's keys have changed since the wrapper was created. /// /// # Change detection /// /// It is illegal to change the keys of a borrowed `&mut T`. `RefMut` attempts /// to enforce this invariant. /// /// `RefMut` stores the `Hash` output of keys at creation time, and recomputes /// these hashes when it is dropped or when [`Self::into_ref`] is called. If a /// key changes, there's a small but non-negligible chance that its hash value /// stays the same[^collision-chance]. In that case, as long as the new key is /// not the same as another existing one, internal invariants are not violated /// and the [`BiHashMap`] will continue to work correctly. (But don't do this!) /// /// It is also possible to deliberately write pathological `Hash` /// implementations that collide more often. (Don't do this either.) /// /// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is /// called on it. If a key is changed and `mem::forget` is then called on the /// `RefMut`, the `BiHashMap` will stop functioning correctly. This will not /// introduce memory safety issues, however. /// /// The issues here are similar to using interior mutability (e.g. `RefCell` or /// `Mutex`) to mutate keys in a regular `HashMap`. /// /// [`mem::forget`]: std::mem::forget /// /// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability /// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday /// problem], the probability of a collision by chance reaches 10⁻⁶ within /// around 6 × 10⁶ elements. /// /// [`BiHashMap`]: crate::BiHashMap /// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table pub struct RefMut< 'a, T: BiHashItem, S: Clone + BuildHasher = DefaultHashBuilder, > { inner: Option>, } impl<'a, T: BiHashItem, S: Clone + BuildHasher> RefMut<'a, T, S> { pub(super) fn new( state: S, hashes: [MapHash; 2], borrowed: &'a mut T, ) -> Self { Self { inner: Some(RefMutInner { state, hashes, borrowed }) } } /// Borrows self into a shorter-lived `RefMut`. /// /// This `RefMut` will also check hash equality on drop. pub fn reborrow(&mut self) -> RefMut<'_, T, S> { let inner = self.inner.as_mut().unwrap(); let borrowed = &mut *inner.borrowed; RefMut::new(inner.state.clone(), inner.hashes.clone(), borrowed) } /// Converts this `RefMut` into a `&'a T`. pub fn into_ref(mut self) -> &'a T { let inner = self.inner.take().unwrap(); inner.into_ref() } } impl Drop for RefMut<'_, T, S> { fn drop(&mut self) { if let Some(inner) = self.inner.take() { inner.into_ref(); } } } impl Deref for RefMut<'_, T, S> { type Target = T; fn deref(&self) -> &Self::Target { self.inner.as_ref().unwrap().borrowed } } impl DerefMut for RefMut<'_, T, S> { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.as_mut().unwrap().borrowed } } impl fmt::Debug for RefMut<'_, T, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.inner { Some(ref inner) => inner.fmt(f), None => { f.debug_struct("RefMut").field("borrowed", &"missing").finish() } } } } struct RefMutInner<'a, T: BiHashItem, S> { state: S, hashes: [MapHash; 2], borrowed: &'a mut T, } impl<'a, T: BiHashItem, S: BuildHasher> RefMutInner<'a, T, S> { fn into_ref(self) -> &'a T { if !self.hashes[0].is_same_hash(&self.state, self.borrowed.key1()) { panic!("key1 changed during RefMut borrow"); } if !self.hashes[1].is_same_hash(&self.state, self.borrowed.key2()) { panic!("key2 changed during RefMut borrow"); } self.borrowed } } impl fmt::Debug for RefMutInner<'_, T, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.borrowed.fmt(f) } } iddqd-0.3.17/src/bi_hash_map/schemars_impls.rs000064400000000000000000000023531046102023000174040ustar 00000000000000//! Schemars implementations for BiHashMap. use crate::{ bi_hash_map::{ imp::BiHashMap, serde_impls::BiHashMapAsMap, trait_defs::BiHashItem, }, support::{ alloc::Allocator, schemars_utils::{create_map_schema, create_object_schema}, }, }; use alloc::string::String; use schemars::{JsonSchema, gen::SchemaGenerator, schema::Schema}; impl JsonSchema for BiHashMap where T: JsonSchema + BiHashItem, A: Allocator, { fn schema_name() -> String { alloc::format!("BiHashMap_of_{}", T::schema_name()) } fn json_schema(generator: &mut SchemaGenerator) -> Schema { create_map_schema::("BiHashMap", "iddqd::BiHashMap", generator) } fn is_referenceable() -> bool { false } } impl JsonSchema for BiHashMapAsMap where T: JsonSchema + BiHashItem, A: Allocator, { fn schema_name() -> String { alloc::format!("BiHashMapAsMap_of_{}", T::schema_name()) } fn json_schema(generator: &mut SchemaGenerator) -> Schema { create_object_schema::( "BiHashMapAsMap", "iddqd::BiHashMap", generator, ) } fn is_referenceable() -> bool { false } } iddqd-0.3.17/src/bi_hash_map/serde_impls.rs000064400000000000000000000250431046102023000167020ustar 00000000000000use crate::{ BiHashItem, BiHashMap, DefaultHashBuilder, support::alloc::{Allocator, Global}, }; use core::{fmt, hash::BuildHasher, marker::PhantomData}; use serde_core::{ Deserialize, Deserializer, Serialize, Serializer, de::{MapAccess, SeqAccess, Visitor}, ser::SerializeMap, }; /// A `BiHashMap` serializes to the list of items. Items are serialized in /// arbitrary order. /// /// Serializing as a list of items rather than as a map works around the lack of /// non-string keys in formats like JSON. /// /// To serialize as a map instead, see [`BiHashMapAsMap`]. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// # use iddqd_test_utils::serde_json; /// use serde::{Deserialize, Serialize}; /// /// #[derive(Debug, Serialize)] /// struct Item { /// id: u32, /// name: String, /// email: String, /// value: usize, /// } /// /// // This is a complex key, so it can't be a JSON map key. /// #[derive(Eq, Hash, PartialEq)] /// struct ComplexKey<'a> { /// name: &'a str, /// email: &'a str, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = ComplexKey<'a>; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// ComplexKey { name: &self.name, email: &self.email } /// } /// bi_upcast!(); /// } /// /// let mut map = BiHashMap::::new(); /// map.insert_unique(Item { /// id: 1, /// name: "Alice".to_string(), /// email: "alice@example.com".to_string(), /// value: 42, /// }) /// .unwrap(); /// /// // The map is serialized as a list of items. /// let serialized = serde_json::to_string(&map).unwrap(); /// assert_eq!( /// serialized, /// r#"[{"id":1,"name":"Alice","email":"alice@example.com","value":42}]"#, /// ); /// # } /// ``` impl Serialize for BiHashMap where T: Serialize, { fn serialize( &self, serializer: Ser, ) -> Result { // Serialize just the items -- don't serialize the indexes. We'll // rebuild the indexes on deserialization. self.items.serialize(serializer) } } /// The `Deserialize` impl for `BiHashMap` deserializes from either a sequence /// or a map of items, rebuilding the indexes and producing an error if there /// are any duplicates. /// /// In case a map is deserialized, the key is not deserialized or verified /// against the value. (In general, verification is not possible because the key /// type has a lifetime parameter embedded in it.) /// /// The `fmt::Debug` bound on `T` ensures better error reporting. impl< 'de, T: BiHashItem + fmt::Debug, S: Clone + BuildHasher + Default, A: Default + Allocator + Clone, > Deserialize<'de> for BiHashMap where T: Deserialize<'de>, { fn deserialize>( deserializer: D, ) -> Result { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher: S::default(), alloc: A::default(), }) } } impl< 'de, T: BiHashItem + fmt::Debug + Deserialize<'de>, S: Clone + BuildHasher, A: Clone + Allocator, > BiHashMap { /// Deserializes from a list of items, allocating new storage within the /// provided allocator. pub fn deserialize_in>( deserializer: D, alloc: A, ) -> Result where S: Default, { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher: S::default(), alloc, }) } /// Deserializes from a list of items, with the given hasher, using the /// default allocator. pub fn deserialize_with_hasher>( deserializer: D, hasher: S, ) -> Result where A: Default, { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher, alloc: A::default(), }) } /// Deserializes from a list of items, with the given hasher, and allocating /// new storage within the provided allocator. pub fn deserialize_with_hasher_in>( deserializer: D, hasher: S, alloc: A, ) -> Result { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher, alloc, }) } } struct SeqVisitor { _marker: PhantomData T>, hasher: S, alloc: A, } impl<'de, T, S, A> Visitor<'de> for SeqVisitor where T: BiHashItem + Deserialize<'de> + fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Value = BiHashMap; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .write_str("a sequence or map of items representing a BiHashMap") } fn visit_seq( self, mut seq: Access, ) -> Result where Access: SeqAccess<'de>, { let mut map = match seq.size_hint() { Some(size) => BiHashMap::with_capacity_and_hasher_in( size, self.hasher, self.alloc, ), None => BiHashMap::with_hasher_in(self.hasher, self.alloc), }; while let Some(element) = seq.next_element()? { map.insert_unique(element) .map_err(serde_core::de::Error::custom)?; } Ok(map) } fn visit_map( self, mut map_access: Access, ) -> Result where Access: MapAccess<'de>, { let mut map = match map_access.size_hint() { Some(size) => BiHashMap::with_capacity_and_hasher_in( size, self.hasher, self.alloc, ), None => BiHashMap::with_hasher_in(self.hasher, self.alloc), }; while let Some((_, value)) = map_access.next_entry::()? { map.insert_unique(value).map_err(serde_core::de::Error::custom)?; } Ok(map) } } /// Marker type for [`BiHashMap`] serialized as a map, for use with serde's /// `with` attribute. /// /// The key type [`Self::K1`](BiHashItem::K1) is used as the map key. /// /// # Examples /// /// Use with serde's `with` attribute: /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{ /// BiHashItem, BiHashMap, bi_hash_map::BiHashMapAsMap, bi_upcast, /// }; /// use serde::{Deserialize, Serialize}; /// /// #[derive(Debug, Serialize, Deserialize)] /// struct Item { /// id: u32, /// name: String, /// } /// /// impl BiHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// #[derive(Serialize, Deserialize)] /// struct Config { /// #[serde(with = "BiHashMapAsMap")] /// items: BiHashMap, /// } /// # } /// ``` /// /// # Requirements /// /// - For serialization, the key type `K1` must implement [`Serialize`]. /// - For JSON serialization, `K1` should be string-like or convertible to a string key. pub struct BiHashMapAsMap { #[expect(clippy::type_complexity)] _marker: PhantomData (T, S, A)>, } struct MapVisitorAsMap { _marker: PhantomData T>, hasher: S, alloc: A, } impl<'de, T, S, A> Visitor<'de> for MapVisitorAsMap where T: BiHashItem + Deserialize<'de> + fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Value = BiHashMap; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a map with items representing a BiHashMap") } fn visit_map( self, mut map_access: Access, ) -> Result where Access: MapAccess<'de>, { let mut map = match map_access.size_hint() { Some(size) => BiHashMap::with_capacity_and_hasher_in( size, self.hasher, self.alloc, ), None => BiHashMap::with_hasher_in(self.hasher, self.alloc), }; while let Some((_, value)) = map_access.next_entry::()? { map.insert_unique(value).map_err(serde_core::de::Error::custom)?; } Ok(map) } } impl BiHashMapAsMap where S: Clone + BuildHasher, A: Allocator, { /// Serializes a `BiHashMap` as a JSON object/map using `key1()` as keys. pub fn serialize<'a, Ser>( map: &BiHashMap, serializer: Ser, ) -> Result where T: BiHashItem + Serialize, T: 'a, T::K1<'a>: Serialize, Ser: Serializer, { let mut ser_map = serializer.serialize_map(Some(map.len()))?; for item in map.iter() { let key1 = item.key1(); // SAFETY: // // * Lifetime extension: for a type T and two lifetime params 'a and // 'b, T<'a> and T<'b> aren't guaranteed to have the same layout, // but (a) that is true today and (b) it would be shocking and // break half the Rust ecosystem if that were to change in the // future. // * We only use key within the scope of this block before // immediately dropping it. In particular, ser_map.serialize_entry // serializes the key without holding a reference to it. let key1 = unsafe { core::mem::transmute::, T::K1<'a>>(key1) }; ser_map.serialize_entry(&key1, item)?; } ser_map.end() } /// Deserializes a `BiHashMap` from a JSON object/map. pub fn deserialize<'de, D>( deserializer: D, ) -> Result, D::Error> where T: BiHashItem + Deserialize<'de> + fmt::Debug, S: Default, A: Clone + Default, D: Deserializer<'de>, { deserializer.deserialize_map(MapVisitorAsMap { _marker: PhantomData, hasher: S::default(), alloc: A::default(), }) } } iddqd-0.3.17/src/bi_hash_map/tables.rs000064400000000000000000000041321046102023000156420ustar 00000000000000use crate::{ BiHashItem, internal::{ValidateCompact, ValidationError}, support::{ alloc::{Allocator, Global, global_alloc}, hash_table::MapHashTable, map_hash::MapHash, }, }; use core::hash::BuildHasher; #[derive(Clone, Debug, Default)] pub(super) struct BiHashMapTables { pub(super) state: S, pub(super) k1_to_item: MapHashTable, pub(super) k2_to_item: MapHashTable, } impl BiHashMapTables { pub(super) const fn with_hasher(hasher: S) -> Self { Self { state: hasher, k1_to_item: MapHashTable::new_in(global_alloc()), k2_to_item: MapHashTable::new_in(global_alloc()), } } } impl BiHashMapTables { pub(super) fn with_capacity_and_hasher_in( capacity: usize, hasher: S, alloc: A, ) -> Self { Self { state: hasher, k1_to_item: MapHashTable::with_capacity_in(capacity, alloc.clone()), k2_to_item: MapHashTable::with_capacity_in(capacity, alloc), } } } impl BiHashMapTables { #[cfg(feature = "daft")] pub(super) fn hasher(&self) -> &S { &self.state } pub(super) fn validate( &self, expected_len: usize, compactness: ValidateCompact, ) -> Result<(), ValidationError> { // Check that all the maps are of the right size. self.k1_to_item.validate(expected_len, compactness).map_err( |error| ValidationError::Table { name: "k1_to_table", error }, )?; self.k2_to_item.validate(expected_len, compactness).map_err( |error| ValidationError::Table { name: "k2_to_table", error }, )?; Ok(()) } pub(super) fn make_hashes( &self, k1: &T::K1<'_>, k2: &T::K2<'_>, ) -> [MapHash; 2] { let h1 = self.k1_to_item.compute_hash(&self.state, k1); let h2 = self.k2_to_item.compute_hash(&self.state, k2); [h1, h2] } } iddqd-0.3.17/src/bi_hash_map/trait_defs.rs000064400000000000000000000077731046102023000165320ustar 00000000000000//! Trait definitions for `BiHashMap`. use alloc::{boxed::Box, rc::Rc, sync::Arc}; use core::hash::Hash; /// An item in a [`BiHashMap`]. /// /// This trait is used to define the keys. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, BiHashMap, bi_upcast}; /// /// // Define a struct with two keys. /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct MyPair { /// id: u32, /// name: String, /// } /// /// // Implement BiHashItem for the struct. /// impl BiHashItem for MyPair { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// /// bi_upcast!(); /// } /// /// // Create a BiHashMap and insert items. /// let mut map = BiHashMap::new(); /// map.insert_unique(MyPair { id: 1, name: "Alice".to_string() }).unwrap(); /// map.insert_unique(MyPair { id: 2, name: "Bob".to_string() }).unwrap(); /// # } /// ``` /// /// [`BiHashMap`]: crate::BiHashMap pub trait BiHashItem { /// The first key type. type K1<'a>: Eq + Hash where Self: 'a; /// The second key type. type K2<'a>: Eq + Hash where Self: 'a; /// Retrieves the first key. fn key1(&self) -> Self::K1<'_>; /// Retrieves the second key. fn key2(&self) -> Self::K2<'_>; /// Upcasts the first key to a shorter lifetime, in effect asserting that /// the lifetime `'a` on [`BiHashItem::K1`] is covariant. /// /// Typically implemented via the [`bi_upcast`] macro. /// /// [`bi_upcast`]: crate::bi_upcast fn upcast_key1<'short, 'long: 'short>( long: Self::K1<'long>, ) -> Self::K1<'short>; /// Upcasts the second key to a shorter lifetime, in effect asserting that /// the lifetime `'a` on [`BiHashItem::K2`] is covariant. /// /// Typically implemented via the [`bi_upcast`] macro. /// /// [`bi_upcast`]: crate::bi_upcast fn upcast_key2<'short, 'long: 'short>( long: Self::K2<'long>, ) -> Self::K2<'short>; } macro_rules! impl_for_ref { ($type:ty) => { impl<'b, T: 'b + ?Sized + BiHashItem> BiHashItem for $type { type K1<'a> = T::K1<'a> where Self: 'a; type K2<'a> = T::K2<'a> where Self: 'a; fn key1(&self) -> Self::K1<'_> { (**self).key1() } fn key2(&self) -> Self::K2<'_> { (**self).key2() } fn upcast_key1<'short, 'long: 'short>( long: Self::K1<'long>, ) -> Self::K1<'short> where Self: 'long, { T::upcast_key1(long) } fn upcast_key2<'short, 'long: 'short>( long: Self::K2<'long>, ) -> Self::K2<'short> where Self: 'long, { T::upcast_key2(long) } } }; } impl_for_ref!(&'b T); impl_for_ref!(&'b mut T); macro_rules! impl_for_box { ($type:ty) => { impl BiHashItem for $type { type K1<'a> = T::K1<'a> where Self: 'a; type K2<'a> = T::K2<'a> where Self: 'a; fn key1(&self) -> Self::K1<'_> { (**self).key1() } fn key2(&self) -> Self::K2<'_> { (**self).key2() } fn upcast_key1<'short, 'long: 'short>( long: Self::K1<'long>, ) -> Self::K1<'short> { T::upcast_key1(long) } fn upcast_key2<'short, 'long: 'short>( long: Self::K2<'long>, ) -> Self::K2<'short> { T::upcast_key2(long) } } }; } impl_for_box!(Box); impl_for_box!(Rc); impl_for_box!(Arc); iddqd-0.3.17/src/errors.rs000064400000000000000000000063441046102023000134610ustar 00000000000000//! Error types for this crate. //! //! These types are shared across all map implementations in this crate. use alloc::vec::Vec; use core::fmt; /// An item conflicts with existing items. #[derive(Debug)] pub struct DuplicateItem { new: T, duplicates: Vec, } impl DuplicateItem { /// Creates a new `DuplicateItem` error. #[doc(hidden)] pub fn __internal_new(new: T, duplicates: Vec) -> Self { DuplicateItem { new, duplicates } } /// Returns the new item that was attempted to be inserted. #[inline] pub fn new_item(&self) -> &T { &self.new } /// Returns the list of items that conflict with the new item. #[inline] pub fn duplicates(&self) -> &[D] { &self.duplicates } /// Converts self into its constituent parts. pub fn into_parts(self) -> (T, Vec) { (self.new, self.duplicates) } } impl DuplicateItem { /// Converts self to an owned `DuplicateItem` by cloning the list of /// duplicates. /// /// If `T` is `'static`, the owned form is suitable for conversion to /// `Box`, `anyhow::Error`, and so on. pub fn into_owned(self) -> DuplicateItem { DuplicateItem { new: self.new, duplicates: self.duplicates.into_iter().cloned().collect(), } } } impl fmt::Display for DuplicateItem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "new item: {:?} conflicts with existing: {:?}", self.new, self.duplicates ) } } impl core::error::Error for DuplicateItem {} /// The error type for `try_reserve` methods. /// /// This wraps the underlying allocation error from the hash table implementation. #[derive(Clone, PartialEq, Eq, Debug)] pub struct TryReserveError { kind: TryReserveErrorKind, } #[derive(Clone, PartialEq, Eq, Debug)] enum TryReserveErrorKind { /// Error due to the computed capacity exceeding the collection's maximum /// (usually `isize::MAX` bytes). CapacityOverflow, /// The memory allocator returned an error AllocError { /// The layout of the allocation request that failed layout: core::alloc::Layout, }, } impl TryReserveError { /// Converts from a hashbrown `TryReserveError`. pub(crate) fn from_hashbrown(error: hashbrown::TryReserveError) -> Self { let kind = match error { hashbrown::TryReserveError::CapacityOverflow => { TryReserveErrorKind::CapacityOverflow } hashbrown::TryReserveError::AllocError { layout } => { TryReserveErrorKind::AllocError { layout } } }; Self { kind } } } impl fmt::Display for TryReserveError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.kind { TryReserveErrorKind::CapacityOverflow => { write!(f, "capacity overflow") } TryReserveErrorKind::AllocError { .. } => { write!(f, "memory allocation failed") } } } } impl core::error::Error for TryReserveError {} iddqd-0.3.17/src/id_hash_map/daft_impls.rs000064400000000000000000000166441046102023000165270ustar 00000000000000//! `Diffable` implementation. use super::{IdHashItem, IdHashMap}; use crate::{ DefaultHashBuilder, support::{ alloc::{Allocator, Global}, daft_utils::IdLeaf, }, }; use core::{ fmt, hash::{BuildHasher, Hash}, }; use daft::Diffable; use equivalent::Equivalent; impl Diffable for IdHashMap { type Diff<'a> = Diff<'a, T, S, A> where T: 'a, S: 'a, A: 'a; fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> { let mut diff = Diff::with_hasher_in( self.hasher().clone(), self.allocator().clone(), ); for item in self { if let Some(other_item) = other.get(&item.key()) { diff.common.insert_overwrite(IdLeaf::new(item, other_item)); } else { diff.removed.insert_overwrite(item); } } for item in other { if !self.contains_key(&item.key()) { diff.added.insert_overwrite(item); } } diff } } /// A diff of two [`IdHashMap`]s. /// /// Generated by the [`Diffable`] implementation for [`IdHashMap`]. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use daft::Diffable; /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Eq, PartialEq)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// // Create two IdHashMaps with overlapping items. /// let mut map1 = IdHashMap::new(); /// map1.insert_unique(Item { id: "a".to_string(), value: 1 }); /// map1.insert_unique(Item { id: "b".to_string(), value: 2 }); /// /// let mut map2 = IdHashMap::new(); /// map2.insert_unique(Item { id: "b".to_string(), value: 3 }); /// map2.insert_unique(Item { id: "c".to_string(), value: 4 }); /// /// // Compute the diff between the two maps. /// let diff = map1.diff(&map2); /// /// // "a" is removed. /// assert!(diff.removed.contains_key("a")); /// // "b" is modified (value changed from 2 to 3). /// assert!(diff.is_modified("b")); /// // "c" is added. /// assert!(diff.added.contains_key("c")); /// # } /// ``` /// /// [`Diffable`]: daft::Diffable pub struct Diff< 'daft, T: ?Sized + IdHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { /// Entries common to both maps. /// /// Items are stored as [`IdLeaf`]s to references. pub common: IdHashMap, S, A>, /// Added entries. pub added: IdHashMap<&'daft T, S, A>, /// Removed entries. pub removed: IdHashMap<&'daft T, S, A>, } impl<'a, 'daft, T, S: Clone + BuildHasher, A: Allocator> fmt::Debug for Diff<'daft, T, S, A> where T: ?Sized + IdHashItem + fmt::Debug, T::Key<'a>: fmt::Debug, T: 'a, 'daft: 'a, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Diff") .field("common", &self.common) .field("added", &self.added) .field("removed", &self.removed) .finish() } } impl<'daft, T: ?Sized + IdHashItem, S: Default, A: Allocator + Default> Default for Diff<'daft, T, S, A> { fn default() -> Self { Self { common: IdHashMap::default(), added: IdHashMap::default(), removed: IdHashMap::default(), } } } #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] impl<'daft, T: ?Sized + IdHashItem> Diff<'daft, T> { /// Creates a new, empty `Diff`. pub fn new() -> Self { Self { common: IdHashMap::new(), added: IdHashMap::new(), removed: IdHashMap::new(), } } } #[cfg(feature = "allocator-api2")] impl<'daft, T: ?Sized + IdHashItem, S: Clone + BuildHasher> Diff<'daft, T, S> { /// Creates a new, empty `Diff` with the given hasher. pub fn with_hasher(hasher: S) -> Self { Self { common: IdHashMap::with_hasher(hasher.clone()), added: IdHashMap::with_hasher(hasher.clone()), removed: IdHashMap::with_hasher(hasher), } } } impl< 'daft, T: ?Sized + IdHashItem, S: Clone + BuildHasher, A: Clone + Allocator, > Diff<'daft, T, S, A> { /// Creates a new, empty `Diff` with the given hasher and allocator. pub fn with_hasher_in(hasher: S, alloc: A) -> Self { Self { common: IdHashMap::with_hasher_in(hasher.clone(), alloc.clone()), added: IdHashMap::with_hasher_in(hasher.clone(), alloc.clone()), removed: IdHashMap::with_hasher_in(hasher, alloc), } } } impl<'daft, T: ?Sized + IdHashItem + Eq, S: Clone + BuildHasher, A: Allocator> Diff<'daft, T, S, A> { /// Returns an iterator over unchanged keys and values. pub fn unchanged(&self) -> impl Iterator + '_ { self.common .iter() .filter_map(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns true if the item corresponding to the key is unchanged. pub fn is_unchanged<'a, Q>(&'a self, key: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get(key).is_some_and(|leaf| leaf.is_unchanged()) } /// Returns the value associated with the key if it is unchanged, /// otherwise `None`. pub fn get_unchanged<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> where Q: ?Sized + Hash + Equivalent>, { self.common .get(key) .and_then(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns an iterator over modified keys and values. pub fn modified(&self) -> impl Iterator> + '_ { self.common .iter() .filter_map(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns true if the value corresponding to the key is /// modified. pub fn is_modified<'a, Q>(&'a self, key: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get(key).is_some_and(|leaf| leaf.is_modified()) } /// Returns the [`IdLeaf`] associated with the key if it is modified, /// otherwise `None`. pub fn get_modified<'a, Q>(&'a self, key: &Q) -> Option> where Q: ?Sized + Hash + Equivalent>, { self.common .get(key) .and_then(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns an iterator over modified keys and values, performing /// a diff on the values. /// /// This is useful when `T::Diff` is a complex type, not just a /// [`daft::Leaf`]. pub fn modified_diff(&self) -> impl Iterator> + '_ where T: Diffable, { self.modified().map(|leaf| leaf.diff_pair()) } } impl IdHashItem for IdLeaf { type Key<'a> = T::Key<'a> where T: 'a; fn key(&self) -> Self::Key<'_> { let before_key = self.before().key(); if before_key != self.after().key() { panic!("key is different between before and after"); } before_key } #[inline] fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> { T::upcast_key(long) } } iddqd-0.3.17/src/id_hash_map/entry.rs000064400000000000000000000211701046102023000155340ustar 00000000000000use super::{IdHashItem, IdHashMap, RefMut}; use crate::{ DefaultHashBuilder, support::{ alloc::{Allocator, Global}, borrow::DormantMutRef, map_hash::MapHash, }, }; use core::{fmt, hash::BuildHasher}; /// An implementation of the Entry API for [`IdHashMap`]. pub enum Entry<'a, T: IdHashItem, S = DefaultHashBuilder, A: Allocator = Global> { /// A vacant entry. Vacant(VacantEntry<'a, T, S, A>), /// An occupied entry. Occupied(OccupiedEntry<'a, T, S, A>), } impl<'a, T: IdHashItem, S, A: Allocator> fmt::Debug for Entry<'a, T, S, A> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Entry::Vacant(entry) => { f.debug_tuple("Vacant").field(entry).finish() } Entry::Occupied(entry) => { f.debug_tuple("Occupied").field(entry).finish() } } } } impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> Entry<'a, T, S, A> { /// Ensures a value is in the entry by inserting the default if empty, and /// returns a mutable reference to the value in the entry. /// /// # Panics /// /// Panics if the key hashes to a different value than the one passed /// into [`IdHashMap::entry`]. #[inline] pub fn or_insert(self, default: T) -> RefMut<'a, T, S> { match self { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => entry.insert(default), } } /// Ensures a value is in the entry by inserting the result of the default /// function if empty, and returns a mutable reference to the value in the /// entry. /// /// # Panics /// /// Panics if the key hashes to a different value than the one passed /// into [`IdHashMap::entry`]. #[inline] pub fn or_insert_with T>( self, default: F, ) -> RefMut<'a, T, S> { match self { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => entry.insert(default()), } } /// Provides in-place mutable access to an occupied entry before any /// potential inserts into the map. #[inline] pub fn and_modify(self, f: F) -> Self where F: FnOnce(RefMut<'_, T, S>), { match self { Entry::Occupied(mut entry) => { f(entry.get_mut()); Entry::Occupied(entry) } Entry::Vacant(entry) => Entry::Vacant(entry), } } } /// A vacant entry. pub struct VacantEntry< 'a, T: IdHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { map: DormantMutRef<'a, IdHashMap>, hash: MapHash, } impl<'a, T: IdHashItem, S, A: Allocator> fmt::Debug for VacantEntry<'a, T, S, A> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("VacantEntry") .field("hash", &self.hash) .finish_non_exhaustive() } } impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> VacantEntry<'a, T, S, A> { pub(super) unsafe fn new( map: DormantMutRef<'a, IdHashMap>, hash: MapHash, ) -> Self { VacantEntry { map, hash } } /// Sets the entry to a new value, returning a mutable reference to the /// value. pub fn insert(self, value: T) -> RefMut<'a, T, S> { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.awaken() }; let state = &map.tables.state; if !self.hash.is_same_hash(state, value.key()) { panic!("key hashes do not match"); } let Ok(index) = map.insert_unique_impl(value) else { panic!("key already present in map"); }; map.get_by_index_mut(index).expect("index is known to be valid") } /// Sets the value of the entry, and returns an `OccupiedEntry`. #[inline] pub fn insert_entry(mut self, value: T) -> OccupiedEntry<'a, T, S, A> { let index = { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.reborrow() }; let state = &map.tables.state; if !self.hash.is_same_hash(state, value.key()) { panic!("key hashes do not match"); } let Ok(index) = map.insert_unique_impl(value) else { panic!("key already present in map"); }; index }; // SAFETY: map, as well as anything that was borrowed from it, is // dropped once the above block exits. unsafe { OccupiedEntry::new(self.map, index) } } } /// A view into an occupied entry in an [`IdHashMap`]. Part of the [`Entry`] /// enum. pub struct OccupiedEntry< 'a, T: IdHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { map: DormantMutRef<'a, IdHashMap>, // index is a valid index into the map's internal hash table. index: usize, } impl<'a, T: IdHashItem, S, A: Allocator> fmt::Debug for OccupiedEntry<'a, T, S, A> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("OccupiedEntry") .field("index", &self.index) .finish_non_exhaustive() } } impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> OccupiedEntry<'a, T, S, A> { /// # Safety /// /// After self is created, the original reference created by /// `DormantMutRef::new` must not be used. pub(super) unsafe fn new( map: DormantMutRef<'a, IdHashMap>, index: usize, ) -> Self { OccupiedEntry { map, index } } /// Gets a reference to the value. /// /// If you need a reference to `T` that may outlive the destruction of the /// `Entry` value, see [`into_ref`](Self::into_ref). pub fn get(&self) -> &T { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. unsafe { self.map.reborrow_shared() } .get_by_index(self.index) .expect("index is known to be valid") } /// Gets a mutable reference to the value. /// /// If you need a reference to `T` that may outlive the destruction of the /// `Entry` value, see [`into_mut`](Self::into_mut). pub fn get_mut(&mut self) -> RefMut<'_, T, S> { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. unsafe { self.map.reborrow() } .get_by_index_mut(self.index) .expect("index is known to be valid") } /// Converts self into a reference to the value. /// /// If you need multiple references to the `OccupiedEntry`, see /// [`get`](Self::get). pub fn into_ref(self) -> &'a T { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. unsafe { self.map.awaken() } .get_by_index(self.index) .expect("index is known to be valid") } /// Converts self into a mutable reference to the value. /// /// If you need multiple references to the `OccupiedEntry`, see /// [`get_mut`](Self::get_mut). pub fn into_mut(self) -> RefMut<'a, T, S> { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. unsafe { self.map.awaken() } .get_by_index_mut(self.index) .expect("index is known to be valid") } /// Sets the entry to a new value, returning the old value. /// /// # Panics /// /// Panics if `value.key()` is different from the key of the entry. pub fn insert(&mut self, value: T) -> T { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. // // Note that `replace_at_index` panics if the keys don't match. unsafe { self.map.reborrow() }.replace_at_index(self.index, value) } /// Takes ownership of the value from the map. pub fn remove(mut self) -> T { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. unsafe { self.map.reborrow() } .remove_by_index(self.index) .expect("index is known to be valid") } } iddqd-0.3.17/src/id_hash_map/imp.rs000064400000000000000000001522301046102023000151620ustar 00000000000000use super::{ Entry, IdHashItem, IntoIter, Iter, IterMut, OccupiedEntry, RefMut, VacantEntry, tables::IdHashMapTables, }; use crate::{ DefaultHashBuilder, errors::DuplicateItem, internal::{ValidateCompact, ValidationError}, support::{ alloc::{Allocator, Global, global_alloc}, borrow::DormantMutRef, item_set::ItemSet, map_hash::MapHash, }, }; use alloc::collections::BTreeSet; use core::{ fmt, hash::{BuildHasher, Hash}, }; use equivalent::Equivalent; use hashbrown::hash_table; /// A hash map where the key is part of the value. /// /// The storage mechanism is a fast hash table of integer indexes to items, with /// these indexes stored in a hash table. This allows for efficient lookups by /// the key and prevents duplicates. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// // Define a struct with a key. /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct MyItem { /// id: String, /// value: u32, /// } /// /// // Implement IdHashItem for the struct. /// impl IdHashItem for MyItem { /// // Keys can borrow from the item. /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// // Create an IdHashMap and insert items. /// let mut map = IdHashMap::new(); /// map.insert_unique(MyItem { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(MyItem { id: "bar".to_string(), value: 20 }).unwrap(); /// /// // Look up items by their keys. /// assert_eq!(map.get("foo").unwrap().value, 42); /// assert_eq!(map.get("bar").unwrap().value, 20); /// assert!(map.get("baz").is_none()); /// # } /// ``` #[derive(Clone)] pub struct IdHashMap { pub(super) items: ItemSet, pub(super) tables: IdHashMapTables, } impl Default for IdHashMap { fn default() -> Self { Self { items: ItemSet::with_capacity_in(0, A::default()), tables: IdHashMapTables::default(), } } } #[cfg(feature = "default-hasher")] impl IdHashMap { /// Creates a new, empty `IdHashMap`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let map: IdHashMap = IdHashMap::new(); /// assert!(map.is_empty()); /// assert_eq!(map.len(), 0); /// # } /// ``` #[inline] pub fn new() -> Self { Self { items: ItemSet::new(), tables: IdHashMapTables::default() } } /// Creates a new `IdHashMap` with the given capacity. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let map: IdHashMap = IdHashMap::with_capacity(10); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_capacity(capacity: usize) -> Self { Self { items: ItemSet::with_capacity_in(capacity, global_alloc()), tables: IdHashMapTables::with_capacity_and_hasher_in( capacity, DefaultHashBuilder::default(), global_alloc(), ), } } } impl IdHashMap { /// Creates a new, empty `IdHashMap` with the given hasher. /// /// # Examples /// /// ``` /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let hasher = RandomState::new(); /// let map: IdHashMap = IdHashMap::with_hasher(hasher); /// assert!(map.is_empty()); /// ``` pub const fn with_hasher(hasher: S) -> Self { Self { items: ItemSet::new(), tables: IdHashMapTables::with_hasher_in(hasher, global_alloc()), } } /// Creates a new `IdHashMap` with the given capacity and hasher. /// /// # Examples /// /// ``` /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let hasher = RandomState::new(); /// let map: IdHashMap = /// IdHashMap::with_capacity_and_hasher(10, hasher); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// ``` pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self { Self { items: ItemSet::with_capacity_in(capacity, global_alloc()), tables: IdHashMapTables::with_capacity_and_hasher_in( capacity, hasher, global_alloc(), ), } } } #[cfg(feature = "default-hasher")] impl IdHashMap { /// Creates a new empty `IdHashMap` using the given allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] { /// use iddqd::{IdHashMap, IdHashItem, id_upcast}; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { &self.id } /// id_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// // Create a new IdHashMap using the allocator. /// let map: IdHashMap = IdHashMap::new_in(&bump); /// assert!(map.is_empty()); /// # } /// ``` pub fn new_in(alloc: A) -> Self { Self { items: ItemSet::with_capacity_in(0, alloc.clone()), tables: IdHashMapTables::with_capacity_and_hasher_in( 0, DefaultHashBuilder::default(), alloc, ), } } /// Creates an empty `IdHashMap` with the specified capacity using the given /// allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] { /// use iddqd::{IdHashMap, IdHashItem, id_upcast}; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { &self.id } /// id_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// // Create a new IdHashMap with capacity using the allocator. /// let map: IdHashMap = IdHashMap::with_capacity_in(10, &bump); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_capacity_in(capacity: usize, alloc: A) -> Self { Self { items: ItemSet::with_capacity_in(capacity, alloc.clone()), tables: IdHashMapTables::with_capacity_and_hasher_in( capacity, DefaultHashBuilder::default(), alloc, ), } } } impl IdHashMap { /// Creates a new, empty `IdHashMap` with the given hasher and allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(feature = "allocator-api2")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// use std::collections::hash_map::RandomState; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// let hasher = RandomState::new(); /// // Create a new IdHashMap with hasher using the allocator. /// let map: IdHashMap = /// IdHashMap::with_hasher_in(hasher, &bump); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_hasher_in(hasher: S, alloc: A) -> Self { Self { items: ItemSet::new_in(alloc.clone()), tables: IdHashMapTables::with_hasher_in(hasher, alloc), } } /// Creates a new, empty `IdHashMap` with the given capacity, hasher, and /// allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(feature = "allocator-api2")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// use std::collections::hash_map::RandomState; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// let hasher = RandomState::new(); /// // Create a new IdHashMap with capacity and hasher using the allocator. /// let map: IdHashMap = /// IdHashMap::with_capacity_and_hasher_in(10, hasher, &bump); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_capacity_and_hasher_in( capacity: usize, hasher: S, alloc: A, ) -> Self { Self { items: ItemSet::with_capacity_in(capacity, alloc.clone()), tables: IdHashMapTables::with_capacity_and_hasher_in( capacity, hasher, alloc, ), } } } impl IdHashMap { #[cfg(feature = "daft")] pub(crate) fn hasher(&self) -> &S { self.tables.hasher() } /// Returns the allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] { /// use iddqd::{IdHashMap, IdHashItem, id_upcast}; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { &self.id } /// id_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// // Create a new IdHashMap using the allocator. /// let map: IdHashMap = IdHashMap::new_in(&bump); /// let _allocator = map.allocator(); /// # } /// ``` pub fn allocator(&self) -> &A { self.items.allocator() } /// Returns the currently allocated capacity of the map. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let map: IdHashMap = IdHashMap::with_capacity(10); /// assert!(map.capacity() >= 10); /// # } /// ``` pub fn capacity(&self) -> usize { // items and tables.capacity might theoretically diverge: use // items.capacity. self.items.capacity() } /// Returns true if the map is empty. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// assert!(map.is_empty()); /// /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// assert!(!map.is_empty()); /// # } /// ``` #[inline] pub fn is_empty(&self) -> bool { self.items.is_empty() } /// Returns the number of items in the map. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// assert_eq!(map.len(), 0); /// /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// assert_eq!(map.len(), 1); /// /// map.insert_unique(Item { id: "bar".to_string(), value: 20 }).unwrap(); /// assert_eq!(map.len(), 2); /// # } /// ``` #[inline] pub fn len(&self) -> usize { self.items.len() } /// Clears the map, removing all items. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 20 }).unwrap(); /// assert_eq!(map.len(), 2); /// /// map.clear(); /// assert!(map.is_empty()); /// assert_eq!(map.len(), 0); /// # } /// ``` pub fn clear(&mut self) { self.items.clear(); self.tables.key_to_item.clear(); } /// Reserves capacity for at least `additional` more elements to be inserted /// in the `IdHashMap`. The collection may reserve more space to /// speculatively avoid frequent reallocations. After calling `reserve`, /// capacity will be greater than or equal to `self.len() + additional`. /// Does nothing if capacity is already sufficient. /// /// # Panics /// /// Panics if the new capacity overflows [`isize::MAX`] bytes, and /// [`abort`]s the program in case of an allocation error. Use /// [`try_reserve`](Self::try_reserve) instead if you want to handle memory /// allocation failure. /// /// [`isize::MAX`]: https://doc.rust-lang.org/std/primitive.isize.html /// [`abort`]: https://doc.rust-lang.org/alloc/alloc/fn.handle_alloc_error.html /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map: IdHashMap = IdHashMap::new(); /// map.reserve(100); /// assert!(map.capacity() >= 100); /// # } /// ``` pub fn reserve(&mut self, additional: usize) { self.items.reserve(additional); self.tables.key_to_item.reserve(additional); } /// Tries to reserve capacity for at least `additional` more elements to be /// inserted in the `IdHashMap`. The collection may reserve more space to /// speculatively avoid frequent reallocations. After calling `try_reserve`, /// capacity will be greater than or equal to `self.len() + additional` if /// it returns `Ok(())`. Does nothing if capacity is already sufficient. /// /// # Errors /// /// If the capacity overflows, or the allocator reports a failure, then an /// error is returned. /// /// # Notes /// /// If reservation fails partway through, some internal structures may have /// already increased their capacity. The map remains in a valid state but /// may have uneven capacities across its internal structures. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map: IdHashMap = IdHashMap::new(); /// map.try_reserve(100).expect("allocation should succeed"); /// assert!(map.capacity() >= 100); /// # } /// ``` pub fn try_reserve( &mut self, additional: usize, ) -> Result<(), crate::errors::TryReserveError> { self.items .try_reserve(additional) .map_err(crate::errors::TryReserveError::from_hashbrown)?; self.tables .key_to_item .try_reserve(additional) .map_err(crate::errors::TryReserveError::from_hashbrown)?; Ok(()) } /// Shrinks the capacity of the map as much as possible. It will drop /// down as much as possible while maintaining the internal rules /// and possibly leaving some space in accordance with the resize policy. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map: IdHashMap = IdHashMap::with_capacity(100); /// map.insert_unique(Item { id: "foo".to_string(), value: 1 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 2 }).unwrap(); /// assert!(map.capacity() >= 100); /// map.shrink_to_fit(); /// assert!(map.capacity() >= 2); /// # } /// ``` pub fn shrink_to_fit(&mut self) { self.items.shrink_to_fit(); self.tables.key_to_item.shrink_to_fit(); } /// Shrinks the capacity of the map with a lower limit. It will drop /// down no lower than the supplied limit while maintaining the internal /// rules and possibly leaving some space in accordance with the resize /// policy. /// /// If the current capacity is less than the lower limit, this is a no-op. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map: IdHashMap = IdHashMap::with_capacity(100); /// map.insert_unique(Item { id: "foo".to_string(), value: 1 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 2 }).unwrap(); /// assert!(map.capacity() >= 100); /// map.shrink_to(10); /// assert!(map.capacity() >= 10); /// map.shrink_to(0); /// assert!(map.capacity() >= 2); /// # } /// ``` pub fn shrink_to(&mut self, min_capacity: usize) { self.items.shrink_to(min_capacity); self.tables.key_to_item.shrink_to(min_capacity); } /// Iterates over the items in the map. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not /// guaranteed to be stable. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 20 }).unwrap(); /// /// let mut values: Vec = map.iter().map(|item| item.value).collect(); /// values.sort(); /// assert_eq!(values, vec![20, 42]); /// # } /// ``` /// /// [`HashMap`]: std::collections::HashMap #[inline] pub fn iter(&self) -> Iter<'_, T> { Iter::new(&self.items) } /// Iterates over the items in the map, allowing for mutation. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not /// guaranteed to be stable. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 20 }).unwrap(); /// /// for mut item in map.iter_mut() { /// item.value *= 2; /// } /// /// assert_eq!(map.get("foo").unwrap().value, 84); /// assert_eq!(map.get("bar").unwrap().value, 40); /// # } /// ``` /// /// [`HashMap`]: std::collections::HashMap #[inline] pub fn iter_mut(&mut self) -> IterMut<'_, T, S, A> { IterMut::new(&self.tables, &mut self.items) } /// Checks general invariants of the map. /// /// The code below always upholds these invariants, but it's useful to have /// an explicit check for tests. #[doc(hidden)] pub fn validate( &self, compactness: ValidateCompact, ) -> Result<(), ValidationError> where T: fmt::Debug, { self.items.validate(compactness)?; self.tables.validate(self.len(), compactness)?; // Check that the indexes are all correct. for (&ix, item) in self.items.iter() { let key = item.key(); let Some(ix1) = self.find_index(&key) else { return Err(ValidationError::general(format!( "item at index {ix} has no key1 index" ))); }; if ix1 != ix { return Err(ValidationError::General(format!( "item at index {ix} has mismatched indexes: ix1: {ix1}", ))); } } Ok(()) } /// Inserts a value into the map, removing and returning the conflicting /// item, if any. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// /// // First insertion returns None /// let old = map.insert_overwrite(Item { id: "foo".to_string(), value: 42 }); /// assert!(old.is_none()); /// /// // Second insertion with same key returns the old value /// let old = map.insert_overwrite(Item { id: "foo".to_string(), value: 100 }); /// assert_eq!(old.unwrap().value, 42); /// assert_eq!(map.get("foo").unwrap().value, 100); /// # } /// ``` #[doc(alias = "insert")] pub fn insert_overwrite(&mut self, value: T) -> Option { // Trying to write this function for maximal efficiency can get very // tricky, requiring delicate handling of indexes. We follow a very // simple approach instead: // // 1. Remove items corresponding to the key that is already in the map. // 2. Add the item to the map. let duplicate = self.remove(&value.key()); if self.insert_unique(value).is_err() { // We should never get here, because we just removed all the // duplicates. panic!("insert_unique failed after removing duplicates"); } duplicate } /// Inserts a value into the set, returning an error if any duplicates were /// added. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// /// // First insertion succeeds /// assert!( /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).is_ok() /// ); /// /// // Second insertion with different key succeeds /// assert!( /// map.insert_unique(Item { id: "bar".to_string(), value: 20 }).is_ok() /// ); /// /// // Third insertion with duplicate key fails /// assert!( /// map.insert_unique(Item { id: "foo".to_string(), value: 100 }).is_err() /// ); /// # } /// ``` pub fn insert_unique( &mut self, value: T, ) -> Result<(), DuplicateItem> { let _ = self.insert_unique_impl(value)?; Ok(()) } /// Returns true if the map contains the given key. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// /// assert!(map.contains_key("foo")); /// assert!(!map.contains_key("bar")); /// # } /// ``` pub fn contains_key<'a, Q>(&'a self, key1: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.find_index(key1).is_some() } /// Gets a reference to the value associated with the given key. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// /// assert_eq!(map.get("foo").unwrap().value, 42); /// assert!(map.get("bar").is_none()); /// # } /// ``` pub fn get<'a, Q>(&'a self, key: &Q) -> Option<&'a T> where Q: ?Sized + Hash + Equivalent>, { self.find_index(key).map(|ix| &self.items[ix]) } /// Gets a mutable reference to the value associated with the given key. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// /// if let Some(mut item) = map.get_mut("foo") { /// item.value = 100; /// } /// /// assert_eq!(map.get("foo").unwrap().value, 100); /// assert!(map.get_mut("bar").is_none()); /// # } /// ``` pub fn get_mut<'a, Q>(&'a mut self, key: &Q) -> Option> where Q: ?Sized + Hash + Equivalent>, { let (dormant_map, index) = { let (map, dormant_map) = DormantMutRef::new(self); let index = map.find_index(key)?; (dormant_map, index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; let item = &mut awakened_map.items[index]; let state = awakened_map.tables.state.clone(); let hashes = awakened_map.tables.make_hash(item); Some(RefMut::new(state, hashes, item)) } /// Removes an item from the map by its key. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// /// let removed = map.remove("foo"); /// assert_eq!(removed.unwrap().value, 42); /// assert!(map.is_empty()); /// /// // Removing non-existent key returns None /// assert!(map.remove("bar").is_none()); /// # } /// ``` pub fn remove<'a, Q>(&'a mut self, key: &Q) -> Option where Q: ?Sized + Hash + Equivalent>, { let (dormant_map, remove_index) = { let (map, dormant_map) = DormantMutRef::new(self); let remove_index = map.find_index(key)?; (dormant_map, remove_index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; let value = awakened_map .items .remove(remove_index) .expect("items missing key1 that was just retrieved"); // Remove the value from the tables. let state = &awakened_map.tables.state; let Ok(item1) = awakened_map.tables.key_to_item.find_entry( state, &value.key(), |index| { if index == remove_index { value.key() } else { awakened_map.items[index].key() } }, ) else { // The item was not found. panic!("we just looked this item up"); }; item1.remove(); Some(value) } /// Retrieves an entry by its key. /// /// Due to borrow checker limitations, this always accepts an owned key /// rather than a borrowed form of it. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// /// // Use entry API for conditional insertion /// map.entry("foo").or_insert(Item { id: "foo".to_string(), value: 42 }); /// map.entry("bar").or_insert(Item { id: "bar".to_string(), value: 20 }); /// /// assert_eq!(map.len(), 2); /// # } /// ``` pub fn entry<'a>(&'a mut self, key: T::Key<'_>) -> Entry<'a, T, S, A> { // Why does this always take an owned key? Well, it would seem like we // should be able to pass in any Q that is equivalent. That results in // *this* code compiling fine, but callers have trouble using it because // the borrow checker believes the keys are borrowed for the full 'a // rather than a shorter lifetime. // // By accepting owned keys, we can use the upcast functions to convert // them to a shorter lifetime (so this function accepts T::Key<'_> // rather than T::Key<'a>). // // Really, the solution here is to allow GATs to require covariant // parameters. If that were allowed, the borrow checker should be able // to figure out that keys don't need to be borrowed for the full 'a, // just for some shorter lifetime. let (map, dormant_map) = DormantMutRef::new(self); let key = T::upcast_key(key); { // index is explicitly typed to show that it has a trivial Drop impl // that doesn't capture anything from map. let index: Option = map.tables.key_to_item.find_index( &map.tables.state, &key, |index| map.items[index].key(), ); if let Some(index) = index { drop(key); return Entry::Occupied( // SAFETY: `map` is not used after this point. unsafe { OccupiedEntry::new(dormant_map, index) }, ); } } let hash = map.make_key_hash(&key); Entry::Vacant( // SAFETY: `map` is not used after this point. unsafe { VacantEntry::new(dormant_map, hash) }, ) } /// Retains only the elements specified by the predicate. /// /// In other words, remove all items `T` for which `f(RefMut)` returns /// false. The elements are visited in an arbitrary order. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 20 }).unwrap(); /// map.insert_unique(Item { id: "baz".to_string(), value: 99 }).unwrap(); /// /// // Retain only items where value is greater than 30 /// map.retain(|item| item.value > 30); /// /// assert_eq!(map.len(), 2); /// assert_eq!(map.get("foo").unwrap().value, 42); /// assert_eq!(map.get("baz").unwrap().value, 99); /// assert!(map.get("bar").is_none()); /// # } /// ``` pub fn retain<'a, F>(&'a mut self, mut f: F) where F: FnMut(RefMut<'a, T, S>) -> bool, { let hash_state = self.tables.state.clone(); let (_, mut dormant_items) = DormantMutRef::new(&mut self.items); self.tables.key_to_item.retain(|index| { let (item, dormant_items) = { // SAFETY: All uses of `items` ended in the previous iteration. let items = unsafe { dormant_items.reborrow() }; let (items, dormant_items) = DormantMutRef::new(items); let item: &'a mut T = items .get_mut(index) .expect("all indexes are present in self.items"); (item, dormant_items) }; let (hash, dormant_item) = { let (item, dormant_item): (&'a mut T, _) = DormantMutRef::new(item); // Use T::key(item) rather than item.key() to force the key // trait function to be called for T rather than &mut T. let key = T::key(item); let hash = hash_state.hash_one(key); (MapHash::new(hash), dormant_item) }; let retain = { // SAFETY: The original item is no longer used after the second // block above. dormant_items, from which item is derived, is // currently dormant. let item = unsafe { dormant_item.awaken() }; let ref_mut = RefMut::new(hash_state.clone(), hash, item); f(ref_mut) }; if retain { true } else { // SAFETY: The original items is no longer used after the first // block above, and item + dormant item have been used above. let items = unsafe { dormant_items.awaken() }; items.remove(index); false } }); } fn find_index<'a, Q>(&'a self, k: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { self.tables .key_to_item .find_index(&self.tables.state, k, |index| self.items[index].key()) } fn make_hash(&self, item: &T) -> MapHash { self.tables.make_hash(item) } fn make_key_hash(&self, key: &T::Key<'_>) -> MapHash { self.tables.make_key_hash::(key) } pub(super) fn get_by_index(&self, index: usize) -> Option<&T> { self.items.get(index) } pub(super) fn get_by_index_mut( &mut self, index: usize, ) -> Option> { let state = self.tables.state.clone(); let hashes = self.make_hash(&self.items[index]); let item = &mut self.items[index]; Some(RefMut::new(state, hashes, item)) } pub(super) fn insert_unique_impl( &mut self, value: T, ) -> Result> { let mut duplicates = BTreeSet::new(); // Check for duplicates *before* inserting the new item, because we // don't want to partially insert the new item and then have to roll // back. let key = value.key(); let state = &self.tables.state; let entry = match self .tables .key_to_item .entry(state, key, |index| self.items[index].key()) { hash_table::Entry::Occupied(slot) => { duplicates.insert(*slot.get()); None } hash_table::Entry::Vacant(slot) => Some(slot), }; if !duplicates.is_empty() { return Err(DuplicateItem::__internal_new( value, duplicates.iter().map(|ix| &self.items[*ix]).collect(), )); } let next_index = self.items.insert_at_next_index(value); entry.unwrap().insert(next_index); Ok(next_index) } pub(super) fn remove_by_index(&mut self, remove_index: usize) -> Option { let value = self.items.remove(remove_index)?; // Remove the value from the tables. let state = &self.tables.state; let Ok(item) = self.tables.key_to_item.find_entry(state, &value.key(), |index| { if index == remove_index { value.key() } else { self.items[index].key() } }) else { // The item was not found. panic!("we just looked this item up"); }; item.remove(); Some(value) } pub(super) fn replace_at_index(&mut self, index: usize, value: T) -> T { // We check the key before removing it, to avoid leaving the map in an // inconsistent state. let old_key = self.get_by_index(index).expect("index is known to be valid").key(); if T::upcast_key(old_key) != value.key() { panic!( "must insert a value with \ the same key used to create the entry" ); } // Now that we know the key is the same, we can replace the value // directly without needing to tweak any tables. self.items.replace(index, value) } } impl<'a, T, S: Clone + BuildHasher, A: Allocator> fmt::Debug for IdHashMap where T: IdHashItem + fmt::Debug, T::Key<'a>: fmt::Debug, T: 'a, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut map = f.debug_map(); for item in self.iter() { let key = item.key(); // SAFETY: // // * Lifetime extension: for a type T and two lifetime params 'a and // 'b, T<'a> and T<'b> aren't guaranteed to have the same layout, // but (a) that is true today and (b) it would be shocking and // break half the Rust ecosystem if that were to change in the // future. // * We only use key within the scope of this block before immediately // dropping it. In particular, map.entry calls key.fmt() without // holding a reference to it. let key: T::Key<'a> = unsafe { core::mem::transmute::, T::Key<'a>>(key) }; map.entry(&key, item); } map.finish() } } impl PartialEq for IdHashMap { fn eq(&self, other: &Self) -> bool { // Implementing PartialEq for IdHashMap is tricky because IdHashMap is // not semantically like an IndexMap: two maps are equivalent even if // their items are in a different order. In other words, any permutation // of items is equivalent. // // We also can't sort the items because they're not necessarily Ord. // // So we write a custom equality check that checks that each key in one // map points to the same item as in the other map. if self.items.len() != other.items.len() { return false; } // Walk over all the items in the first map and check that they point to // the same item in the second map. for item in self.items.values() { let k1 = item.key(); // Check that the indexes are the same in the other map. let Some(other_ix) = other.find_index(&k1) else { return false; }; // Check that the other map's item is the same as this map's // item. (This is what we use the `PartialEq` bound on T for.) // // Because we've checked that other_ix is Some, we know that it is // valid and points to the expected item. let other_item = &other.items[other_ix]; if item != other_item { return false; } } true } } // The Eq bound on T ensures that the TriHashMap forms an equivalence class. impl Eq for IdHashMap { } /// The `Extend` implementation overwrites duplicates. In the future, there will /// also be an `extend_unique` method that will return an error. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// /// let new_items = vec![ /// Item { id: "foo".to_string(), value: 100 }, // overwrites existing /// Item { id: "bar".to_string(), value: 20 }, // new item /// ]; /// /// map.extend(new_items); /// assert_eq!(map.len(), 2); /// assert_eq!(map.get("foo").unwrap().value, 100); // overwritten /// assert_eq!(map.get("bar").unwrap().value, 20); // new /// /// # } /// ``` impl Extend for IdHashMap { fn extend>(&mut self, iter: I) { // Keys may already be present in the map, or multiple times in the // iterator. Reserve the entire hint lower bound if the map is empty. // Otherwise reserve half the hint (rounded up), so the map will only // resize twice in the worst case. let iter = iter.into_iter(); let reserve = if self.is_empty() { iter.size_hint().0 } else { iter.size_hint().0.div_ceil(2) }; self.reserve(reserve); for item in iter { self.insert_overwrite(item); } } } impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> IntoIterator for &'a IdHashMap { type Item = &'a T; type IntoIter = Iter<'a, T>; /// Creates an iterator over references to the items in the map. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 20 }).unwrap(); /// /// let mut values: Vec = /// (&map).into_iter().map(|item| item.value).collect(); /// values.sort(); /// assert_eq!(values, vec![20, 42]); /// # } /// ``` #[inline] fn into_iter(self) -> Self::IntoIter { self.iter() } } impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> IntoIterator for &'a mut IdHashMap { type Item = RefMut<'a, T, S>; type IntoIter = IterMut<'a, T, S, A>; /// Creates an iterator over mutable references to the items in the map. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 20 }).unwrap(); /// /// for mut item in &mut map { /// item.value *= 2; /// } /// /// assert_eq!(map.get("foo").unwrap().value, 84); /// assert_eq!(map.get("bar").unwrap().value, 40); /// # } /// ``` #[inline] fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl IntoIterator for IdHashMap { type Item = T; type IntoIter = IntoIter; /// Consumes the map and creates an iterator over the owned items. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 20 }).unwrap(); /// /// let mut values: Vec = map.into_iter().map(|item| item.value).collect(); /// values.sort(); /// assert_eq!(values, vec![20, 42]); /// # } /// ``` #[inline] fn into_iter(self) -> Self::IntoIter { IntoIter::new(self.items) } } /// The `FromIterator` implementation for `IdHashMap` overwrites duplicate /// items. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let items = vec![ /// Item { id: "foo".to_string(), value: 42 }, /// Item { id: "bar".to_string(), value: 20 }, /// Item { id: "foo".to_string(), value: 100 }, // duplicate key, overwrites /// ]; /// /// let map: IdHashMap = items.into_iter().collect(); /// assert_eq!(map.len(), 2); /// assert_eq!(map.get("foo").unwrap().value, 100); // last value wins /// assert_eq!(map.get("bar").unwrap().value, 20); /// # } /// ``` impl FromIterator for IdHashMap { fn from_iter>(iter: I) -> Self { let mut map = IdHashMap::default(); for item in iter { map.insert_overwrite(item); } map } } iddqd-0.3.17/src/id_hash_map/iter.rs000064400000000000000000000076561046102023000153530ustar 00000000000000use super::{RefMut, tables::IdHashMapTables}; use crate::{ DefaultHashBuilder, IdHashItem, support::{ alloc::{AllocWrapper, Allocator, Global}, item_set::ItemSet, }, }; use core::{hash::BuildHasher, iter::FusedIterator}; use hashbrown::hash_map; /// An iterator over the elements of a [`IdHashMap`] by shared reference. /// Created by [`IdHashMap::iter`]. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed /// to be stable. /// /// [`IdHashMap`]: crate::IdHashMap /// [`IdHashMap::iter`]: crate::IdHashMap::iter /// [`HashMap`]: std::collections::HashMap #[derive(Clone, Debug, Default)] pub struct Iter<'a, T: IdHashItem> { inner: hash_map::Values<'a, usize, T>, } impl<'a, T: IdHashItem> Iter<'a, T> { pub(crate) fn new(items: &'a ItemSet) -> Self { Self { inner: items.values() } } } impl<'a, T: IdHashItem> Iterator for Iter<'a, T> { type Item = &'a T; #[inline] fn next(&mut self) -> Option { self.inner.next() } } impl ExactSizeIterator for Iter<'_, T> { #[inline] fn len(&self) -> usize { self.inner.len() } } // hash_map::Iter is a FusedIterator, so Iter is as well. impl FusedIterator for Iter<'_, T> {} /// An iterator over the elements of a [`IdHashMap`] by mutable reference. /// Created by [`IdHashMap::iter_mut`]. /// /// This iterator returns [`RefMut`] instances. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed /// to be stable. /// /// [`IdHashMap`]: crate::IdHashMap /// [`IdHashMap::iter_mut`]: crate::IdHashMap::iter_mut /// [`HashMap`]: std::collections::HashMap #[derive(Debug)] pub struct IterMut< 'a, T: IdHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { tables: &'a IdHashMapTables, inner: hash_map::ValuesMut<'a, usize, T>, } impl<'a, T: IdHashItem, S: BuildHasher, A: Allocator> IterMut<'a, T, S, A> { pub(super) fn new( tables: &'a IdHashMapTables, items: &'a mut ItemSet, ) -> Self { Self { tables, inner: items.values_mut() } } } impl<'a, T: IdHashItem, S: Clone + BuildHasher, A: Allocator> Iterator for IterMut<'a, T, S, A> { type Item = RefMut<'a, T, S>; #[inline] fn next(&mut self) -> Option { let next = self.inner.next()?; let hashes = self.tables.make_hash(next); Some(RefMut::new(self.tables.state.clone(), hashes, next)) } } impl ExactSizeIterator for IterMut<'_, T, S, A> { #[inline] fn len(&self) -> usize { self.inner.len() } } // hash_map::IterMut is a FusedIterator, so IterMut is as well. impl FusedIterator for IterMut<'_, T, S, A> { } /// An iterator over the elements of a [`IdHashMap`] by ownership. Created by /// [`IdHashMap::into_iter`]. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed /// to be stable. /// /// [`IdHashMap`]: crate::IdHashMap /// [`IdHashMap::into_iter`]: crate::IdHashMap::into_iter /// [`HashMap`]: std::collections::HashMap #[derive(Debug)] pub struct IntoIter { inner: hash_map::IntoValues>, } impl IntoIter { pub(crate) fn new(items: ItemSet) -> Self { Self { inner: items.into_values() } } } impl Iterator for IntoIter { type Item = T; #[inline] fn next(&mut self) -> Option { self.inner.next() } } impl ExactSizeIterator for IntoIter { #[inline] fn len(&self) -> usize { self.inner.len() } } // hash_map::IterMut is a FusedIterator, so IterMut is as well. impl FusedIterator for IntoIter {} iddqd-0.3.17/src/id_hash_map/mod.rs000064400000000000000000000016411046102023000151530ustar 00000000000000//! A hash map where keys are part of the values. //! //! For more information, see [`IdHashMap`]. #[cfg(feature = "daft")] mod daft_impls; mod entry; pub(crate) mod imp; mod iter; #[cfg(feature = "proptest")] mod proptest_impls; mod ref_mut; #[cfg(feature = "schemars08")] mod schemars_impls; #[cfg(feature = "serde")] mod serde_impls; mod tables; pub(crate) mod trait_defs; #[cfg(feature = "daft")] pub use daft_impls::Diff; pub use entry::{Entry, OccupiedEntry, VacantEntry}; pub use imp::IdHashMap; pub use iter::{IntoIter, Iter, IterMut}; #[cfg(all(feature = "proptest", feature = "default-hasher"))] pub use proptest_impls::prop_strategy; #[cfg(feature = "proptest")] pub use proptest_impls::{ IdHashMapStrategy, IdHashMapValueTree, prop_strategy_with_hasher, prop_strategy_with_hasher_in, }; pub use ref_mut::RefMut; #[cfg(feature = "serde")] pub use serde_impls::IdHashMapAsMap; pub use trait_defs::IdHashItem; iddqd-0.3.17/src/id_hash_map/proptest_impls.rs000064400000000000000000000174611046102023000174670ustar 00000000000000//! Proptest strategies for generating [`IdHashMap`]s with random inputs. use crate::{ IdHashItem, id_hash_map::IdHashMap, support::{ alloc::{Allocator, Global}, hash_builder::DefaultHashBuilder, }, }; use core::{fmt, hash::BuildHasher}; use proptest::{ arbitrary::{Arbitrary, StrategyFor, any_with}, collection::{SizeRange, VecStrategy, VecValueTree}, strategy::{NewTree, Strategy, ValueTree}, test_runner::TestRunner, }; /// Strategy to create [`IdHashMap`]s with a length in a certain range. /// /// Created by the [`prop_strategy()`] function. #[must_use = "strategies do nothing unless used"] #[derive(Clone)] pub struct IdHashMapStrategy where T: Strategy, { inner: VecStrategy, hasher: S, allocator: A, } impl fmt::Debug for IdHashMapStrategy where T: Strategy, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("IdHashMapStrategy") .field("inner", &self.inner) .finish_non_exhaustive() } } /// Creates a strategy to generate [`IdHashMap`]s containing items drawn from /// `element` and with a size within the given range. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_hash_map, id_upcast}; /// use proptest::{ /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, /// }; /// /// #[derive(Debug, Clone, PartialEq, Eq)] /// struct Person { /// id: u32, /// name: String, /// } /// /// impl IdHashItem for Person { /// type Key<'a> = u32; /// /// fn key(&self) -> Self::Key<'_> { /// self.id /// } /// id_upcast!(); /// } /// /// // Create a strategy using a tuple and mapping it to Person. /// let strategy = id_hash_map::prop_strategy( /// (any::(), any::()) /// .prop_map(|(id, name)| Person { id, name }), /// 0..=5, /// ); /// /// // The strategy can be used in proptest contexts. /// let mut runner = TestRunner::default(); /// let _tree = strategy.new_tree(&mut runner).unwrap(); /// # } /// ``` #[cfg(feature = "default-hasher")] pub fn prop_strategy( element: T, size: impl Into, ) -> IdHashMapStrategy { IdHashMapStrategy { inner: proptest::collection::vec(element, size), hasher: DefaultHashBuilder::default(), allocator: crate::support::alloc::global_alloc(), } } /// Creates a strategy to generate [`IdHashMap`]s with a custom hasher. /// /// # Examples /// /// ``` /// use iddqd::{IdHashItem, IdHashMap, id_hash_map, id_upcast}; /// use proptest::{ /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, /// }; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, Clone, PartialEq, Eq)] /// struct Person { /// id: u32, /// name: String, /// } /// /// impl IdHashItem for Person { /// type Key<'a> = u32; /// /// fn key(&self) -> Self::Key<'_> { /// self.id /// } /// id_upcast!(); /// } /// /// // Create a strategy with a custom hasher. /// let hasher = RandomState::new(); /// let strategy = id_hash_map::prop_strategy_with_hasher( /// (any::(), any::()) /// .prop_map(|(id, name)| Person { id, name }), /// 0..=3, /// hasher, /// ); /// /// // The strategy can be used in proptest contexts. /// let mut runner = TestRunner::default(); /// let _tree = strategy.new_tree(&mut runner).unwrap(); /// ``` pub fn prop_strategy_with_hasher( element: T, size: impl Into, hasher: S, ) -> IdHashMapStrategy { let size = size.into(); IdHashMapStrategy { inner: proptest::collection::vec(element, size), hasher, allocator: crate::support::alloc::global_alloc(), } } /// Creates a strategy to generate [`IdHashMap`]s with a custom hasher and /// allocator. /// /// # Examples /// /// ``` /// # #[cfg(feature = "allocator-api2")] { /// use allocator_api2::alloc::Global; /// use iddqd::{IdHashItem, IdHashMap, id_hash_map, id_upcast}; /// use proptest::{ /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, /// }; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, Clone, PartialEq, Eq)] /// struct Person { /// id: u32, /// name: String, /// } /// /// impl IdHashItem for Person { /// type Key<'a> = u32; /// /// fn key(&self) -> Self::Key<'_> { /// self.id /// } /// id_upcast!(); /// } /// /// // Create a strategy with custom hasher and allocator. /// let hasher = RandomState::new(); /// let allocator = Global; /// let strategy = id_hash_map::prop_strategy_with_hasher_in( /// (any::(), any::()) /// .prop_map(|(id, name)| Person { id, name }), /// 1..=4, /// hasher, /// allocator, /// ); /// /// // The strategy can be used in proptest contexts. /// let mut runner = TestRunner::default(); /// let _tree = strategy.new_tree(&mut runner).unwrap(); /// # } /// ``` pub fn prop_strategy_with_hasher_in( element: T, size: impl Into, hasher: S, allocator: A, ) -> IdHashMapStrategy { let size = size.into(); IdHashMapStrategy { inner: proptest::collection::vec(element, size), hasher, allocator, } } impl<'a, T, S, A> Strategy for IdHashMapStrategy where T: Strategy, T::Value: 'a + IdHashItem, ::Key<'a>: fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Tree = IdHashMapValueTree; type Value = IdHashMap; fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let inner = self.inner.new_tree(runner)?; Ok(IdHashMapValueTree { inner, hasher: self.hasher.clone(), allocator: self.allocator.clone(), }) } } /// `ValueTree` corresponding to [`IdHashMapStrategy`]. #[derive(Clone)] pub struct IdHashMapValueTree where T: ValueTree, { inner: VecValueTree, hasher: S, allocator: A, } impl fmt::Debug for IdHashMapValueTree where T: ValueTree + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("IdHashMapValueTree") .field("inner", &self.inner) .finish_non_exhaustive() } } impl<'a, T, S, A> ValueTree for IdHashMapValueTree where T: ValueTree, T::Value: 'a + IdHashItem, ::Key<'a>: fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Value = IdHashMap; fn current(&self) -> Self::Value { let items = self.inner.current(); let mut map = IdHashMap::with_capacity_and_hasher_in( items.len(), self.hasher.clone(), self.allocator.clone(), ); for item in items { // Use insert_overwrite to handle duplicate keys. map.insert_overwrite(item); } map } fn simplify(&mut self) -> bool { self.inner.simplify() } fn complicate(&mut self) -> bool { self.inner.complicate() } } impl<'a, T, S, A> Arbitrary for IdHashMap where T: 'a + IdHashItem + Arbitrary, ::Key<'a>: fmt::Debug, S: Clone + BuildHasher + Default, A: Clone + Allocator + Default, { type Parameters = (SizeRange, T::Parameters); type Strategy = IdHashMapStrategy, S, A>; fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { let (size, element_args) = args; prop_strategy_with_hasher_in( any_with::(element_args), size, S::default(), A::default(), ) } } iddqd-0.3.17/src/id_hash_map/ref_mut.rs000064400000000000000000000103601046102023000160330ustar 00000000000000use crate::{DefaultHashBuilder, IdHashItem, support::map_hash::MapHash}; use core::{ fmt, hash::BuildHasher, ops::{Deref, DerefMut}, }; /// A mutable reference to an [`IdHashMap`] item. /// /// This is a wrapper around a `&mut T` that panics when dropped, if the /// borrowed value's keys have changed since the wrapper was created. /// /// # Change detection /// /// It is illegal to change the key of a borrowed `&mut T`. `RefMut` attempts to /// enforce this invariant. /// /// `RefMut` stores the `Hash` output of the key at creation time, and /// recomputes this hash when it is dropped or when [`Self::into_ref`] is /// called. If a key changes, there's a small but non-negligible chance that its /// hash value stays the same[^collision-chance]. In that case, as long as the /// new key is not the same as another existing one, internal invariants are not /// violated and the [`IdHashMap`] will continue to work correctly. (But don't /// rely on this!) /// /// It is also possible to deliberately write pathological `Hash` /// implementations that collide more often. (Don't do this either.) /// /// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is /// called on it. If the key is changed and `mem::forget` is then called on the /// `RefMut`, the [`IdHashMap`] will stop functioning correctly. This will not /// introduce memory safety issues, however. /// /// The issues here are similar to using interior mutability (e.g. `RefCell` or /// `Mutex`) to mutate keys in a regular `HashMap`. /// /// [`mem::forget`]: std::mem::forget /// /// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability /// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday /// problem], the probability of a collision by chance reaches 10⁻⁶ within /// around 6 × 10⁶ elements. /// /// [`IdHashMap`]: crate::IdHashMap /// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table pub struct RefMut< 'a, T: IdHashItem, S: Clone + BuildHasher = DefaultHashBuilder, > { inner: Option>, } impl<'a, T: IdHashItem, S: Clone + BuildHasher> RefMut<'a, T, S> { pub(super) fn new(state: S, hash: MapHash, borrowed: &'a mut T) -> Self { Self { inner: Some(RefMutInner { state, hash, borrowed }) } } /// Borrows self into a shorter-lived `RefMut`. /// /// This `RefMut` will also check hash equality on drop. pub fn reborrow(&mut self) -> RefMut<'_, T, S> { let inner = self.inner.as_mut().unwrap(); let borrowed = &mut *inner.borrowed; RefMut::new(inner.state.clone(), inner.hash.clone(), borrowed) } /// Converts this `RefMut` into a `&'a T`. pub fn into_ref(mut self) -> &'a T { let inner = self.inner.take().unwrap(); inner.into_ref() } } impl Drop for RefMut<'_, T, S> { fn drop(&mut self) { if let Some(inner) = self.inner.take() { inner.into_ref(); } } } impl Deref for RefMut<'_, T, S> { type Target = T; fn deref(&self) -> &Self::Target { self.inner.as_ref().unwrap().borrowed } } impl DerefMut for RefMut<'_, T, S> { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.as_mut().unwrap().borrowed } } impl fmt::Debug for RefMut<'_, T, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.inner { Some(ref inner) => inner.fmt(f), None => { f.debug_struct("RefMut").field("borrowed", &"missing").finish() } } } } struct RefMutInner<'a, T: IdHashItem, S> { state: S, hash: MapHash, borrowed: &'a mut T, } impl<'a, T: IdHashItem, S: BuildHasher> RefMutInner<'a, T, S> { fn into_ref(self) -> &'a T { if !self.hash.is_same_hash(&self.state, self.borrowed.key()) { panic!("key changed during RefMut borrow"); } self.borrowed } } impl fmt::Debug for RefMutInner<'_, T, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.borrowed.fmt(f) } } iddqd-0.3.17/src/id_hash_map/schemars_impls.rs000064400000000000000000000023531046102023000174060ustar 00000000000000//! Schemars implementations for IdHashMap. use crate::{ id_hash_map::{ imp::IdHashMap, serde_impls::IdHashMapAsMap, trait_defs::IdHashItem, }, support::{ alloc::Allocator, schemars_utils::{create_map_schema, create_object_schema}, }, }; use alloc::string::String; use schemars::{JsonSchema, gen::SchemaGenerator, schema::Schema}; impl JsonSchema for IdHashMap where T: JsonSchema + IdHashItem, A: Allocator, { fn schema_name() -> String { alloc::format!("IdHashMap_of_{}", T::schema_name()) } fn json_schema(generator: &mut SchemaGenerator) -> Schema { create_map_schema::("IdHashMap", "iddqd::IdHashMap", generator) } fn is_referenceable() -> bool { false } } impl JsonSchema for IdHashMapAsMap where T: JsonSchema + IdHashItem, A: Allocator, { fn schema_name() -> String { alloc::format!("IdHashMapAsMap_of_{}", T::schema_name()) } fn json_schema(generator: &mut SchemaGenerator) -> Schema { create_object_schema::( "IdHashMapAsMap", "iddqd::IdHashMap", generator, ) } fn is_referenceable() -> bool { false } } iddqd-0.3.17/src/id_hash_map/serde_impls.rs000064400000000000000000000243641046102023000167110ustar 00000000000000use crate::{ DefaultHashBuilder, IdHashItem, IdHashMap, support::alloc::{Allocator, Global}, }; use core::{fmt, hash::BuildHasher, marker::PhantomData}; use serde_core::{ Deserialize, Deserializer, Serialize, Serializer, de::{MapAccess, SeqAccess, Visitor}, ser::SerializeMap, }; /// An `IdHashMap` serializes to the list of items. Items are serialized in /// arbitrary order. /// /// Serializing as a list of items rather than as a map works around the lack of /// non-string keys in formats like JSON. /// /// To serialize as a map instead, see [`IdHashMapAsMap`]. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// # use iddqd_test_utils::serde_json; /// use serde::{Deserialize, Serialize}; /// /// #[derive(Debug, Serialize)] /// struct Item { /// id: u32, /// name: String, /// email: String, /// } /// /// // This is a complex key, so it can't be a JSON map key. /// #[derive(Eq, Hash, PartialEq)] /// struct ComplexKey<'a> { /// id: u32, /// email: &'a str, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = ComplexKey<'a>; /// fn key(&self) -> Self::Key<'_> { /// ComplexKey { id: self.id, email: &self.email } /// } /// id_upcast!(); /// } /// /// let mut map = IdHashMap::::new(); /// map.insert_unique(Item { /// id: 1, /// name: "Alice".to_string(), /// email: "alice@example.com".to_string(), /// }) /// .unwrap(); /// /// // The map is serialized as a list of items. /// let serialized = serde_json::to_string(&map).unwrap(); /// assert_eq!( /// serialized, /// r#"[{"id":1,"name":"Alice","email":"alice@example.com"}]"#, /// ); /// # } /// ``` impl Serialize for IdHashMap where T: Serialize, { fn serialize( &self, serializer: Ser, ) -> Result { // Serialize just the items -- don't serialize the indexes. We'll // rebuild the indexes on deserialization. self.items.serialize(serializer) } } /// The `Deserialize` impl for `IdHashMap` deserializes from either a sequence /// or a map of items, then rebuilds the indexes and produces an error if there /// are any duplicates. /// /// In case a map is deserialized, the key is not deserialized or verified /// against the value. (In general, verification is not possible because the key /// type has a lifetime parameter embedded in it.) /// /// The `fmt::Debug` bound on `T` ensures better error reporting. impl< 'de, T: IdHashItem + fmt::Debug, S: Clone + BuildHasher + Default, A: Default + Clone + Allocator, > Deserialize<'de> for IdHashMap where T: Deserialize<'de>, { fn deserialize>( deserializer: D, ) -> Result { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher: S::default(), alloc: A::default(), }) } } impl< 'de, T: IdHashItem + fmt::Debug + Deserialize<'de>, S: Clone + BuildHasher, A: Clone + Allocator, > IdHashMap { /// Deserializes from a list of items, allocating new storage within the /// provided allocator. pub fn deserialize_in>( deserializer: D, alloc: A, ) -> Result where S: Default, { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher: S::default(), alloc, }) } /// Deserializes from a list of items, with the given hasher, using the /// default allocator. pub fn deserialize_with_hasher>( deserializer: D, hasher: S, ) -> Result where A: Default, { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher, alloc: A::default(), }) } /// Deserializes from a list of items, with the given hasher, and allocating /// new storage within the provided allocator. pub fn deserialize_with_hasher_in>( deserializer: D, hasher: S, alloc: A, ) -> Result { // First, deserialize the items. deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher, alloc, }) } } struct SeqVisitor { _marker: PhantomData T>, hasher: S, alloc: A, } impl<'de, T, S, A> Visitor<'de> for SeqVisitor where T: IdHashItem + Deserialize<'de> + fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Value = IdHashMap; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .write_str("a sequence or map of items representing an IdHashMap") } fn visit_seq( self, mut seq: Access, ) -> Result where Access: SeqAccess<'de>, { let mut map = match seq.size_hint() { Some(size) => IdHashMap::with_capacity_and_hasher_in( size, self.hasher, self.alloc, ), None => IdHashMap::with_hasher_in(self.hasher, self.alloc), }; while let Some(element) = seq.next_element()? { map.insert_unique(element) .map_err(serde_core::de::Error::custom)?; } Ok(map) } fn visit_map( self, mut map_access: Access, ) -> Result where Access: MapAccess<'de>, { let mut map = match map_access.size_hint() { Some(size) => IdHashMap::with_capacity_and_hasher_in( size, self.hasher, self.alloc, ), None => IdHashMap::with_hasher_in(self.hasher, self.alloc), }; while let Some((_, value)) = map_access.next_entry::()? { map.insert_unique(value).map_err(serde_core::de::Error::custom)?; } Ok(map) } } /// Marker type for [`IdHashMap`] serialized as a map, for use with serde's /// `with` attribute. /// /// # Examples /// /// Use with serde's `with` attribute: /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{ /// IdHashItem, IdHashMap, id_hash_map::IdHashMapAsMap, id_upcast, /// }; /// use serde::{Deserialize, Serialize}; /// /// #[derive(Debug, Serialize, Deserialize)] /// struct Item { /// id: u32, /// name: String, /// } /// /// impl IdHashItem for Item { /// type Key<'a> = u32; /// fn key(&self) -> Self::Key<'_> { /// self.id /// } /// id_upcast!(); /// } /// /// #[derive(Serialize, Deserialize)] /// struct Config { /// #[serde(with = "IdHashMapAsMap")] /// items: IdHashMap, /// } /// # } /// ``` /// /// # Requirements /// /// - For serialization, the key type must implement [`Serialize`]. /// - For JSON serialization, the key should be string-like or convertible to a string key. pub struct IdHashMapAsMap { #[expect(clippy::type_complexity)] _marker: PhantomData (T, S, A)>, } struct MapVisitorAsMap { _marker: PhantomData T>, hasher: S, alloc: A, } impl<'de, T, S, A> Visitor<'de> for MapVisitorAsMap where T: IdHashItem + Deserialize<'de> + fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Value = IdHashMap; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a map with items representing an IdHashMap") } fn visit_map( self, mut map_access: Access, ) -> Result where Access: MapAccess<'de>, { let mut map = match map_access.size_hint() { Some(size) => IdHashMap::with_capacity_and_hasher_in( size, self.hasher, self.alloc, ), None => IdHashMap::with_hasher_in(self.hasher, self.alloc), }; while let Some((_, value)) = map_access.next_entry::()? { map.insert_unique(value).map_err(serde_core::de::Error::custom)?; } Ok(map) } } impl IdHashMapAsMap where S: Clone + BuildHasher, A: Allocator, { /// Serializes an `IdHashMap` as a JSON object/map using `key()` as keys. pub fn serialize<'a, Ser>( map: &IdHashMap, serializer: Ser, ) -> Result where T: 'a + IdHashItem + Serialize, T::Key<'a>: Serialize, Ser: Serializer, { let mut ser_map = serializer.serialize_map(Some(map.len()))?; for item in map.iter() { let key = item.key(); // SAFETY: // // * Lifetime extension: for a type T and two lifetime params 'a and // 'b, T<'a> and T<'b> aren't guaranteed to have the same layout, // but (a) that is true today and (b) it would be shocking and // break half the Rust ecosystem if that were to change in the // future. // * We only use key within the scope of this block before // immediately dropping it. In particular, ser_map.serialize_entry // serializes the key without holding a reference to it. let key1 = unsafe { core::mem::transmute::, T::Key<'a>>(key) }; ser_map.serialize_entry(&key1, item)?; } ser_map.end() } /// Deserializes an `IdHashMap` from a JSON object/map. pub fn deserialize<'de, D>( deserializer: D, ) -> Result, D::Error> where T: IdHashItem + Deserialize<'de> + fmt::Debug, S: Default, A: Clone + Default, D: Deserializer<'de>, { deserializer.deserialize_map(MapVisitorAsMap { _marker: PhantomData, hasher: S::default(), alloc: A::default(), }) } } iddqd-0.3.17/src/id_hash_map/tables.rs000064400000000000000000000030521046102023000156440ustar 00000000000000use crate::{ IdHashItem, internal::{ValidateCompact, ValidationError}, support::{alloc::Allocator, hash_table::MapHashTable, map_hash::MapHash}, }; use core::hash::BuildHasher; #[derive(Clone, Debug, Default)] pub(super) struct IdHashMapTables { pub(super) state: S, pub(super) key_to_item: MapHashTable, } impl IdHashMapTables { #[cfg(feature = "daft")] pub(crate) fn hasher(&self) -> &S { &self.state } pub(super) const fn with_hasher_in(hasher: S, alloc: A) -> Self { Self { state: hasher, key_to_item: MapHashTable::new_in(alloc) } } pub(super) fn with_capacity_and_hasher_in( capacity: usize, hasher: S, alloc: A, ) -> Self { Self { state: hasher, key_to_item: MapHashTable::with_capacity_in(capacity, alloc), } } pub(super) fn validate( &self, expected_len: usize, compactness: ValidateCompact, ) -> Result<(), ValidationError> { self.key_to_item.validate(expected_len, compactness).map_err( |error| ValidationError::Table { name: "key_to_table", error }, )?; Ok(()) } pub(super) fn make_hash(&self, item: &T) -> MapHash { let k1 = item.key(); self.key_to_item.compute_hash(&self.state, k1) } pub(super) fn make_key_hash( &self, key: &T::Key<'_>, ) -> MapHash { self.key_to_item.compute_hash(&self.state, key) } } iddqd-0.3.17/src/id_hash_map/trait_defs.rs000064400000000000000000000051331046102023000165200ustar 00000000000000use alloc::{boxed::Box, rc::Rc, sync::Arc}; use core::hash::Hash; /// An element stored in an [`IdHashMap`]. /// /// This trait is used to define the key type for the map. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, IdHashMap, id_upcast}; /// /// // Define a struct with a key. /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct MyItem { /// id: String, /// value: u32, /// } /// /// // Implement IdHashItem for the struct. /// impl IdHashItem for MyItem { /// // Keys can borrow from the item. /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// // Create an IdHashMap and insert items. /// let mut map = IdHashMap::new(); /// map.insert_unique(MyItem { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(MyItem { id: "bar".to_string(), value: 20 }).unwrap(); /// # } /// ``` /// /// [`IdHashMap`]: crate::IdHashMap pub trait IdHashItem { /// The key type. type Key<'a>: Eq + Hash where Self: 'a; /// Retrieves the key. fn key(&self) -> Self::Key<'_>; /// Upcasts the key to a shorter lifetime, in effect asserting that the /// lifetime `'a` on [`IdHashItem::Key`] is covariant. /// /// Typically implemented via the [`id_upcast`] macro. fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short>; } macro_rules! impl_for_ref { ($type:ty) => { impl<'b, T: 'b + ?Sized + IdHashItem> IdHashItem for $type { type Key<'a> = T::Key<'a> where Self: 'a; fn key(&self) -> Self::Key<'_> { (**self).key() } fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> where Self: 'long, { T::upcast_key(long) } } }; } impl_for_ref!(&'b T); impl_for_ref!(&'b mut T); macro_rules! impl_for_box { ($type:ty) => { impl IdHashItem for $type { type Key<'a> = T::Key<'a> where Self: 'a; fn key(&self) -> Self::Key<'_> { (**self).key() } fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> { T::upcast_key(long) } } }; } impl_for_box!(Box); impl_for_box!(Rc); impl_for_box!(Arc); iddqd-0.3.17/src/id_ord_map/daft_impls.rs000064400000000000000000000134321046102023000163600ustar 00000000000000//! `Diffable` implementation. use super::{IdOrdItem, IdOrdMap}; use crate::support::daft_utils::IdLeaf; use core::fmt; use daft::Diffable; use equivalent::Comparable; impl Diffable for IdOrdMap { type Diff<'a> = Diff<'a, T> where T: 'a; fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> { let mut diff = Diff::new(); for item in self { if let Some(other_item) = other.get(&item.key()) { diff.common.insert_overwrite(IdLeaf::new(item, other_item)); } else { diff.removed.insert_overwrite(item); } } for item in other { if !self.contains_key(&item.key()) { diff.added.insert_overwrite(item); } } diff } } /// A diff of two [`IdOrdMap`]s. /// /// Generated by the [`Diffable`] implementation for [`IdOrdMap`]. /// /// # Examples /// /// ``` /// use daft::Diffable; /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Eq, PartialEq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// // Create two IdOrdMaps with overlapping items. /// let mut map1 = IdOrdMap::new(); /// map1.insert_unique(Item { id: "a".to_string(), value: 1 }); /// map1.insert_unique(Item { id: "b".to_string(), value: 2 }); /// /// let mut map2 = IdOrdMap::new(); /// map2.insert_unique(Item { id: "b".to_string(), value: 3 }); /// map2.insert_unique(Item { id: "c".to_string(), value: 4 }); /// /// // Compute the diff between the two maps. /// let diff = map1.diff(&map2); /// /// // "a" is removed. /// assert!(diff.removed.contains_key("a")); /// // "b" is modified (value changed from 2 to 3). /// assert!(diff.is_modified("b")); /// // "c" is added. /// assert!(diff.added.contains_key("c")); /// ``` /// /// [`Diffable`]: daft::Diffable pub struct Diff<'daft, T: ?Sized + IdOrdItem> { /// Entries common to both maps. /// /// Items are stored as [`IdLeaf`]s to references. pub common: IdOrdMap>, /// Added entries. pub added: IdOrdMap<&'daft T>, /// Removed entries. pub removed: IdOrdMap<&'daft T>, } impl<'a, 'daft, T> fmt::Debug for Diff<'daft, T> where T: ?Sized + IdOrdItem + fmt::Debug, T::Key<'a>: fmt::Debug, T: 'a, 'daft: 'a, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Diff") .field("common", &self.common) .field("added", &self.added) .field("removed", &self.removed) .finish() } } impl<'daft, T: ?Sized + IdOrdItem> Diff<'daft, T> { /// Creates a new, empty `Diff`. pub fn new() -> Self { Self { common: IdOrdMap::new(), added: IdOrdMap::new(), removed: IdOrdMap::new(), } } } impl<'daft, T: ?Sized + IdOrdItem + Eq> Diff<'daft, T> { /// Returns an iterator over unchanged keys and values. pub fn unchanged(&self) -> impl Iterator + '_ { self.common .iter() .filter_map(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns true if the item corresponding to the key is unchanged. pub fn is_unchanged<'a, Q>(&'a self, key: &Q) -> bool where Q: ?Sized + Comparable>, { self.common.get(key).is_some_and(|leaf| leaf.is_unchanged()) } /// Returns the value associated with the key if it is unchanged, /// otherwise `None`. pub fn get_unchanged<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> where Q: ?Sized + Comparable>, { self.common .get(key) .and_then(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns an iterator over modified keys and values. pub fn modified(&self) -> impl Iterator> + '_ { self.common .iter() .filter_map(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns true if the value corresponding to the key is /// modified. pub fn is_modified<'a, Q>(&'a self, key: &Q) -> bool where Q: ?Sized + Comparable>, { self.common.get(key).is_some_and(|leaf| leaf.is_modified()) } /// Returns the [`IdLeaf`] associated with the key if it is modified, /// otherwise `None`. pub fn get_modified<'a, Q>(&'a self, key: &Q) -> Option> where Q: ?Sized + Comparable>, { self.common .get(key) .and_then(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns an iterator over modified keys and values, performing /// a diff on the values. /// /// This is useful when `T::Diff` is a complex type, not just a /// [`daft::Leaf`]. pub fn modified_diff(&self) -> impl Iterator> + '_ where T: Diffable, { self.modified().map(|leaf| leaf.diff_pair()) } } // Note: not deriving Default here because we don't want to require // T to be Default. impl<'daft, T: IdOrdItem> Default for Diff<'daft, T> { fn default() -> Self { Self::new() } } impl IdOrdItem for IdLeaf { type Key<'a> = T::Key<'a> where T: 'a; fn key(&self) -> Self::Key<'_> { let before_key = self.before().key(); if before_key != self.after().key() { panic!("key is different between before and after"); } before_key } #[inline] fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> { T::upcast_key(long) } } iddqd-0.3.17/src/id_ord_map/entry.rs000064400000000000000000000261551046102023000154050ustar 00000000000000use super::{IdOrdItem, IdOrdMap, RefMut}; use crate::support::borrow::DormantMutRef; use core::{fmt, hash::Hash}; /// An implementation of the Entry API for [`IdOrdMap`]. pub enum Entry<'a, T: IdOrdItem> { /// A vacant entry. Vacant(VacantEntry<'a, T>), /// An occupied entry. Occupied(OccupiedEntry<'a, T>), } impl<'a, T: IdOrdItem> fmt::Debug for Entry<'a, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Entry::Vacant(entry) => { f.debug_tuple("Vacant").field(entry).finish() } Entry::Occupied(entry) => { f.debug_tuple("Occupied").field(entry).finish() } } } } impl<'a, T: IdOrdItem> Entry<'a, T> { /// Ensures a value is in the entry by inserting the default if empty, and /// returns a shared reference to the value in the entry. /// /// # Panics /// /// Panics if the key is already present in the map. (The intention is that /// the key should be what was passed into [`IdOrdMap::entry`], but that /// isn't checked in this API due to borrow checker limitations.) #[inline] pub fn or_insert_ref(self, default: T) -> &'a T { match self { Entry::Occupied(entry) => entry.into_ref(), Entry::Vacant(entry) => entry.insert_ref(default), } } /// Ensures a value is in the entry by inserting the default if empty, and /// returns a mutable reference to the value in the entry. /// /// # Panics /// /// Panics if the key is already present in the map. (The intention is that /// the key should be what was passed into [`IdOrdMap::entry`], but that /// isn't checked in this API due to borrow checker limitations.) #[inline] pub fn or_insert(self, default: T) -> RefMut<'a, T> where T::Key<'a>: Hash, { match self { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => entry.insert(default), } } /// Ensures a value is in the entry by inserting the result of the default /// function if empty, and returns a shared reference to the value in the /// entry. /// /// # Panics /// /// Panics if the key is already present in the map. (The intention is that /// the key should be what was passed into [`IdOrdMap::entry`], but that /// isn't checked in this API due to borrow checker limitations.) #[inline] pub fn or_insert_with_ref T>(self, default: F) -> &'a T { match self { Entry::Occupied(entry) => entry.into_ref(), Entry::Vacant(entry) => entry.insert_ref(default()), } } /// Ensures a value is in the entry by inserting the result of the default /// function if empty, and returns a mutable reference to the value in the /// entry. /// /// # Panics /// /// Panics if the key is already present in the map. (The intention is that /// the key should be what was passed into [`IdOrdMap::entry`], but that /// isn't checked in this API due to borrow checker limitations.) #[inline] pub fn or_insert_with T>(self, default: F) -> RefMut<'a, T> where T::Key<'a>: Hash, { match self { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => entry.insert(default()), } } /// Provides in-place mutable access to an occupied entry before any /// potential inserts into the map. #[inline] pub fn and_modify(self, f: F) -> Self where F: FnOnce(RefMut<'_, T>), T::Key<'a>: Hash, { match self { Entry::Occupied(mut entry) => { { let (state, hash, dormant) = { // SAFETY: The safety assumption behind // `OccupiedEntry::new` guarantees that the original // reference to the map is not used at this point. let map = unsafe { entry.map.reborrow() }; let item = map .items .get_mut(entry.index) .expect("index is valid"); let (item, dormant) = DormantMutRef::new(item); let hash = map.tables.make_hash(item); let state = map.tables.state().clone(); (state, hash, dormant) }; // SAFETY: the item above is not used after this point. let awakened_item = unsafe { dormant.awaken() }; let ref_mut = RefMut::new(state, hash, awakened_item); f(ref_mut); } Entry::Occupied(entry) } Entry::Vacant(entry) => Entry::Vacant(entry), } } } /// A vacant entry. pub struct VacantEntry<'a, T: IdOrdItem> { map: DormantMutRef<'a, IdOrdMap>, } impl<'a, T: IdOrdItem> fmt::Debug for VacantEntry<'a, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("VacantEntry").finish_non_exhaustive() } } impl<'a, T: IdOrdItem> VacantEntry<'a, T> { pub(super) unsafe fn new(map: DormantMutRef<'a, IdOrdMap>) -> Self { VacantEntry { map } } /// Sets the entry to a new value, returning a shared reference to the /// value. /// /// # Panics /// /// Panics if the key is already present in the map. (The intention is that /// the key should be what was passed into [`IdOrdMap::entry`], but that /// isn't checked in this API due to borrow checker limitations.) pub fn insert_ref(self, value: T) -> &'a T { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.awaken() }; let Ok(index) = map.insert_unique_impl(value) else { panic!("key already present in map"); }; map.get_by_index(index).expect("index is known to be valid") } /// Sets the entry to a new value, returning a mutable reference to the /// value. pub fn insert(self, value: T) -> RefMut<'a, T> where T::Key<'a>: Hash, { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.awaken() }; let Ok(index) = map.insert_unique_impl(value) else { panic!("key already present in map"); }; map.get_by_index_mut(index).expect("index is known to be valid") } /// Sets the value of the entry, and returns an `OccupiedEntry`. #[inline] pub fn insert_entry(mut self, value: T) -> OccupiedEntry<'a, T> { let index = { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. let map = unsafe { self.map.reborrow() }; let Ok(index) = map.insert_unique_impl(value) else { panic!("key already present in map"); }; index }; // SAFETY: map, as well as anything that was borrowed from it, is // dropped once the above block exits. unsafe { OccupiedEntry::new(self.map, index) } } } /// A view into an occupied entry in an [`IdOrdMap`]. Part of the [`Entry`] /// enum. pub struct OccupiedEntry<'a, T: IdOrdItem> { map: DormantMutRef<'a, IdOrdMap>, // index is a valid index into the map's internal hash table. index: usize, } impl<'a, T: IdOrdItem> fmt::Debug for OccupiedEntry<'a, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("OccupiedEntry") .field("index", &self.index) .finish_non_exhaustive() } } impl<'a, T: IdOrdItem> OccupiedEntry<'a, T> { /// # Safety /// /// After self is created, the original reference created by /// `DormantMutRef::new` must not be used. pub(super) unsafe fn new( map: DormantMutRef<'a, IdOrdMap>, index: usize, ) -> Self { OccupiedEntry { map, index } } /// Gets a reference to the value. /// /// If you need a reference to `T` that may outlive the destruction of the /// `Entry` value, see [`into_ref`](Self::into_ref). pub fn get(&self) -> &T { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. unsafe { self.map.reborrow_shared() } .get_by_index(self.index) .expect("index is known to be valid") } /// Gets a mutable reference to the value. /// /// If you need a reference to `T` that may outlive the destruction of the /// `Entry` value, see [`into_mut`](Self::into_mut). pub fn get_mut<'b>(&'b mut self) -> RefMut<'b, T> where T::Key<'b>: Hash, { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. unsafe { self.map.reborrow() } .get_by_index_mut(self.index) .expect("index is known to be valid") } /// Converts self into a reference to the value. /// /// If you need multiple references to the `OccupiedEntry`, see /// [`get`](Self::get). pub fn into_ref(self) -> &'a T { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. unsafe { self.map.awaken() } .get_by_index(self.index) .expect("index is known to be valid") } /// Converts self into a mutable reference to the value. /// /// If you need multiple references to the `OccupiedEntry`, see /// [`get_mut`](Self::get_mut). pub fn into_mut(self) -> RefMut<'a, T> where T::Key<'a>: Hash, { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. unsafe { self.map.awaken() } .get_by_index_mut(self.index) .expect("index is known to be valid") } /// Sets the entry to a new value, returning the old value. /// /// # Panics /// /// Panics if `value.key()` is different from the key of the entry. pub fn insert(&mut self, value: T) -> T { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. // // Note that `replace_at_index` panics if the keys don't match. unsafe { self.map.reborrow() }.replace_at_index(self.index, value) } /// Takes ownership of the value from the map. pub fn remove(mut self) -> T { // SAFETY: The safety assumption behind `Self::new` guarantees that the // original reference to the map is not used at this point. unsafe { self.map.reborrow() } .remove_by_index(self.index) .expect("index is known to be valid") } } iddqd-0.3.17/src/id_ord_map/imp.rs000064400000000000000000001356031046102023000150300ustar 00000000000000use super::{ Entry, IdOrdItem, IntoIter, Iter, IterMut, OccupiedEntry, RefMut, VacantEntry, tables::IdOrdMapTables, }; use crate::{ errors::DuplicateItem, internal::{ValidateChaos, ValidateCompact, ValidationError}, support::{ alloc::{Global, global_alloc}, borrow::DormantMutRef, item_set::ItemSet, map_hash::MapHash, }, }; use alloc::collections::BTreeSet; use core::{ fmt, hash::{BuildHasher, Hash}, }; use equivalent::{Comparable, Equivalent}; /// An ordered map where the keys are part of the values, based on a B-Tree. /// /// The storage mechanism is a fast hash table of integer indexes to items, with /// the indexes stored in a B-Tree map. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// // Define a struct with a key. /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct MyItem { /// id: String, /// value: u32, /// } /// /// // Implement IdOrdItem for the struct. /// impl IdOrdItem for MyItem { /// // Keys can borrow from the item. /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// // Create an IdOrdMap and insert items. /// let mut map = IdOrdMap::new(); /// map.insert_unique(MyItem { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(MyItem { id: "bar".to_string(), value: 20 }).unwrap(); /// /// // Look up items by their keys. /// assert_eq!(map.get("foo").unwrap().value, 42); /// assert_eq!(map.get("bar").unwrap().value, 20); /// assert!(map.get("baz").is_none()); /// # } /// ``` #[derive(Clone)] pub struct IdOrdMap { // We don't expose an allocator trait here because it isn't stable with // std's BTreeMap. pub(super) items: ItemSet, // Invariant: the values (usize) in these tables are valid indexes into // `items`, and are a 1:1 mapping. pub(super) tables: IdOrdMapTables, } impl Default for IdOrdMap { fn default() -> Self { Self::new() } } impl IdOrdMap { /// Creates a new, empty `IdOrdMap`. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let map: IdOrdMap = IdOrdMap::new(); /// assert!(map.is_empty()); /// assert_eq!(map.len(), 0); /// ``` #[inline] pub const fn new() -> Self { Self { items: ItemSet::new(), tables: IdOrdMapTables::new() } } /// Creates a new `IdOrdMap` with the given capacity. /// /// The capacity will be used to initialize the underlying hash table. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let map: IdOrdMap = IdOrdMap::with_capacity(10); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// ``` pub fn with_capacity(capacity: usize) -> Self { Self { items: ItemSet::with_capacity_in(capacity, global_alloc()), tables: IdOrdMapTables::new(), } } /// Returns the currently allocated capacity of the map. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let map: IdOrdMap = IdOrdMap::with_capacity(10); /// assert!(map.capacity() >= 10); /// ``` pub fn capacity(&self) -> usize { // There's no self.tables.capacity. self.items.capacity() } /// Constructs a new `IdOrdMap` from an iterator of values, rejecting /// duplicates. /// /// To overwrite duplicates instead, use [`IdOrdMap::from_iter`]. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let items = vec![ /// Item { id: "foo".to_string(), value: 42 }, /// Item { id: "bar".to_string(), value: 99 }, /// ]; /// /// // Successful creation with unique keys /// let map = IdOrdMap::from_iter_unique(items).unwrap(); /// assert_eq!(map.len(), 2); /// assert_eq!(map.get("foo").unwrap().value, 42); /// /// // Error with duplicate keys /// let duplicate_items = vec![ /// Item { id: "foo".to_string(), value: 42 }, /// Item { id: "foo".to_string(), value: 99 }, /// ]; /// assert!(IdOrdMap::from_iter_unique(duplicate_items).is_err()); /// ``` pub fn from_iter_unique>( iter: I, ) -> Result> { let mut map = IdOrdMap::new(); for value in iter { // It would be nice to use insert_overwrite here, but that would // return a `DuplicateItem`, which can only be converted into // an owned value if T: Clone. Doing this via the Entry API means we // can return a `DuplicateItem` without requiring T to be Clone. match map.entry(value.key()) { Entry::Occupied(entry) => { let duplicate = entry.remove(); return Err(DuplicateItem::__internal_new( value, vec![duplicate], )); } Entry::Vacant(entry) => { entry.insert_ref(value); } } } Ok(map) } /// Returns true if the map is empty. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// assert!(map.is_empty()); /// /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// assert!(!map.is_empty()); /// ``` #[inline] pub fn is_empty(&self) -> bool { self.items.is_empty() } /// Returns the number of items in the map. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// assert_eq!(map.len(), 0); /// /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 99 }).unwrap(); /// assert_eq!(map.len(), 2); /// ``` #[inline] pub fn len(&self) -> usize { self.items.len() } /// Clears the map, removing all items. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 99 }).unwrap(); /// assert_eq!(map.len(), 2); /// /// map.clear(); /// assert!(map.is_empty()); /// assert_eq!(map.len(), 0); /// ``` pub fn clear(&mut self) { self.items.clear(); self.tables.key_to_item.clear(); } /// Reserves capacity for at least `additional` more elements to be inserted /// in the `IdOrdMap`. The collection may reserve more space to /// speculatively avoid frequent reallocations. After calling `reserve`, /// capacity will be greater than or equal to `self.len() + additional`. /// Does nothing if capacity is already sufficient. /// /// Note: This only reserves capacity in the item storage. The internal /// [`BTreeSet`] used for key-to-item mapping does not support capacity /// reservation. /// /// # Panics /// /// Panics if the new capacity overflows [`isize::MAX`] bytes, and /// [`abort`]s the program in case of an allocation error. /// /// [`isize::MAX`]: https://doc.rust-lang.org/std/primitive.isize.html /// [`abort`]: https://doc.rust-lang.org/alloc/alloc/fn.handle_alloc_error.html /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map: IdOrdMap = IdOrdMap::new(); /// map.reserve(100); /// assert!(map.capacity() >= 100); /// ``` pub fn reserve(&mut self, additional: usize) { self.items.reserve(additional); } /// Shrinks the capacity of the map as much as possible. It will drop /// down as much as possible while maintaining the internal rules /// and possibly leaving some space in accordance with the resize policy. /// /// Note: This only shrinks the item storage capacity. The internal /// [`BTreeSet`] used for key-to-item mapping does not support capacity /// control. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map: IdOrdMap = IdOrdMap::with_capacity(100); /// map.insert_unique(Item { id: "foo".to_string(), value: 1 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 2 }).unwrap(); /// assert!(map.capacity() >= 100); /// map.shrink_to_fit(); /// assert!(map.capacity() >= 2); /// ``` pub fn shrink_to_fit(&mut self) { self.items.shrink_to_fit(); } /// Shrinks the capacity of the map with a lower limit. It will drop /// down no lower than the supplied limit while maintaining the internal /// rules and possibly leaving some space in accordance with the resize /// policy. /// /// If the current capacity is less than the lower limit, this is a no-op. /// /// Note: This only shrinks the item storage capacity. The internal /// [`BTreeSet`] used for key-to-item mapping does not support capacity /// control. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// id_upcast!(); /// } /// /// let mut map: IdOrdMap = IdOrdMap::with_capacity(100); /// map.insert_unique(Item { id: "foo".to_string(), value: 1 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 2 }).unwrap(); /// assert!(map.capacity() >= 100); /// map.shrink_to(10); /// assert!(map.capacity() >= 10); /// map.shrink_to(0); /// assert!(map.capacity() >= 2); /// ``` pub fn shrink_to(&mut self, min_capacity: usize) { self.items.shrink_to(min_capacity); } /// Iterates over the items in the map. /// /// Similar to [`BTreeMap`], the iteration is ordered by [`T::Key`]. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "charlie".to_string(), value: 30 }).unwrap(); /// map.insert_unique(Item { id: "alice".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bob".to_string(), value: 99 }).unwrap(); /// /// // Iteration is ordered by key /// let mut iter = map.iter(); /// let item = iter.next().unwrap(); /// assert_eq!(item.id, "alice"); /// let item = iter.next().unwrap(); /// assert_eq!(item.id, "bob"); /// let item = iter.next().unwrap(); /// assert_eq!(item.id, "charlie"); /// assert!(iter.next().is_none()); /// ``` /// /// [`BTreeMap`]: std::collections::BTreeMap /// [`T::Key`]: crate::IdOrdItem::Key #[inline] pub fn iter(&self) -> Iter<'_, T> { Iter::new(&self.items, &self.tables) } /// Iterates over the items in the map, allowing for mutation. /// /// Similar to [`BTreeMap`], the iteration is ordered by [`T::Key`]. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 99 }).unwrap(); /// /// // Modify values through the mutable iterator /// for mut item in map.iter_mut() { /// item.value *= 2; /// } /// /// assert_eq!(map.get("foo").unwrap().value, 84); /// assert_eq!(map.get("bar").unwrap().value, 198); /// ``` /// /// [`BTreeMap`]: std::collections::BTreeMap /// [`T::Key`]: crate::IdOrdItem::Key #[inline] pub fn iter_mut<'a>(&'a mut self) -> IterMut<'a, T> where T::Key<'a>: Hash, { IterMut::new(&mut self.items, &self.tables) } /// Checks general invariants of the map. /// /// The code below always upholds these invariants, but it's useful to have /// an explicit check for tests. #[doc(hidden)] pub fn validate( &self, compactness: ValidateCompact, chaos: ValidateChaos, ) -> Result<(), ValidationError> where T: fmt::Debug, { self.items.validate(compactness)?; self.tables.validate(self.len(), compactness)?; // Check that the indexes are all correct. for (&ix, item) in self.items.iter() { let key = item.key(); let ix1 = match chaos { ValidateChaos::Yes => { // Fall back to a linear search. self.linear_search_index(&key) } ValidateChaos::No => { // Use the B-Tree table to find the index. self.find_index(&key) } }; let Some(ix1) = ix1 else { return Err(ValidationError::general(format!( "item at index {ix} has no key1 index" ))); }; if ix1 != ix { return Err(ValidationError::General(format!( "item at index {ix} has mismatched indexes: ix1: {ix1}", ))); } } Ok(()) } /// Inserts a value into the set, returning an error if any duplicates were /// added. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// /// // Successful insertion /// assert!( /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).is_ok() /// ); /// assert!( /// map.insert_unique(Item { id: "bar".to_string(), value: 99 }).is_ok() /// ); /// /// // Duplicate key /// assert!( /// map.insert_unique(Item { id: "foo".to_string(), value: 100 }).is_err() /// ); /// ``` pub fn insert_unique( &mut self, value: T, ) -> Result<(), DuplicateItem> { let _ = self.insert_unique_impl(value)?; Ok(()) } /// Inserts a value into the map, removing and returning the conflicting /// item, if any. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// /// // First insertion - no conflict /// let old = map.insert_overwrite(Item { id: "foo".to_string(), value: 42 }); /// assert!(old.is_none()); /// /// // Overwrite existing key - returns old value /// let old = map.insert_overwrite(Item { id: "foo".to_string(), value: 99 }); /// assert!(old.is_some()); /// assert_eq!(old.unwrap().value, 42); /// /// // Verify new value is in the map /// assert_eq!(map.get("foo").unwrap().value, 99); /// ``` #[doc(alias = "insert")] pub fn insert_overwrite(&mut self, value: T) -> Option { // Trying to write this function for maximal efficiency can get very // tricky, requiring delicate handling of indexes. We follow a very // simple approach instead: // // 1. Remove the item corresponding to the key that is already in the map. // 2. Add the item to the map. let duplicate = self.remove(&value.key()); if self.insert_unique(value).is_err() { // We should never get here, because we just removed all the // duplicates. panic!("insert_unique failed after removing duplicates"); } duplicate } /// Returns true if the map contains the given `key`. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// /// assert!(map.contains_key("foo")); /// assert!(!map.contains_key("bar")); /// ``` pub fn contains_key<'a, Q>(&'a self, key: &Q) -> bool where Q: ?Sized + Comparable>, { self.find_index(key).is_some() } /// Gets a reference to the value associated with the given `key`. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// /// assert_eq!(map.get("foo").unwrap().value, 42); /// assert!(map.get("bar").is_none()); /// ``` pub fn get<'a, Q>(&'a self, key: &Q) -> Option<&'a T> where Q: ?Sized + Comparable>, { self.find(key) } /// Gets a mutable reference to the item associated with the given `key`. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// /// if let Some(mut item) = map.get_mut("foo") { /// item.value = 99; /// } /// /// assert_eq!(map.get("foo").unwrap().value, 99); /// ``` pub fn get_mut<'a, Q>(&'a mut self, key: &Q) -> Option> where Q: ?Sized + Comparable>, T::Key<'a>: Hash, { let (dormant_map, index) = { let (map, dormant_map) = DormantMutRef::new(self); let index = map.find_index(key)?; (dormant_map, index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; let item = &mut awakened_map.items[index]; let state = awakened_map.tables.state().clone(); let (hash, dormant) = { let (item, dormant) = DormantMutRef::new(item); let hash = awakened_map.tables.make_hash(item); (hash, dormant) }; // SAFETY: the original item is not used after this point. let item = unsafe { dormant.awaken() }; Some(RefMut::new(state, hash, item)) } /// Removes an item from the map by its `key`. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// /// let removed = map.remove("foo"); /// assert!(removed.is_some()); /// assert_eq!(removed.unwrap().value, 42); /// assert!(map.is_empty()); /// /// // Removing a non-existent key returns None /// assert!(map.remove("bar").is_none()); /// ``` pub fn remove<'a, Q>(&'a mut self, key: &Q) -> Option where Q: ?Sized + Comparable>, { let (dormant_map, remove_index) = { let (map, dormant_map) = DormantMutRef::new(self); let remove_index = map.find_index(key)?; (dormant_map, remove_index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; awakened_map.remove_by_index(remove_index) } /// Retrieves an entry by its `key`. /// /// Due to borrow checker limitations, this always accepts an owned key rather /// than a borrowed form. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_ord_map, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// /// // Insert via vacant entry /// match map.entry("foo") { /// id_ord_map::Entry::Vacant(entry) => { /// entry.insert(Item { id: "foo".to_string(), value: 42 }); /// } /// id_ord_map::Entry::Occupied(_) => {} /// } /// /// // Update via occupied entry /// match map.entry("foo") { /// id_ord_map::Entry::Occupied(mut entry) => { /// entry.get_mut().value = 99; /// } /// id_ord_map::Entry::Vacant(_) => {} /// } /// /// assert_eq!(map.get("foo").unwrap().value, 99); /// ``` pub fn entry<'a>(&'a mut self, key: T::Key<'_>) -> Entry<'a, T> { // Why does this always take an owned key? Well, it would seem like we // should be able to pass in any Q that is equivalent. That results in // *this* code compiling fine, but callers have trouble using it because // the borrow checker believes the keys are borrowed for the full 'a // rather than a shorter lifetime. // // By accepting owned keys, we can use the upcast functions to convert // them to a shorter lifetime (so this function accepts T::Key<'_> // rather than T::Key<'a>). // // Really, the solution here is to allow GATs to require covariant // parameters. If that were allowed, the borrow checker should be able // to figure out that keys don't need to be borrowed for the full 'a, // just for some shorter lifetime. let (map, dormant_map) = DormantMutRef::new(self); let key = T::upcast_key(key); { // index is explicitly typed to show that it has a trivial Drop impl // that doesn't capture anything from map. let index: Option = map .tables .key_to_item .find_index(&key, |index| map.items[index].key()); if let Some(index) = index { drop(key); return Entry::Occupied( // SAFETY: `map` is not used after this point. unsafe { OccupiedEntry::new(dormant_map, index) }, ); } } Entry::Vacant( // SAFETY: `map` is not used after this point. unsafe { VacantEntry::new(dormant_map) }, ) } /// Returns the first item in the map. The key of this item is the minimum /// key in the map. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "charlie".to_string(), value: 30 }).unwrap(); /// map.insert_unique(Item { id: "alice".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bob".to_string(), value: 99 }).unwrap(); /// /// // First item has the minimum key. /// let first = map.first().unwrap(); /// assert_eq!(first.id, "alice"); /// assert_eq!(first.value, 42); /// /// // Empty map returns None. /// let empty_map: IdOrdMap = IdOrdMap::new(); /// assert!(empty_map.first().is_none()); /// ``` #[inline] pub fn first(&self) -> Option<&T> { self.tables.key_to_item.first().map(|index| &self.items[index]) } /// Returns the first entry in the map for in-place manipulation. The key of /// this entry is the minimum key in the map. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "charlie".to_string(), value: 30 }).unwrap(); /// map.insert_unique(Item { id: "alice".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bob".to_string(), value: 99 }).unwrap(); /// /// // Modify the first entry. /// if let Some(mut entry) = map.first_entry() { /// entry.get_mut().value = 100; /// } /// /// assert_eq!(map.get("alice").unwrap().value, 100); /// ``` pub fn first_entry(&mut self) -> Option> { let index = self.tables.key_to_item.first()?; let (_, dormant_map) = DormantMutRef::new(self); Some( // SAFETY: `map` is dropped immediately while creating the // DormantMutRef. unsafe { OccupiedEntry::new(dormant_map, index) }, ) } /// Removes and returns the first element in the map. The key of this /// element is the minimum key in the map. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "charlie".to_string(), value: 30 }).unwrap(); /// map.insert_unique(Item { id: "alice".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bob".to_string(), value: 99 }).unwrap(); /// /// // Remove the first element. /// let first = map.pop_first().unwrap(); /// assert_eq!(first.id, "alice"); /// assert_eq!(first.value, 42); /// assert_eq!(map.len(), 2); /// /// // Remove the next element. /// let first = map.pop_first().unwrap(); /// assert_eq!(first.id, "bob"); /// /// // Empty map returns None. /// map.pop_first(); /// assert!(map.pop_first().is_none()); /// ``` pub fn pop_first(&mut self) -> Option { let index = self.tables.key_to_item.first()?; self.remove_by_index(index) } /// Returns the last item in the map. The key of this item is the maximum /// key in the map. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "charlie".to_string(), value: 30 }).unwrap(); /// map.insert_unique(Item { id: "alice".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bob".to_string(), value: 99 }).unwrap(); /// /// // Last item has the maximum key. /// let last = map.last().unwrap(); /// assert_eq!(last.id, "charlie"); /// assert_eq!(last.value, 30); /// /// // Empty map returns None. /// let empty_map: IdOrdMap = IdOrdMap::new(); /// assert!(empty_map.last().is_none()); /// ``` #[inline] pub fn last(&self) -> Option<&T> { self.tables.key_to_item.last().map(|index| &self.items[index]) } /// Returns the last entry in the map for in-place manipulation. The key of /// this entry is the maximum key in the map. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "charlie".to_string(), value: 30 }).unwrap(); /// map.insert_unique(Item { id: "alice".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bob".to_string(), value: 99 }).unwrap(); /// /// // Modify the last entry. /// if let Some(mut entry) = map.last_entry() { /// entry.get_mut().value = 200; /// } /// /// assert_eq!(map.get("charlie").unwrap().value, 200); /// ``` pub fn last_entry(&mut self) -> Option> { let index = self.tables.key_to_item.last()?; let (_, dormant_map) = DormantMutRef::new(self); Some( // SAFETY: `map` is dropped immediately while creating the // DormantMutRef. unsafe { OccupiedEntry::new(dormant_map, index) }, ) } /// Removes and returns the last element in the map. The key of this /// element is the maximum key in the map. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "charlie".to_string(), value: 30 }).unwrap(); /// map.insert_unique(Item { id: "alice".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bob".to_string(), value: 99 }).unwrap(); /// /// // Remove the last element. /// let last = map.pop_last().unwrap(); /// assert_eq!(last.id, "charlie"); /// assert_eq!(last.value, 30); /// assert_eq!(map.len(), 2); /// /// // Remove the next element. /// let last = map.pop_last().unwrap(); /// assert_eq!(last.id, "bob"); /// /// // Empty map returns None. /// map.pop_last(); /// assert!(map.pop_last().is_none()); /// ``` pub fn pop_last(&mut self) -> Option { let index = self.tables.key_to_item.last()?; self.remove_by_index(index) } /// Retains only the elements specified by the predicate. /// /// In other words, remove all items `T` for which `f(RefMut)` returns /// false. The elements are visited in ascending key order. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct Item { /// id: String, /// value: u32, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::new(); /// map.insert_unique(Item { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(Item { id: "bar".to_string(), value: 20 }).unwrap(); /// map.insert_unique(Item { id: "baz".to_string(), value: 99 }).unwrap(); /// /// // Retain only items where value is greater than 30 /// map.retain(|item| item.value > 30); /// /// assert_eq!(map.len(), 2); /// assert_eq!(map.get("foo").unwrap().value, 42); /// assert_eq!(map.get("baz").unwrap().value, 99); /// assert!(map.get("bar").is_none()); /// ``` pub fn retain<'a, F>(&'a mut self, mut f: F) where F: FnMut(RefMut<'a, T>) -> bool, T::Key<'a>: Hash, { let hash_state = self.tables.state().clone(); let (_, mut dormant_items) = DormantMutRef::new(&mut self.items); self.tables.key_to_item.retain(|index| { let (item, dormant_items) = { // SAFETY: All uses of `items` ended in the previous iteration. let items = unsafe { dormant_items.reborrow() }; let (items, dormant_items) = DormantMutRef::new(items); let item: &'a mut T = items .get_mut(index) .expect("all indexes are present in self.items"); (item, dormant_items) }; let (hash, dormant_item) = { let (item, dormant_item): (&'a mut T, _) = DormantMutRef::new(item); // Use T::key(item) rather than item.key() to force the key // trait function to be called for T rather than &mut T. let key = T::key(item); let hash = hash_state.hash_one(key); (MapHash::new(hash), dormant_item) }; let retain = { // SAFETY: The original item is no longer used after the second // block above. dormant_items, from which item is derived, is // currently dormant. let item = unsafe { dormant_item.awaken() }; let ref_mut = RefMut::new(hash_state.clone(), hash, item); f(ref_mut) }; if retain { true } else { // SAFETY: The original items is no longer used after the first // block above, and item + dormant_item have been dropped after // being used above. let items = unsafe { dormant_items.awaken() }; items.remove(index); false } }); } fn find<'a, Q>(&'a self, k: &Q) -> Option<&'a T> where Q: ?Sized + Comparable>, { self.find_index(k).map(|ix| &self.items[ix]) } fn linear_search_index<'a, Q>(&'a self, k: &Q) -> Option where Q: ?Sized + Ord + Equivalent>, { self.items.iter().find_map(|(index, item)| { (k.equivalent(&item.key())).then_some(*index) }) } fn find_index<'a, Q>(&'a self, k: &Q) -> Option where Q: ?Sized + Comparable>, { self.tables.key_to_item.find_index(k, |index| self.items[index].key()) } pub(super) fn get_by_index(&self, index: usize) -> Option<&T> { self.items.get(index) } pub(super) fn get_by_index_mut<'a>( &'a mut self, index: usize, ) -> Option> where T::Key<'a>: Hash, { let state = self.tables.state().clone(); let (hash, dormant) = { let item: &'a mut T = self.items.get_mut(index)?; let (item, dormant) = DormantMutRef::new(item); let hash = self.tables.make_hash(item); (hash, dormant) }; // SAFETY: item is no longer used after the above point. let item = unsafe { dormant.awaken() }; Some(RefMut::new(state, hash, item)) } pub(super) fn insert_unique_impl( &mut self, value: T, ) -> Result> { let mut duplicates = BTreeSet::new(); // Check for duplicates *before* inserting the new item, because we // don't want to partially insert the new item and then have to roll // back. let key = value.key(); if let Some(index) = self .tables .key_to_item .find_index(&key, |index| self.items[index].key()) { duplicates.insert(index); } if !duplicates.is_empty() { drop(key); return Err(DuplicateItem::__internal_new( value, duplicates.iter().map(|ix| &self.items[*ix]).collect(), )); } let next_index = self.items.next_index(); self.tables .key_to_item .insert(next_index, &key, |index| self.items[index].key()); drop(key); self.items.insert_at_next_index(value); Ok(next_index) } pub(super) fn remove_by_index(&mut self, remove_index: usize) -> Option { let value = self.items.remove(remove_index)?; // Remove the value from the table. self.tables.key_to_item.remove(remove_index, value.key(), |index| { if index == remove_index { value.key() } else { self.items[index].key() } }); Some(value) } pub(super) fn replace_at_index(&mut self, index: usize, value: T) -> T { // We check the key before removing it, to avoid leaving the map in an // inconsistent state. let old_key = self.get_by_index(index).expect("index is known to be valid").key(); if T::upcast_key(old_key) != value.key() { panic!( "must insert a value with \ the same key used to create the entry" ); } // Now that we know the key is the same, we can replace the value // directly without needing to tweak any tables. self.items.replace(index, value) } } impl<'a, T: IdOrdItem> fmt::Debug for IdOrdMap where T: fmt::Debug, T::Key<'a>: fmt::Debug, T: 'a, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut map = f.debug_map(); for item in self.iter() { let key = item.key(); // SAFETY: // // * Lifetime extension: for a type T and two lifetime params 'a and // 'b, T<'a> and T<'b> aren't guaranteed to have the same layout, // but (a) that is true today and (b) it would be shocking and // break half the Rust ecosystem if that were to change in the // future. // * We only use key within the scope of this block before immediately // dropping it. In particular, map.entry calls key.fmt() without // holding a reference to it. let key: T::Key<'a> = unsafe { core::mem::transmute::, T::Key<'a>>(key) }; map.entry(&key, &item); } map.finish() } } impl PartialEq for IdOrdMap { fn eq(&self, other: &Self) -> bool { // Items are stored in sorted order, so we can just walk over both // iterators. if self.items.len() != other.items.len() { return false; } self.iter().zip(other.iter()).all(|(item1, item2)| { // Check that the items are equal. item1 == item2 }) } } // The Eq bound on T ensures that the IdOrdMap forms an equivalence class. impl Eq for IdOrdMap {} /// The `Extend` implementation overwrites duplicates. In the future, there will /// also be an `extend_unique` method that will return an error. impl Extend for IdOrdMap { fn extend>(&mut self, iter: I) { // Keys may already be present in the map, or multiple times in the // iterator. Reserve the entire hint lower bound if the map is empty. // Otherwise reserve half the hint (rounded up), so the map will only // resize twice in the worst case. let iter = iter.into_iter(); let reserve = if self.is_empty() { iter.size_hint().0 } else { iter.size_hint().0.div_ceil(2) }; self.reserve(reserve); for item in iter { self.insert_overwrite(item); } } } impl<'a, T: IdOrdItem> IntoIterator for &'a IdOrdMap { type Item = &'a T; type IntoIter = Iter<'a, T>; #[inline] fn into_iter(self) -> Self::IntoIter { self.iter() } } impl<'a, T: IdOrdItem> IntoIterator for &'a mut IdOrdMap where T::Key<'a>: Hash, { type Item = RefMut<'a, T>; type IntoIter = IterMut<'a, T>; #[inline] fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl IntoIterator for IdOrdMap { type Item = T; type IntoIter = IntoIter; #[inline] fn into_iter(self) -> Self::IntoIter { IntoIter::new(self.items, self.tables) } } /// The `FromIterator` implementation for `IdOrdMap` overwrites duplicate /// items. /// /// To reject duplicates, use [`IdOrdMap::from_iter_unique`]. impl FromIterator for IdOrdMap { fn from_iter>(iter: I) -> Self { let mut map = IdOrdMap::new(); for value in iter { map.insert_overwrite(value); } map } } iddqd-0.3.17/src/id_ord_map/iter.rs000064400000000000000000000122361046102023000152020ustar 00000000000000use super::{IdOrdItem, RefMut, tables::IdOrdMapTables}; use crate::support::{ alloc::Global, borrow::DormantMutRef, btree_table, item_set::ItemSet, }; use core::{hash::Hash, iter::FusedIterator}; /// An iterator over the elements of an [`IdOrdMap`] by shared reference. /// /// Created by [`IdOrdMap::iter`], and ordered by keys. /// /// [`IdOrdMap`]: crate::IdOrdMap /// [`IdOrdMap::iter`]: crate::IdOrdMap::iter #[derive(Clone, Debug)] pub struct Iter<'a, T: IdOrdItem> { items: &'a ItemSet, iter: btree_table::Iter<'a>, } impl<'a, T: IdOrdItem> Iter<'a, T> { pub(super) fn new( items: &'a ItemSet, tables: &'a IdOrdMapTables, ) -> Self { Self { items, iter: tables.key_to_item.iter() } } } impl<'a, T: IdOrdItem> Iterator for Iter<'a, T> { type Item = &'a T; #[inline] fn next(&mut self) -> Option { let index = self.iter.next()?; Some(&self.items[index]) } } impl ExactSizeIterator for Iter<'_, T> { #[inline] fn len(&self) -> usize { self.iter.len() } } // btree_set::Iter is a FusedIterator, so Iter is as well. impl FusedIterator for Iter<'_, T> {} /// An iterator over the elements of a [`IdOrdMap`] by mutable reference. /// /// This iterator returns [`RefMut`] instances. /// /// Created by [`IdOrdMap::iter_mut`], and ordered by keys. /// /// [`IdOrdMap`]: crate::IdOrdMap /// [`IdOrdMap::iter_mut`]: crate::IdOrdMap::iter_mut #[derive(Debug)] pub struct IterMut<'a, T: IdOrdItem> where T::Key<'a>: Hash, { items: &'a mut ItemSet, tables: &'a IdOrdMapTables, iter: btree_table::Iter<'a>, } impl<'a, T: IdOrdItem> IterMut<'a, T> where T::Key<'a>: Hash, { pub(super) fn new( items: &'a mut ItemSet, tables: &'a IdOrdMapTables, ) -> Self { Self { items, tables, iter: tables.key_to_item.iter() } } } impl<'a, T: IdOrdItem + 'a> Iterator for IterMut<'a, T> where T::Key<'a>: Hash, { type Item = RefMut<'a, T>; #[inline] fn next(&mut self) -> Option { let index = self.iter.next()?; let item = &mut self.items[index]; // SAFETY: This lifetime extension from self to 'a is safe based on two // things: // // 1. We never repeat indexes, i.e. for an index i, once we've handed // out an item at i, creating `&mut T`, we'll never get the index i // again. (This is guaranteed from the set-based nature of the // iterator.) This means that we don't ever create a mutable alias to // the same memory. // // In particular, unlike all the other places we look up data from a // btree table, we don't pass a lookup function into // self.iter.next(). If we did, then it is possible the lookup // function would have been called with an old index i. But we don't // need to do that. // // 2. All mutable references to data within self.items are derived from // self.items. So, the rule described at [1] is upheld: // // > When creating a mutable reference, then while this reference // > exists, the memory it points to must not get accessed (read or // > written) through any other pointer or reference not derived from // > this reference. // // [1]: // https://doc.rust-lang.org/std/ptr/index.html#pointer-to-reference-conversion let item = unsafe { core::mem::transmute::<&mut T, &'a mut T>(item) }; let (hash, dormant) = { let (item, dormant) = DormantMutRef::new(item); let hash = self.tables.make_hash(item); (hash, dormant) }; // SAFETY: item is dropped above, and self is no longer used after this // point. let item = unsafe { dormant.awaken() }; Some(RefMut::new(self.tables.state().clone(), hash, item)) } } impl<'a, T: IdOrdItem + 'a> ExactSizeIterator for IterMut<'a, T> where T::Key<'a>: Hash, { #[inline] fn len(&self) -> usize { self.iter.len() } } // hash_map::IterMut is a FusedIterator, so IterMut is as well. impl<'a, T: IdOrdItem + 'a> FusedIterator for IterMut<'a, T> where T::Key<'a>: Hash { } /// An iterator over the elements of a [`IdOrdMap`] by ownership. /// /// Created by [`IdOrdMap::into_iter`], and ordered by keys. /// /// [`IdOrdMap`]: crate::IdOrdMap /// [`IdOrdMap::into_iter`]: crate::IdOrdMap::into_iter #[derive(Debug)] pub struct IntoIter { items: ItemSet, iter: btree_table::IntoIter, } impl IntoIter { pub(super) fn new( items: ItemSet, tables: IdOrdMapTables, ) -> Self { Self { items, iter: tables.key_to_item.into_iter() } } } impl Iterator for IntoIter { type Item = T; #[inline] fn next(&mut self) -> Option { let index = self.iter.next()?; let next = self .items .remove(index) .unwrap_or_else(|| panic!("index {index} not found in items")); Some(next) } } iddqd-0.3.17/src/id_ord_map/mod.rs000064400000000000000000000014341046102023000150140ustar 00000000000000//! An ordered map where the keys are part of the values, based on a B-Tree. //! //! For more information, see [`IdOrdMap`]. #[cfg(feature = "daft")] mod daft_impls; mod entry; pub(crate) mod imp; mod iter; #[cfg(feature = "proptest")] mod proptest_impls; mod ref_mut; #[cfg(feature = "schemars08")] mod schemars_impls; #[cfg(feature = "serde")] mod serde_impls; mod tables; pub(crate) mod trait_defs; #[cfg(feature = "daft")] pub use daft_impls::Diff; pub use entry::{Entry, OccupiedEntry, VacantEntry}; pub use imp::IdOrdMap; pub use iter::{IntoIter, Iter, IterMut}; #[cfg(feature = "proptest")] pub use proptest_impls::{IdOrdMapStrategy, IdOrdMapValueTree, prop_strategy}; pub use ref_mut::RefMut; #[cfg(feature = "serde")] pub use serde_impls::IdOrdMapAsMap; pub use trait_defs::IdOrdItem; iddqd-0.3.17/src/id_ord_map/proptest_impls.rs000064400000000000000000000075451046102023000173320ustar 00000000000000//! Proptest strategies for generating [`IdOrdMap`]s with random inputs. use crate::{IdOrdItem, id_ord_map::IdOrdMap}; use core::fmt; use proptest::{ arbitrary::{Arbitrary, StrategyFor, any_with}, collection::{SizeRange, VecStrategy, VecValueTree}, strategy::{NewTree, Strategy, ValueTree}, test_runner::TestRunner, }; /// Strategy to create [`IdOrdMap`]s with a length in a certain range. /// /// Created by the [`prop_strategy()`] function. #[must_use = "strategies do nothing unless used"] #[derive(Clone)] pub struct IdOrdMapStrategy where T: Strategy, { inner: VecStrategy, } impl fmt::Debug for IdOrdMapStrategy where T: Strategy, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("IdOrdMapStrategy") .field("inner", &self.inner) .finish_non_exhaustive() } } /// Creates a strategy to generate [`IdOrdMap`]s containing items drawn from /// `element` and with a size within the given range. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_ord_map, id_upcast}; /// use proptest::{ /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, /// }; /// /// #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] /// struct Person { /// id: u32, /// name: String, /// } /// /// impl IdOrdItem for Person { /// type Key<'a> = u32; /// /// fn key(&self) -> Self::Key<'_> { /// self.id /// } /// id_upcast!(); /// } /// /// // Create a strategy using a tuple and mapping it to Person. /// let strategy = id_ord_map::prop_strategy( /// (any::(), any::()) /// .prop_map(|(id, name)| Person { id, name }), /// 0..=5, /// ); /// /// // The strategy can be used in proptest contexts. /// let mut runner = TestRunner::default(); /// let _tree = strategy.new_tree(&mut runner).unwrap(); /// ``` pub fn prop_strategy( element: T, size: impl Into, ) -> IdOrdMapStrategy { IdOrdMapStrategy { inner: proptest::collection::vec(element, size) } } impl<'a, T> Strategy for IdOrdMapStrategy where T: Strategy, T::Value: 'a + IdOrdItem, ::Key<'a>: fmt::Debug, { type Tree = IdOrdMapValueTree; type Value = IdOrdMap; fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let inner = self.inner.new_tree(runner)?; Ok(IdOrdMapValueTree { inner }) } } /// `ValueTree` corresponding to [`IdOrdMapStrategy`]. #[derive(Clone)] pub struct IdOrdMapValueTree where T: ValueTree, { inner: VecValueTree, } impl fmt::Debug for IdOrdMapValueTree where T: ValueTree + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("IdOrdMapValueTree") .field("inner", &self.inner) .finish_non_exhaustive() } } impl<'a, T> ValueTree for IdOrdMapValueTree where T: ValueTree, T::Value: 'a + IdOrdItem, ::Key<'a>: fmt::Debug, { type Value = IdOrdMap; fn current(&self) -> Self::Value { let items = self.inner.current(); let mut map = IdOrdMap::new(); for item in items { // Use insert_overwrite to handle duplicate keys. map.insert_overwrite(item); } map } fn simplify(&mut self) -> bool { self.inner.simplify() } fn complicate(&mut self) -> bool { self.inner.complicate() } } impl<'a, T> Arbitrary for IdOrdMap where T: 'a + IdOrdItem + Arbitrary, ::Key<'a>: fmt::Debug, { type Parameters = (SizeRange, T::Parameters); type Strategy = IdOrdMapStrategy>; fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { let (size, element_args) = args; prop_strategy(any_with::(element_args), size) } } iddqd-0.3.17/src/id_ord_map/ref_mut.rs000064400000000000000000000121571046102023000157020ustar 00000000000000use super::IdOrdItem; use crate::support::map_hash::MapHash; use core::{ fmt, hash::Hash, ops::{Deref, DerefMut}, }; /// A mutable reference to an [`IdOrdMap`] entry. /// /// This is a wrapper around a `&mut T` that panics when dropped, if the /// borrowed value's key has changed since the wrapper was created. /// /// # Change detection /// /// It is illegal to change the keys of a borrowed `&mut T`. `RefMut` attempts /// to enforce this invariant, and as part of that, it requires that the key /// type implement [`Hash`]. /// /// `RefMut` stores the `Hash` output of keys at creation time, and recomputes /// these hashes when it is dropped or when [`Self::into_ref`] is called. If a /// key changes, there's a small but non-negligible chance that its hash value /// stays the same[^collision-chance]. In that case, the map will no longer /// function correctly and might panic on access. This will not introduce memory /// safety issues, however. /// /// It is also possible to deliberately write pathological `Hash` /// implementations that collide more often. (Don't do this.) /// /// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is /// called on it. If a key is changed and `mem::forget` is then called on the /// `RefMut`, the [`IdOrdMap`] will no longer function correctly and might panic /// on access. This will not introduce memory safety issues, however. /// /// The issues here are similar to using interior mutability (e.g. `RefCell` or /// `Mutex`) to mutate keys in a regular `HashMap`. /// /// [`mem::forget`]: std::mem::forget /// /// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability /// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday /// problem], the probability of a collision by chance reaches 10⁻⁶ within /// around 6 × 10⁶ elements. /// /// [`IdOrdMap`]: crate::IdOrdMap /// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table pub struct RefMut<'a, T: IdOrdItem> where T::Key<'a>: Hash, { inner: Option>, } impl<'a, T: IdOrdItem> RefMut<'a, T> where T::Key<'a>: Hash, { pub(super) fn new( state: foldhash::fast::FixedState, hash: MapHash, borrowed: &'a mut T, ) -> Self { let inner = RefMutInner { state, hash, borrowed }; Self { inner: Some(inner) } } /// Converts this `RefMut` into a `&'a T`. pub fn into_ref(mut self) -> &'a T { let inner = self.inner.take().unwrap(); inner.into_ref() } } impl<'a, T: for<'k> IdOrdItemMut<'k>> RefMut<'a, T> { /// Borrows self into a shorter-lived `RefMut`. /// /// This `RefMut` will also check hash equality on drop. pub fn reborrow<'b>(&'b mut self) -> RefMut<'b, T> { let inner = self.inner.as_mut().unwrap(); let borrowed = &mut *inner.borrowed; RefMut::new(inner.state.clone(), inner.hash.clone(), borrowed) } } impl<'a, T: IdOrdItem> Drop for RefMut<'a, T> where T::Key<'a>: Hash, { fn drop(&mut self) { if let Some(inner) = self.inner.take() { inner.into_ref(); } } } impl<'a, T: IdOrdItem> Deref for RefMut<'a, T> where T::Key<'a>: Hash, { type Target = T; fn deref(&self) -> &Self::Target { self.inner.as_ref().unwrap().borrowed } } impl<'a, T: IdOrdItem> DerefMut for RefMut<'a, T> where T::Key<'a>: Hash, { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.as_mut().unwrap().borrowed } } impl<'a, T: IdOrdItem + fmt::Debug> fmt::Debug for RefMut<'a, T> where T::Key<'a>: Hash, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.inner { Some(ref inner) => inner.fmt(f), None => { f.debug_struct("RefMut").field("borrowed", &"missing").finish() } } } } struct RefMutInner<'a, T: IdOrdItem> { state: foldhash::fast::FixedState, hash: MapHash, borrowed: &'a mut T, } impl<'a, T: IdOrdItem> RefMutInner<'a, T> where T::Key<'a>: Hash, { fn into_ref(self) -> &'a T { let key: T::Key<'_> = self.borrowed.key(); // SAFETY: The key is borrowed, then dropped immediately. T is valid for // 'a so T::Key is valid for 'a. let key: T::Key<'a> = unsafe { std::mem::transmute::, T::Key<'a>>(key) }; if !self.hash.is_same_hash(&self.state, &key) { panic!("key changed during RefMut borrow"); } self.borrowed } } impl fmt::Debug for RefMutInner<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.borrowed.fmt(f) } } /// A trait for mutable access to items in an [`IdOrdMap`]. /// /// This is a non-public trait used to work around a Rust borrow checker /// limitation. [This will produce a documentation warning if it becomes /// public]. /// /// This is automatically implemented whenever `T::Key` implements [`Hash`]. /// /// [`IdOrdMap`]: crate::IdOrdMap pub trait IdOrdItemMut<'a>: IdOrdItem: Hash> + 'a {} impl<'a, T> IdOrdItemMut<'a> for T where T: 'a + IdOrdItem: Hash> {} iddqd-0.3.17/src/id_ord_map/schemars_impls.rs000064400000000000000000000021061046102023000172430ustar 00000000000000//! Schemars implementations for IdOrdMap. use crate::{ id_ord_map::{ imp::IdOrdMap, serde_impls::IdOrdMapAsMap, trait_defs::IdOrdItem, }, support::schemars_utils::{create_map_schema, create_object_schema}, }; use alloc::string::String; use schemars::{JsonSchema, gen::SchemaGenerator, schema::Schema}; impl JsonSchema for IdOrdMap where T: JsonSchema + IdOrdItem, { fn schema_name() -> String { alloc::format!("IdOrdMap_of_{}", T::schema_name()) } fn json_schema(generator: &mut SchemaGenerator) -> Schema { create_map_schema::("IdOrdMap", "iddqd::IdOrdMap", generator) } fn is_referenceable() -> bool { false } } impl JsonSchema for IdOrdMapAsMap where T: JsonSchema + IdOrdItem, { fn schema_name() -> String { alloc::format!("IdOrdMapAsMap_of_{}", T::schema_name()) } fn json_schema(generator: &mut SchemaGenerator) -> Schema { create_object_schema::("IdOrdMapAsMap", "iddqd::IdOrdMap", generator) } fn is_referenceable() -> bool { false } } iddqd-0.3.17/src/id_ord_map/serde_impls.rs000064400000000000000000000157101046102023000165450ustar 00000000000000use super::{IdOrdItem, IdOrdMap}; use core::{fmt, marker::PhantomData}; use serde_core::{ Deserialize, Deserializer, Serialize, Serializer, de::{MapAccess, SeqAccess, Visitor}, ser::{SerializeMap, SerializeSeq}, }; /// An `IdOrdMap` serializes to the list of items. Items are serialized in /// order of their keys. /// /// Serializing as a list of items rather than as a map works around the lack of /// non-string keys in formats like JSON. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// # use iddqd_test_utils::serde_json; /// use serde::{Deserialize, Serialize}; /// /// #[derive(Debug, Serialize)] /// struct Item { /// id: u32, /// name: String, /// email: String, /// } /// /// // This is a complex key, so it can't be a JSON map key. /// #[derive(Eq, PartialEq, PartialOrd, Ord)] /// struct ComplexKey<'a> { /// id: u32, /// email: &'a str, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = ComplexKey<'a>; /// fn key(&self) -> Self::Key<'_> { /// ComplexKey { id: self.id, email: &self.email } /// } /// id_upcast!(); /// } /// /// let mut map = IdOrdMap::::new(); /// map.insert_unique(Item { /// id: 1, /// name: "Alice".to_string(), /// email: "alice@example.com".to_string(), /// }) /// .unwrap(); /// /// // The map is serialized as a list of items in order of their keys. /// let serialized = serde_json::to_string(&map).unwrap(); /// assert_eq!( /// serialized, /// r#"[{"id":1,"name":"Alice","email":"alice@example.com"}]"#, /// ); /// ``` impl Serialize for IdOrdMap where T: Serialize, { fn serialize( &self, serializer: S, ) -> Result { let mut seq = serializer.serialize_seq(Some(self.len()))?; for item in self { seq.serialize_element(item)?; } seq.end() } } /// The `Deserialize` impl deserializes from either a sequence or a map of items, /// rebuilding the indexes and producing an error if there are any duplicates. /// /// The `fmt::Debug` bound on `T` ensures better error reporting. impl<'de, T: IdOrdItem + fmt::Debug> Deserialize<'de> for IdOrdMap where T: Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData }) } } struct SeqVisitor { _marker: PhantomData T>, } impl<'de, T> Visitor<'de> for SeqVisitor where T: IdOrdItem + Deserialize<'de> + fmt::Debug, { type Value = IdOrdMap; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .write_str("a sequence or map of items representing an IdOrdMap") } fn visit_seq( self, mut seq: Access, ) -> Result where Access: SeqAccess<'de>, { let mut map = match seq.size_hint() { Some(size) => IdOrdMap::with_capacity(size), None => IdOrdMap::new(), }; while let Some(element) = seq.next_element()? { map.insert_unique(element) .map_err(serde_core::de::Error::custom)?; } Ok(map) } fn visit_map( self, mut map_access: Access, ) -> Result where Access: MapAccess<'de>, { let mut map = IdOrdMap::new(); while let Some((_, value)) = map_access.next_entry::()? { map.insert_unique(value).map_err(serde_core::de::Error::custom)?; } Ok(map) } } /// Marker type for [`IdOrdMap`] serialized as a map, for use with serde's /// `with` attribute. /// /// # Examples /// /// Use with serde's `with` attribute: /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_ord_map::IdOrdMapAsMap, id_upcast}; /// use serde::{Deserialize, Serialize}; /// /// #[derive(Debug, Serialize, Deserialize)] /// struct Item { /// id: u32, /// name: String, /// } /// /// impl IdOrdItem for Item { /// type Key<'a> = u32; /// fn key(&self) -> Self::Key<'_> { /// self.id /// } /// id_upcast!(); /// } /// /// #[derive(Serialize, Deserialize)] /// struct Config { /// #[serde(with = "IdOrdMapAsMap")] /// items: IdOrdMap, /// } /// ``` /// /// # Requirements /// /// - For serialization, the key type must implement [`Serialize`]. /// - For JSON serialization, the key should be string-like or convertible to a string key. pub struct IdOrdMapAsMap { _marker: PhantomData T>, } struct MapVisitorAsMap { _marker: PhantomData T>, } impl<'de, T> Visitor<'de> for MapVisitorAsMap where T: IdOrdItem + Deserialize<'de> + fmt::Debug, { type Value = IdOrdMap; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a map with items representing an IdOrdMap") } fn visit_map( self, mut map_access: Access, ) -> Result where Access: MapAccess<'de>, { let mut map = IdOrdMap::new(); while let Some((_, value)) = map_access.next_entry::()? { map.insert_unique(value).map_err(serde_core::de::Error::custom)?; } Ok(map) } } impl IdOrdMapAsMap { /// Serializes an `IdOrdMap` as a JSON object/map using `key()` as keys. pub fn serialize<'a, Ser>( map: &IdOrdMap, serializer: Ser, ) -> Result where T: 'a + IdOrdItem + Serialize, T::Key<'a>: Serialize, Ser: Serializer, { let mut ser_map = serializer.serialize_map(Some(map.len()))?; for item in map.iter() { // SAFETY: // // * Lifetime extension: for a type T and two lifetime params 'a and // 'b, T<'a> and T<'b> aren't guaranteed to have the same layout, // but (a) that is true today and (b) it would be shocking and // break half the Rust ecosystem if that were to change in the // future. // * We only use key within the scope of this block before // immediately dropping it. In particular, ser_map.serialize_entry // serializes the key without holding a reference to it. let key1 = unsafe { core::mem::transmute::, T::Key<'a>>(item.key()) }; ser_map.serialize_entry(&key1, item)?; } ser_map.end() } /// Deserializes an `IdOrdMap` from a JSON object/map. pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where T: IdOrdItem + Deserialize<'de> + fmt::Debug, D: Deserializer<'de>, { deserializer.deserialize_map(MapVisitorAsMap { _marker: PhantomData }) } } iddqd-0.3.17/src/id_ord_map/tables.rs000064400000000000000000000020341046102023000155040ustar 00000000000000use super::IdOrdItem; use crate::{ internal::{ValidateCompact, ValidationError}, support::{btree_table::MapBTreeTable, map_hash::MapHash}, }; use core::hash::Hash; #[derive(Clone, Debug, Default)] pub(super) struct IdOrdMapTables { pub(super) key_to_item: MapBTreeTable, } impl IdOrdMapTables { pub(super) const fn new() -> Self { Self { key_to_item: MapBTreeTable::new() } } pub(super) fn state(&self) -> &foldhash::fast::FixedState { self.key_to_item.state() } #[doc(hidden)] pub(super) fn validate( &self, expected_len: usize, compactness: ValidateCompact, ) -> Result<(), ValidationError> { self.key_to_item.validate(expected_len, compactness).map_err( |error| ValidationError::Table { name: "key_to_item", error }, )?; Ok(()) } pub(super) fn make_hash<'a, T>(&self, item: &'a T) -> MapHash where T::Key<'a>: Hash, T: 'a + IdOrdItem, { self.key_to_item.compute_hash(item.key()) } } iddqd-0.3.17/src/id_ord_map/trait_defs.rs000064400000000000000000000051371046102023000163650ustar 00000000000000//! Trait definitions for `IdOrdMap`. use alloc::{boxed::Box, rc::Rc, sync::Arc}; /// An element stored in an [`IdOrdMap`]. /// /// This trait is used to define the key type for the map. /// /// # Examples /// /// ``` /// use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; /// /// // Define a struct with a key. /// #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// struct MyItem { /// id: String, /// value: u32, /// } /// /// // Implement IdOrdItem for the struct. /// impl IdOrdItem for MyItem { /// // Keys can borrow from the item. /// type Key<'a> = &'a str; /// /// fn key(&self) -> Self::Key<'_> { /// &self.id /// } /// /// id_upcast!(); /// } /// /// // Create an IdOrdMap and insert items. /// let mut map = IdOrdMap::new(); /// map.insert_unique(MyItem { id: "foo".to_string(), value: 42 }).unwrap(); /// map.insert_unique(MyItem { id: "bar".to_string(), value: 20 }).unwrap(); /// ``` /// /// [`IdOrdMap`]: crate::IdOrdMap pub trait IdOrdItem { /// The key type. type Key<'a>: Ord where Self: 'a; /// Retrieves the key. fn key(&self) -> Self::Key<'_>; /// Upcasts the key to a shorter lifetime, in effect asserting that the /// lifetime `'a` on [`IdOrdItem::Key`] is covariant. /// /// Typically implemented via the [`id_upcast`] macro. /// /// [`id_upcast`]: crate::id_upcast fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short>; } macro_rules! impl_for_ref { ($type:ty) => { impl<'b, T: 'b + ?Sized + IdOrdItem> IdOrdItem for $type { type Key<'a> = T::Key<'a> where Self: 'a; fn key(&self) -> Self::Key<'_> { (**self).key() } fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> where Self: 'long, { T::upcast_key(long) } } }; } impl_for_ref!(&'b T); impl_for_ref!(&'b mut T); macro_rules! impl_for_box { ($type:ty) => { impl IdOrdItem for $type { type Key<'a> = T::Key<'a> where Self: 'a; fn key(&self) -> Self::Key<'_> { (**self).key() } fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> { T::upcast_key(long) } } }; } impl_for_box!(Box); impl_for_box!(Rc); impl_for_box!(Arc); iddqd-0.3.17/src/internal.rs000064400000000000000000000036561046102023000137640ustar 00000000000000/// Re-export `Global` if allocator-api2 isn't enabled; it's not public but is /// used within tests. pub use crate::support::alloc::Global; use alloc::string::String; use core::fmt; /// For validation, indicate whether we expect integer tables to be compact /// (have all values in the range 0..table.len()). /// /// Maps are expected to be compact if no remove operations were performed. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ValidateCompact { Compact, NonCompact, } /// For validation, indicates whether chaos testing is in effect. /// /// If it is, then we fall back to linear searches for table lookups. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ValidateChaos { Yes, No, } #[derive(Debug)] pub enum ValidationError { Table { name: &'static str, error: TableValidationError }, General(String), } impl ValidationError { pub(crate) fn general(msg: impl Into) -> Self { ValidationError::General(msg.into()) } } impl fmt::Display for ValidationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Table { name, error } => { write!(f, "validation error in table {name}: {error}") } Self::General(msg) => msg.fmt(f), } } } impl core::error::Error for ValidationError { fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { match self { ValidationError::Table { error, .. } => Some(error), ValidationError::General(_) => None, } } } #[derive(Debug)] pub struct TableValidationError(String); impl TableValidationError { pub(crate) fn new(msg: impl Into) -> Self { TableValidationError(msg.into()) } } impl fmt::Display for TableValidationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl core::error::Error for TableValidationError {} iddqd-0.3.17/src/lib.rs000064400000000000000000000340031046102023000127040ustar 00000000000000//! Maps where keys are borrowed from values. //! //! This crate consists of several map types, collectively called **ID maps**: //! //! - [`IdOrdMap`]: A B-Tree based map where keys are borrowed from values. //! - [`IdHashMap`]: A hash map where keys are borrowed from values. //! - [`BiHashMap`]: A bijective (1:1) hash map with two keys, borrowed from //! values. //! - [`TriHashMap`]: A trijective (1:1:1) hash map with three keys, borrowed //! from values. //! //! # Usage //! //! * Pick your ID map type. //! * Depending on the ID map type, implement [`IdOrdItem`], [`IdHashItem`], //! [`BiHashItem`], or [`TriHashItem`] for your value type. //! * Store values in the ID map type. //! //! ## Features //! //! This crate was built out a practical need for map types, and addresses //! issues encountered using Rust's default map types in practice at Oxide. //! //! * Keys are retrieved from values, not stored separately from them. Separate //! storage has been a recurring pain point in our codebases: if keys are //! duplicated within values, it's proven to be hard to maintain consistency //! between keys and values. This crate addresses that need. //! * Keys may be borrowed from values, which allows for more flexible //! implementations. (They don't have to be borrowed, but they can be.) //! * There's no `insert` method; insertion must be through either //! `insert_overwrite` or `insert_unique`. You must pick an insertion //! behavior. //! * For hash maps, the default hasher is [`foldhash`], which is much faster //! than SipHash. However, foldhash does *not provide the same level of HashDoS //! resistance* as SipHash. If that is important to you, you can use a different //! hasher. (Disable the `default-hasher` feature to require a hash //! builder type parameter to be passed in.) //! * The serde implementations reject duplicate keys. //! //! We've also sometimes needed to index a set of data by more than one key, or //! perhaps map one key to another. For that purpose, this crate provides //! [`BiHashMap`] and [`TriHashMap`]. //! //! * [`BiHashMap`] has two keys, and provides a bijection (1:1 relationship) //! between the keys. //! * [`TriHashMap`] has three keys, and provides a trijection (1:1:1 //! relationship) between the keys. //! //! As a consequence of the general API structure, maps can have arbitrary //! non-key data associated with them as well. //! //! ## Examples //! //! An example for [`IdOrdMap`]: //! //! ``` //! # #[cfg(feature = "std")] { //! use iddqd::{IdOrdItem, IdOrdMap, id_upcast}; //! //! #[derive(Debug)] //! struct User { //! name: String, //! age: u8, //! } //! //! // Implement IdOrdItem so the map knows how to get the key from the value. //! impl IdOrdItem for User { //! // The key type can borrow from the value. //! type Key<'a> = &'a str; //! //! fn key(&self) -> Self::Key<'_> { //! &self.name //! } //! //! id_upcast!(); //! } //! //! let mut users = IdOrdMap::::new(); //! //! // You must pick an insertion behavior. insert_unique returns an error if //! // the key already exists. //! users.insert_unique(User { name: "Alice".to_string(), age: 30 }).unwrap(); //! users.insert_unique(User { name: "Bob".to_string(), age: 35 }).unwrap(); //! //! // Lookup by name: //! assert_eq!(users.get("Alice").unwrap().age, 30); //! assert_eq!(users.get("Bob").unwrap().age, 35); //! //! // Iterate over users: //! for user in &users { //! println!("User {}: {}", user.name, user.age); //! } //! # } //! ``` //! //! Keys don't have to be borrowed from the value. For smaller `Copy` types, //! it's recommended that you use owned keys. Here's an example of using //! [`IdOrdMap`] with a small integer key: //! //! ``` //! # #[cfg(feature = "std")] { //! # use iddqd::{IdOrdMap, IdOrdItem, id_upcast}; //! struct Record { //! id: u32, //! data: String, //! } //! //! impl IdOrdItem for Record { //! // The key type is small, so an owned key is preferred. //! type Key<'a> = u32; //! //! fn key(&self) -> Self::Key<'_> { //! self.id //! } //! //! id_upcast!(); //! } //! //! // ... //! # } //! ``` //! //! An example for [`IdHashMap`], showing a complex borrowed key. Here, //! "complex" means that the key is not a reference itself, but a struct that //! returns references to more than one field from the value. //! //! ``` //! # #[cfg(feature = "default-hasher")] { //! use iddqd::{IdHashItem, id_hash_map, id_upcast}; //! //! #[derive(Debug)] //! struct Artifact { //! name: String, //! version: String, //! data: Vec, //! } //! //! // The key type is a borrowed form of the name and version. It needs to //! // implement `Eq + Hash`. //! #[derive(Eq, Hash, PartialEq)] //! struct ArtifactKey<'a> { //! name: &'a str, //! version: &'a str, //! } //! //! impl IdHashItem for Artifact { //! // The key type can borrow from the value. //! type Key<'a> = ArtifactKey<'a>; //! //! fn key(&self) -> Self::Key<'_> { //! ArtifactKey { name: &self.name, version: &self.version } //! } //! //! id_upcast!(); //! } //! //! // Create a new `IdHashMap` with the given artifacts. This uses the //! // `id_hash_map!` macro that comes with iddqd. //! let artifacts = id_hash_map! { //! Artifact { //! name: "artifact1".to_owned(), //! version: "1.0".to_owned(), //! data: b"data1".to_vec(), //! }, //! Artifact { //! name: "artifact2".to_owned(), //! version: "1.0".to_owned(), //! data: b"data2".to_vec(), //! }, //! }; //! //! // Look up artifacts by name and version. //! assert_eq!( //! artifacts //! .get(&ArtifactKey { name: "artifact1", version: "1.0" }) //! .unwrap() //! .data, //! b"data1", //! ); //! # } //! ``` //! //! For more examples, see the //! [examples](https://github.com/oxidecomputer/iddqd/tree/main/crates/iddqd/examples) //! and [extended //! examples](https://github.com/oxidecomputer/iddqd/tree/main/crates/iddqd-extended-examples/examples) //! directories. //! //! ### `Equivalent` and `Comparable` //! //! An important feature of the standard library's maps is that they allow any //! borrowed form of the key type to be used for lookups; for example, a //! `HashMap` type can be looked up with a `&str` key. This is done //! through the [`Borrow`] trait. //! //! But the [`Borrow`] trait is a bit too restrictive for complex keys such as //! `ArtifactKey` above, requiring workarounds such as [dynamic //! dispatch](https://github.com/sunshowers-code/borrow-complex-key-example). To //! address this, the crates.io ecosystem has standardized on the [`Equivalent`] //! and [`Comparable`] traits as generalizations of `Borrow`. The map types in //! this crate require these traits. //! //! For a key type `T::Key<'_>` and a lookup type `L`: //! //! * The hash map types require `L: Hash + Equivalent>`. Also, `L` //! must hash in the same way as `T::Key<'_>`. Typically, this is done by //! ensuring that enum variants and struct fields are in the same //! order[^proptest]. //! * [`IdOrdMap`] requires `L: Comparable>`, which in turn requires //! `Equivalent>`. (There's no need for `L` to implement `Ord` or //! `Eq` itself.) //! //! [^proptest]: We recommend that you test this with e.g. a property-based //! test: see [this //! example](https://github.com/sunshowers-code/borrow-complex-key-example/blob/a6f17699/src/lib.rs#L233). //! //! Continuing the `ArtifactKey` example from above, we can perform a lookup //! using a key of this owned form: //! //! ``` //! # #[cfg(feature = "default-hasher")] { //! use equivalent::Equivalent; //! # use iddqd::{id_hash_map, IdHashItem, id_upcast}; //! # #[derive(Debug)] //! # struct Artifact { //! # name: String, //! # version: String, //! # data: Vec, //! # } //! # #[derive(Eq, Hash, PartialEq)] //! # struct ArtifactKey<'a> { //! # name: &'a str, //! # version: &'a str, //! # } //! # impl IdHashItem for Artifact { //! # type Key<'a> = ArtifactKey<'a>; //! # fn key(&self) -> Self::Key<'_> { //! # ArtifactKey { //! # name: &self.name, //! # version: &self.version, //! # } //! # } //! # id_upcast!(); //! # } //! # let artifacts = id_hash_map! { //! # Artifact { //! # name: "artifact1".to_owned(), //! # version: "1.0".to_owned(), //! # data: b"data1".to_vec(), //! # } //! # }; //! //! // This is an owned form of ArtifactKey. The fields are in the same //! // order as ArtifactKey's fields, so it hashes the same way. //! #[derive(Hash)] //! struct OwnedArtifactKey { //! name: String, //! version: String, //! } //! //! impl Equivalent> for OwnedArtifactKey { //! fn equivalent(&self, other: &ArtifactKey<'_>) -> bool { //! self.name == other.name && self.version == other.version //! } //! } //! //! // Now you can use OwnedArtifactKey to look up the artifact. //! let owned_key = OwnedArtifactKey { //! name: "artifact1".to_owned(), //! version: "1.0".to_owned(), //! }; //! assert_eq!(artifacts.get(&owned_key).unwrap().data, b"data1",); //! # } //! ``` //! //! There's a blanket implementation of [`Equivalent`] and [`Comparable`] for //! [`Borrow`], so if your type already implements [`Borrow`], there aren't any //! extra steps to take. //! //! # Testing //! //! This crate is validated through a combination of: //! //! * Unit tests //! * Property-based tests using a naive map as an oracle //! * Chaos tests for several kinds of buggy `Eq` and `Ord` implementations //! * Miri tests for unsafe code //! //! If you see a gap in testing, new tests are welcome. Thank you! //! //! # No-std compatibility //! //! Most of this crate is no-std compatible, though [`alloc`] is required. //! //! The [`IdOrdMap`] type is not currently no-std compatible due to its use of a //! thread-local. This thread-local is just a way to work around a limitation in //! std's `BTreeMap` API, though. Either a custom B-Tree implementation, or a //! platform-specific notion of thread locals, would suffice to make //! [`IdOrdMap`] no-std compatible. //! //! # Optional features //! //! - `allocator-api2`: Enables support for custom allocators via the //! [`allocator_api2`] crate. Both global and scoped/arena allocators //! (such as `bumpalo`) are supported. Custom allocators are not currently //! supported by `IdOrdMap`. //! - `daft`: Enables [`daft`] support for all ID map types. *Not enabled by //! default.* //! - `default-hasher`: Enables the `DefaultHashBuilder` type. Disable this //! feature to require a hash builder type parameter to be passed into //! [`IdHashMap`], [`BiHashMap`], and [`TriHashMap`]. *Enabled by default.* //! - `proptest`: Enables [`proptest`] support for all ID map types, providing //! [`Arbitrary`] implementations and strategies for property-based testing. //! *Not enabled by default.* //! - `schemars08`: Enables [`schemars`] support for all ID map types, //! including support for [automatic replacement] through [`typify`] or //! [`dropshot`]. *Not enabled by default.* //! - `serde`: Enables serde support for all ID map types. *Not enabled by //! default.* //! - `std`: Enables std support. *Enabled by default.* //! //! # Related work //! //! - [`bimap`](https://docs.rs/bimap) provides a bijective map, but does not //! have a way to associate arbitrary values with each pair of keys. However, //! it does support an ordered map type without the need for std. //! //! - [`multi_index_map`](https://crates.io/crates/multi_index_map) provides //! maps with arbitrary indexes on fields, and is more flexible than this //! crate. However, it doesn't expose generic traits for map types, and it //! requires key types to be `Clone`. In `iddqd`, we pick a somewhat different //! point in the design space, but we think `multi_index_map` is also great. //! //! - In general, this is similar to relational database records with //! indexes. For sufficiently complex use cases, consider an embedded //! database like [SQLite](https://www.sqlite.org/), or even a networked //! database like [PostgreSQL](https://www.postgresql.org/). `iddqd` is a //! good fit for simple in-memory caches of data stored in these databases. //! //! # Minimum supported Rust version (MSRV) //! //! This crate's MSRV is **Rust 1.81**. In general we aim for 6 months of Rust //! compatibility. //! //! # What does iddqd mean? //! //! The name `iddqd` is a reference to [a cheat //! code](https://doomwiki.org/wiki/Doom_cheat_codes) in the classic video game //! _Doom_. It has `id` in the name, and is short and memorable. //! //! [`Borrow`]: core::borrow::Borrow //! [JSON Schema]: https://json-schema.org/ //! [OpenAPI]: https://www.openapis.org/ //! [`schemars`]: https://crates.io/crates/schemars //! [automatic replacement]: https://github.com/oxidecomputer/iddqd/blob/main/crates/iddqd-extended-examples/examples/typify-types.rs //! [`typify`]: https://crates.io/crates/typify //! [`dropshot`]: https://crates.io/crates/dropshot //! [`Arbitrary`]: proptest::arbitrary::Arbitrary #![no_std] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![warn(missing_docs)] #[cfg_attr(not(feature = "std"), macro_use)] // for `format!` extern crate alloc; #[cfg(feature = "std")] #[macro_use] extern crate std; // This must go first so macros can be used in the rest of the crate. #[macro_use] mod macros; pub mod bi_hash_map; pub mod errors; pub mod id_hash_map; #[cfg(feature = "std")] pub mod id_ord_map; #[doc(hidden)] pub mod internal; mod support; pub mod tri_hash_map; pub use bi_hash_map::{imp::BiHashMap, trait_defs::BiHashItem}; // Re-exports of equivalent traits. Comparable is only used by IdOrdMap, hence // is restricted to std. #[cfg(feature = "std")] #[doc(no_inline)] pub use equivalent::Comparable; #[doc(no_inline)] pub use equivalent::Equivalent; pub use id_hash_map::{imp::IdHashMap, trait_defs::IdHashItem}; #[cfg(feature = "std")] pub use id_ord_map::{imp::IdOrdMap, trait_defs::IdOrdItem}; #[cfg(feature = "daft")] pub use support::daft_utils::IdLeaf; pub use support::hash_builder::DefaultHashBuilder; pub use tri_hash_map::{imp::TriHashMap, trait_defs::TriHashItem}; iddqd-0.3.17/src/macros.rs000064400000000000000000000312451046102023000134270ustar 00000000000000//! Macros for this crate. /// Creates an [`IdHashMap`](crate::IdHashMap) from a list of items. /// /// An optional [`BuildHasher`](core::hash::BuildHasher) that implements /// `Default` can be provided as the first argument, followed by a semicolon. /// /// # Panics /// /// Panics if the list of items has duplicate keys. For better error handling, /// the item is required to implement `Debug`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{IdHashItem, id_hash_map, id_upcast}; /// /// #[derive(Debug)] /// struct User { /// id: u32, /// name: String, /// } /// /// impl IdHashItem for User { /// type Key<'a> = u32; /// fn key(&self) -> Self::Key<'_> { /// self.id /// } /// id_upcast!(); /// } /// /// let map = id_hash_map! { /// User { id: 1, name: "Alice".to_string() }, /// User { id: 2, name: "Bob".to_string() }, /// }; /// assert_eq!(map.get(&1).unwrap().name, "Alice"); /// assert_eq!(map.get(&2).unwrap().name, "Bob"); /// /// // With a custom hasher: /// let map = id_hash_map! { /// foldhash::quality::RandomState; /// User { id: 3, name: "Charlie".to_string() }, /// User { id: 4, name: "Eve".to_string() }, /// }; /// assert_eq!(map.get(&3).unwrap().name, "Charlie"); /// assert_eq!(map.get(&4).unwrap().name, "Eve"); /// # } /// ``` #[macro_export] macro_rules! id_hash_map { ($($item:expr,)+) => { $crate::id_hash_map!($($item),+) }; ($($item:expr),*) => { { // Note: `stringify!($key)` is just here to consume the repetition, // but we throw away that string literal during constant evaluation. const CAP: usize = <[()]>::len(&[$({ stringify!($item); }),*]); let mut map = $crate::IdHashMap::with_capacity(CAP); $( map.insert_unique($item).unwrap(); )* map } }; ($H:ty; $($item:expr,)+) => { $crate::id_hash_map!($H; $($item),+) }; ($H:ty; $($item:expr),*) => { { // Note: `stringify!($key)` is just here to consume the repetition, // but we throw away that string literal during constant evaluation. const CAP: usize = <[()]>::len(&[$({ stringify!($item); }),*]); let mut map = $crate::IdHashMap::with_capacity_and_hasher(CAP, <$H>::default()); $( map.insert_unique($item).unwrap(); )* map } }; } /// Creates an [`IdOrdMap`](crate::IdOrdMap) from a list of items. /// /// # Panics /// /// Panics if the list of items has duplicate keys. For better error handling, /// the item is required to implement `Debug`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "std")] { /// use iddqd::{IdOrdItem, id_ord_map, id_upcast}; /// /// #[derive(Debug)] /// struct User { /// id: u32, /// name: String, /// } /// /// impl IdOrdItem for User { /// type Key<'a> = u32; /// fn key(&self) -> Self::Key<'_> { /// self.id /// } /// id_upcast!(); /// } /// /// let map = id_ord_map! { /// User { id: 1, name: "Alice".to_string() }, /// User { id: 2, name: "Bob".to_string() }, /// }; /// assert_eq!(map.get(&1).unwrap().name, "Alice"); /// assert_eq!(map.get(&2).unwrap().name, "Bob"); /// # } /// ``` #[cfg(feature = "std")] #[macro_export] macro_rules! id_ord_map { ($($item:expr,)+) => { $crate::id_ord_map!($($item),+) }; ($($item:expr),*) => { { // Note: `stringify!($key)` is just here to consume the repetition, // but we throw away that string literal during constant evaluation. const CAP: usize = <[()]>::len(&[$({ stringify!($item); }),*]); let mut map = $crate::IdOrdMap::with_capacity(CAP); $( map.insert_unique($item).unwrap(); )* map } }; } /// Creates a [`BiHashMap`](crate::BiHashMap) from a list of items. /// /// An optional [`BuildHasher`](core::hash::BuildHasher) that implements /// `Default` can be provided as the first argument, followed by a semicolon. /// /// # Panics /// /// Panics if the list of items has duplicate keys. For better error handling, /// the item is required to implement `Debug`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{BiHashItem, bi_hash_map, bi_upcast}; /// /// #[derive(Debug)] /// struct User { /// id: u32, /// name: String, /// } /// /// impl BiHashItem for User { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// bi_upcast!(); /// } /// /// let map = bi_hash_map! { /// User { id: 1, name: "Alice".to_string() }, /// User { id: 2, name: "Bob".to_string() }, /// }; /// assert_eq!(map.get1(&1).unwrap().name, "Alice"); /// assert_eq!(map.get2("Bob").unwrap().id, 2); /// /// // With a custom hasher: /// let map = bi_hash_map! { /// foldhash::quality::RandomState; /// User { id: 3, name: "Charlie".to_string() }, /// User { id: 4, name: "Eve".to_string() }, /// }; /// assert_eq!(map.get1(&3).unwrap().name, "Charlie"); /// assert_eq!(map.get2("Eve").unwrap().id, 4); /// # } /// ``` #[macro_export] macro_rules! bi_hash_map { ($($item:expr,)+) => { $crate::bi_hash_map!($($item),+) }; ($($item:expr),*) => { { // Note: `stringify!($key)` is just here to consume the repetition, // but we throw away that string literal during constant evaluation. const CAP: usize = <[()]>::len(&[$({ stringify!($item); }),*]); let mut map = $crate::BiHashMap::with_capacity(CAP); $( map.insert_unique($item).unwrap(); )* map } }; ($H:ty; $($item:expr,)+) => { $crate::bi_hash_map!($H; $($item),+) }; ($H:ty; $($item:expr),*) => { { // Note: `stringify!($key)` is just here to consume the repetition, // but we throw away that string literal during constant evaluation. const CAP: usize = <[()]>::len(&[$({ stringify!($item); }),*]); let mut map = $crate::BiHashMap::with_capacity_and_hasher(CAP, <$H>::default()); $( map.insert_unique($item).unwrap(); )* map } }; } /// Creates a [`TriHashMap`](crate::TriHashMap) from a list of items. /// /// An optional [`BuildHasher`](core::hash::BuildHasher) that implements /// `Default` can be provided as the first argument, followed by a semicolon. /// /// # Panics /// /// Panics if the list of items has duplicate keys. For better error handling, /// the item is required to implement `Debug`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, tri_hash_map, tri_upcast}; /// /// #[derive(Debug)] /// struct Person { /// id: u32, /// name: String, /// email: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.email /// } /// tri_upcast!(); /// } /// /// let map = tri_hash_map! { /// Person { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() }, /// Person { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() }, /// }; /// assert_eq!(map.get1(&1).unwrap().name, "Alice"); /// assert_eq!(map.get2("Bob").unwrap().id, 2); /// assert_eq!(map.get3("alice@example.com").unwrap().name, "Alice"); /// /// // With a custom hasher: /// let map = tri_hash_map! { /// foldhash::quality::RandomState; /// Person { id: 3, name: "Charlie".to_string(), email: "charlie@example.com".to_string() }, /// Person { id: 4, name: "Eve".to_string(), email: "eve@example.com".to_string() }, /// }; /// assert_eq!(map.get1(&3).unwrap().name, "Charlie"); /// assert_eq!(map.get2("Eve").unwrap().id, 4); /// # } /// ``` #[macro_export] macro_rules! tri_hash_map { ($($item:expr,)+) => { $crate::tri_hash_map!($($item),+) }; ($($item:expr),*) => { { // Note: `stringify!($key)` is just here to consume the repetition, // but we throw away that string literal during constant evaluation. const CAP: usize = <[()]>::len(&[$({ stringify!($item); }),*]); let mut map = $crate::TriHashMap::with_capacity(CAP); $( map.insert_unique($item).unwrap(); )* map } }; ($H:ty; $($item:expr,)+) => { $crate::tri_hash_map!($H; $($item),+) }; ($H:ty; $($item:expr),*) => { { // Note: `stringify!($key)` is just here to consume the repetition, // but we throw away that string literal during constant evaluation. const CAP: usize = <[()]>::len(&[$({ stringify!($item); }),*]); let mut map = $crate::TriHashMap::with_capacity_and_hasher(CAP, <$H>::default()); $( map.insert_unique($item).unwrap(); )* map } }; } /// Implement upcasts for [`IdOrdMap`] or [`IdHashMap`]. /// /// The maps in this crate require that the key types' lifetimes are covariant. /// This macro assists with implementing this requirement. /// /// The macro is optional, and these implementations can be written by hand as /// well. /// /// [`IdOrdMap`]: crate::IdOrdMap /// [`IdHashMap`]: crate::IdHashMap #[macro_export] macro_rules! id_upcast { () => { #[inline] fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> where Self: 'long, { long } }; } /// Implement upcasts for [`BiHashMap`]. /// /// The maps in this crate require that the key types' lifetimes are covariant. /// This macro assists with implementing this requirement. /// /// The macro is optional, and these implementations can be written by hand as /// well. /// /// [`BiHashMap`]: crate::BiHashMap #[macro_export] macro_rules! bi_upcast { () => { #[inline] fn upcast_key1<'short, 'long: 'short>( long: Self::K1<'long>, ) -> Self::K1<'short> where Self: 'long, { long } #[inline] fn upcast_key2<'short, 'long: 'short>( long: Self::K2<'long>, ) -> Self::K2<'short> where Self: 'long, { long } }; } /// Implement upcasts for [`TriHashMap`]. /// /// The maps in this crate require that the key types' lifetimes are covariant. /// This macro assists with implementing this requirement. /// /// The macro is optional, and these implementations can be written by hand as /// well. /// /// [`TriHashMap`]: crate::TriHashMap #[macro_export] macro_rules! tri_upcast { () => { #[inline] fn upcast_key1<'short, 'long: 'short>( long: Self::K1<'long>, ) -> Self::K1<'short> where Self: 'long, { long } #[inline] fn upcast_key2<'short, 'long: 'short>( long: Self::K2<'long>, ) -> Self::K2<'short> where Self: 'long, { long } #[inline] fn upcast_key3<'short, 'long: 'short>( long: Self::K3<'long>, ) -> Self::K3<'short> where Self: 'long, { long } }; } // Internal macro to implement diffs. #[cfg(feature = "daft")] macro_rules! impl_diff_ref_cast { ($self: ident, $diff_ty: ty, $key_method: ident, $get_method: ident, $contains_method: ident, $ref_cast_ty: ty) => {{ let hasher = $self.before.hasher().clone(); let alloc = $self.before.allocator().clone(); let mut diff = <$diff_ty>::with_hasher_in(hasher, alloc); for before_item in $self.before { if let Some(after_item) = $self.after.$get_method(&before_item.$key_method()) { diff.common.insert_overwrite(IdLeaf::new( <$ref_cast_ty>::ref_cast(before_item), <$ref_cast_ty>::ref_cast(after_item), )); } else { diff.removed .insert_overwrite(<$ref_cast_ty>::ref_cast(before_item)); } } for after_item in $self.after { if !$self.before.$contains_method(&after_item.$key_method()) { diff.added .insert_overwrite(<$ref_cast_ty>::ref_cast(after_item)); } } diff }}; } iddqd-0.3.17/src/support/alloc.rs000064400000000000000000000061531046102023000147510ustar 00000000000000// Adapted from the hashbrown crate, which is licensed under MIT OR Apache-2.0. // Copyright (c) 2016-2025 Amanieu d'Antras and others // SPDX-License-Identifier: MIT OR Apache-2.0 pub use self::inner::Global; pub(crate) use self::inner::{AllocWrapper, Allocator, global_alloc}; // TODO: support nightly. // Basic non-nightly case. #[cfg(feature = "allocator-api2")] mod inner { use allocator_api2::alloc::AllocError; pub use allocator_api2::alloc::{Allocator, Global, Layout}; use core::ptr::NonNull; #[inline] pub(crate) const fn global_alloc() -> Global { Global } #[derive(Clone, Copy, Default)] pub(crate) struct AllocWrapper(pub(crate) T); // SAFETY: These functions just forward to the wrapped allocator. unsafe impl allocator_api2::alloc::Allocator for AllocWrapper { #[inline] fn allocate( &self, layout: Layout, ) -> Result, AllocError> { allocator_api2::alloc::Allocator::allocate(&self.0, layout) } #[inline] unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { allocator_api2::alloc::Allocator::deallocate(&self.0, ptr, layout); } } } // No-defaults case. #[cfg(not(feature = "allocator-api2"))] mod inner { use crate::alloc::alloc::Layout; use allocator_api2::alloc::AllocError; use core::ptr::NonNull; #[inline] pub(crate) const fn global_alloc() -> Global { Global::new() } #[allow(clippy::missing_safety_doc)] // not exposed outside of this crate pub unsafe trait Allocator { fn allocate(&self, layout: Layout) -> Result, AllocError>; unsafe fn deallocate(&self, ptr: NonNull, layout: Layout); } #[derive(Copy, Clone, Default)] #[doc(hidden)] pub struct Global(allocator_api2::alloc::Global); impl Global { #[inline] pub const fn new() -> Self { Global(allocator_api2::alloc::Global) } } // SAFETY: These functions just forward to the wrapped allocator. unsafe impl Allocator for Global { #[inline] fn allocate( &self, layout: Layout, ) -> Result, AllocError> { allocator_api2::alloc::Allocator::allocate(&self.0, layout) } #[inline] unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { allocator_api2::alloc::Allocator::deallocate(&self.0, ptr, layout); } } #[derive(Clone, Copy, Default)] pub(crate) struct AllocWrapper(pub(crate) T); // SAFETY: These functions just forward to the wrapped allocator. unsafe impl allocator_api2::alloc::Allocator for AllocWrapper { #[inline] fn allocate( &self, layout: Layout, ) -> Result, AllocError> { Allocator::allocate(&self.0, layout) } #[inline] unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { Allocator::deallocate(&self.0, ptr, layout); } } } iddqd-0.3.17/src/support/borrow.rs000064400000000000000000000064211046102023000151670ustar 00000000000000// Adapted from the Rust standard library, which is licensed under MIT OR // Apache-2.0. // Copyright (c) The Rust Project Developers // SPDX-License-Identifier: MIT OR Apache-2.0 use core::{marker::PhantomData, ptr::NonNull}; /// Models a reborrow of some unique reference, when you know that the reborrow /// and all its descendants (i.e., all pointers and references derived from it) /// will not be used any more at some point, after which you want to use the /// original unique reference again. /// /// The borrow checker usually handles this stacking of borrows for you, but /// some control flows that accomplish this stacking are too complicated for /// the compiler to follow. A `DormantMutRef` allows you to check borrowing /// yourself, while still expressing its stacked nature, and encapsulating /// the raw pointer code needed to do this without undefined behavior. pub(crate) struct DormantMutRef<'a, T> { ptr: NonNull, _marker: PhantomData<&'a mut T>, } // SAFETY: DormantMutRef<'a, T> stores exactly a reference to T. The "where" // clause is that &mut T implements Sync. unsafe impl<'a, T> Sync for DormantMutRef<'a, T> where &'a mut T: Sync {} // SAFETY: DormantMutRef<'a, T> stores exactly a reference to T. The "where" // clause is that &mut T implements Send. unsafe impl<'a, T> Send for DormantMutRef<'a, T> where &'a mut T: Send {} impl<'a, T> DormantMutRef<'a, T> { /// Capture a unique borrow, and immediately reborrow it. For the compiler, /// the lifetime of the new reference is the same as the lifetime of the /// original reference, but you promise to use it for a shorter period. pub(crate) fn new(t: &'a mut T) -> (&'a mut T, Self) { let ptr = NonNull::from(t); // SAFETY: we hold the borrow throughout 'a via `_marker`, and we expose // only this reference, so it is unique. let new_ref = unsafe { &mut *ptr.as_ptr() }; (new_ref, Self { ptr, _marker: PhantomData }) } /// Revert to the unique borrow initially captured. /// /// # Safety /// /// The reborrow must have ended, i.e., the reference returned by `new` and /// all pointers and references derived from it, must not be used anymore. pub(crate) unsafe fn awaken(self) -> &'a mut T { // SAFETY: our own safety conditions imply this reference is again unique. unsafe { &mut *self.ptr.as_ptr() } } /// Borrows a new mutable reference from the unique borrow initially captured. /// /// # Safety /// /// The reborrow must have ended, i.e., the reference returned by `new` and /// all pointers and references derived from it, must not be used anymore. pub(crate) unsafe fn reborrow(&mut self) -> &'a mut T { // SAFETY: our own safety conditions imply this reference is again unique. unsafe { &mut *self.ptr.as_ptr() } } /// Borrows a new shared reference from the unique borrow initially captured. /// /// # Safety /// /// The reborrow must have ended, i.e., the reference returned by `new` and /// all pointers and references derived from it, must not be used anymore. pub(crate) unsafe fn reborrow_shared(&self) -> &'a T { // SAFETY: our own safety conditions imply this reference is again unique. unsafe { &*self.ptr.as_ptr() } } } iddqd-0.3.17/src/support/btree_table.rs000064400000000000000000000330151046102023000161240ustar 00000000000000//! A "table" of b-tree-based indexes. //! //! Similar to [`super::hash_table::MapHashTable`], b-tree based tables store //! integers (that are indexes corresponding to items), but use an external //! comparator. use super::map_hash::MapHash; use crate::internal::{TableValidationError, ValidateCompact}; use alloc::{ collections::{BTreeSet, btree_set}, vec::Vec, }; use core::{ cell::Cell, cmp::Ordering, hash::{BuildHasher, Hash}, marker::PhantomData, }; use equivalent::Comparable; thread_local! { /// Stores an external comparator function to provide dynamic scoping. /// /// std's BTreeMap doesn't allow passing an external comparator, so we make /// do with this function that's passed in through dynamic scoping. /// /// This works by: /// /// * We store an `Index` in the BTreeSet which knows how to call this /// dynamic comparator. /// * When we need to compare two `Index` values, we create a CmpDropGuard. /// This struct is responsible for managing the lifetime of the /// comparator. /// * When the CmpDropGuard is dropped (including due to a panic), we reset /// the comparator to None. /// /// This is not great! (For one, thread-locals and no-std don't really mix.) /// Some alternatives: /// /// * Using `Borrow` as described in /// https://github.com/sunshowers-code/borrow-complex-key-example. While /// hacky, this actually works for the find operation. But the insert /// operation currently requires a concrete `Index`. /// /// If and when https://github.com/rust-lang/rust/issues/133549 lands, /// this should become a viable option. Worth looking out for! /// /// * Using a third-party BTreeSet implementation that allows passing in /// external comparators. As of 2025-05, there appear to be two options: /// /// 1. copse (https://docs.rs/copse), which doesn't seem like a good fit /// here. /// 2. btree_monstrousity (https://crates.io/crates/btree_monstrousity), /// which has an API perfect for this but is, uhh, not really /// production-ready. /// /// Third-party implementations also run the risk of being relatively /// untested. /// /// * Using some other kind of sorted set. We've picked B-trees here as the /// default choice to balance cache locality, but other options are worth /// benchmarking. We do need to provide a comparator, though, so radix /// trees and such are out of the question. static CMP: Cell Ordering>> = const { Cell::new(None) }; } /// A B-tree-based table with an external comparator. #[derive(Clone, Debug, Default)] pub(crate) struct MapBTreeTable { items: BTreeSet, // We use foldhash directly here because we allow compiling with std but // without the default-hasher. std turns on foldhash but not the default // hasher. hash_state: foldhash::fast::FixedState, } impl MapBTreeTable { pub(crate) const fn new() -> Self { Self { items: BTreeSet::new(), // FixedState::with_seed XORs the passed in seed with a fixed // high-entropy value. hash_state: foldhash::fast::FixedState::with_seed(0), } } #[doc(hidden)] pub(crate) fn len(&self) -> usize { self.items.len() } #[doc(hidden)] pub(crate) fn validate( &self, expected_len: usize, compactness: ValidateCompact, ) -> Result<(), TableValidationError> { if self.len() != expected_len { return Err(TableValidationError::new(format!( "expected length {expected_len}, was {}", self.len(), ))); } match compactness { ValidateCompact::Compact => { // All items between 0 (inclusive) and self.len() (exclusive) // are present, and there are no duplicates. Also, the sentinel // value should not be stored. let mut indexes: Vec<_> = Vec::with_capacity(expected_len); for index in &self.items { match index.0 { Index::SENTINEL_VALUE => { return Err(TableValidationError::new( "sentinel value should not be stored in map", )); } v => { indexes.push(v); } } } indexes.sort_unstable(); for (i, index) in indexes.iter().enumerate() { if *index != i { return Err(TableValidationError::new(format!( "value at index {i} should be {i}, was {index}", ))); } } } ValidateCompact::NonCompact => { // There should be no duplicates, and the sentinel value // should not be stored. let indexes: Vec<_> = self.items.iter().copied().collect(); let index_set: BTreeSet = indexes.iter().map(|ix| ix.0).collect(); if index_set.len() != indexes.len() { return Err(TableValidationError::new(format!( "expected no duplicates, but found {} duplicates \ (values: {:?})", indexes.len() - index_set.len(), indexes, ))); } if index_set.contains(&Index::SENTINEL_VALUE) { return Err(TableValidationError::new( "sentinel value should not be stored in map", )); } } } Ok(()) } #[inline] pub(crate) fn first(&self) -> Option { self.items.first().map(|ix| ix.0) } #[inline] pub(crate) fn last(&self) -> Option { self.items.last().map(|ix| ix.0) } pub(crate) fn find_index( &self, key: &Q, lookup: F, ) -> Option where K: Ord, Q: ?Sized + Comparable, F: Fn(usize) -> K, { let f = find_cmp(key, lookup); let guard = CmpDropGuard::new(&f); let ret = match self.items.get(&Index::SENTINEL) { Some(Index(v)) if *v == Index::SENTINEL_VALUE => { panic!("internal map shouldn't store sentinel value") } Some(Index(v)) => Some(*v), None => { // The key is not in the table. None } }; // drop(guard) isn't necessary, but we make it explicit drop(guard); ret } pub(crate) fn insert(&mut self, index: usize, key: &Q, lookup: F) where K: Ord, Q: ?Sized + Comparable, F: Fn(usize) -> K, { let f = insert_cmp(index, key, lookup); let guard = CmpDropGuard::new(&f); self.items.insert(Index::new(index)); // drop(guard) isn't necessary, but we make it explicit drop(guard); } pub(crate) fn remove(&mut self, index: usize, key: K, lookup: F) where F: Fn(usize) -> K, K: Ord, { let f = insert_cmp(index, &key, lookup); let guard = CmpDropGuard::new(&f); self.items.remove(&Index::new(index)); // drop(guard) isn't necessary, but we make it explicit drop(guard); } pub(crate) fn retain(&mut self, mut f: F) where F: FnMut(usize) -> bool, { // We don't need to set up a comparator in the environment because // `retain` doesn't do any comparisons as part of its operation. self.items.retain(|index| f(index.0)); } /// Clears the B-tree table, removing all items. #[inline] pub(crate) fn clear(&mut self) { self.items.clear(); } pub(crate) fn iter(&self) -> Iter<'_> { Iter::new(self.items.iter()) } pub(crate) fn into_iter(self) -> IntoIter { IntoIter::new(self.items.into_iter()) } pub(crate) fn state(&self) -> &foldhash::fast::FixedState { &self.hash_state } pub(crate) fn compute_hash(&self, key: K) -> MapHash { MapHash { hash: self.hash_state.hash_one(key) } } } #[derive(Clone, Debug)] pub(crate) struct Iter<'a> { inner: btree_set::Iter<'a, Index>, } impl<'a> Iter<'a> { fn new(inner: btree_set::Iter<'a, Index>) -> Self { Self { inner } } pub(crate) fn len(&self) -> usize { self.inner.len() } } impl<'a> Iterator for Iter<'a> { type Item = usize; fn next(&mut self) -> Option { self.inner.next().map(|index| index.0) } } #[derive(Debug)] pub(crate) struct IntoIter { inner: btree_set::IntoIter, } impl IntoIter { fn new(inner: btree_set::IntoIter) -> Self { Self { inner } } } impl Iterator for IntoIter { type Item = usize; fn next(&mut self) -> Option { self.inner.next().map(|index| index.0) } } fn find_cmp<'a, K, Q, F>( key: &'a Q, lookup: F, ) -> impl Fn(Index, Index) -> Ordering + 'a where Q: ?Sized + Comparable, F: 'a + Fn(usize) -> K, K: Ord, { move |a: Index, b: Index| { if a.0 == b.0 { // This is potentially load-bearing! It means that even if the Eq // implementation on map items is wrong, we treat items at the same // index as equal. // // Unsafe code relies on this to ensure that we don't return // multiple mutable references to the same index. return Ordering::Equal; } match (a.0, b.0) { (Index::SENTINEL_VALUE, v) => key.compare(&lookup(v)), (v, Index::SENTINEL_VALUE) => key.compare(&lookup(v)).reverse(), (a, b) => lookup(a).cmp(&lookup(b)), } } } fn insert_cmp<'a, K, Q, F>( index: usize, key: &'a Q, lookup: F, ) -> impl Fn(Index, Index) -> Ordering + 'a where Q: ?Sized + Comparable, F: 'a + Fn(usize) -> K, K: Ord, { move |a: Index, b: Index| { if a.0 == b.0 { // This is potentially load-bearing! It means that even if the Eq // implementation on map items is wrong, we treat items at the same // index as equal. // // Unsafe code relies on this to ensure that we don't return // multiple mutable references to the same index. return Ordering::Equal; } match (a.0, b.0) { // The sentinel value should not be invoked at all, because it's not // passed in during insert and not stored in the table. (Index::SENTINEL_VALUE, _) | (_, Index::SENTINEL_VALUE) => { panic!("sentinel value should not be invoked in insert path") } (a, b) if a == index => key.compare(&lookup(b)), (a, b) if b == index => key.compare(&lookup(a)).reverse(), (a, b) => lookup(a).cmp(&lookup(b)), } } } struct CmpDropGuard<'a> { _marker: PhantomData<&'a ()>, } impl<'a> CmpDropGuard<'a> { fn new(f: &'a dyn Fn(Index, Index) -> Ordering) -> Self { // CMP lasts only as long as this function and is immediately reset to // None once this scope is left. let ret = Self { _marker: PhantomData }; // SAFETY: This is safe because we are not storing the reference // anywhere, and it is only used for the lifetime of this CmpDropGuard. let as_static = unsafe { std::mem::transmute::< &'a dyn Fn(Index, Index) -> Ordering, &'static dyn Fn(Index, Index) -> Ordering, >(f) }; CMP.set(Some(as_static)); ret } } impl Drop for CmpDropGuard<'_> { fn drop(&mut self) { CMP.set(None); } } #[derive(Clone, Copy, Debug)] struct Index(usize); impl Index { const SENTINEL_VALUE: usize = usize::MAX; const SENTINEL: Self = Self(Self::SENTINEL_VALUE); #[inline] fn new(value: usize) -> Self { if value == Self::SENTINEL_VALUE { panic!("btree map overflow, index with value {value:?} was added") } Self(value) } } impl PartialEq for Index { fn eq(&self, other: &Self) -> bool { // For non-sentinel indexes, two values are the same iff their indexes // are the same. This is ensured by the fact that our key types // implement Eq (as part of implementing Ord). if self.0 != Self::SENTINEL_VALUE && other.0 != Self::SENTINEL_VALUE { return self.0 == other.0; } // If any of the two indexes is the sentinel, we're required to perform // a lookup. CMP.with(|cmp| { let cmp = cmp.get().expect("cmp should be set"); cmp(*self, *other) == Ordering::Equal }) } } impl Eq for Index {} impl Ord for Index { #[inline] fn cmp(&self, other: &Self) -> Ordering { // Ord should only be called if we're doing lookups within the table, // which should have set the thread local. CMP.with(|cmp| { let cmp = cmp.get().expect("cmp should be set"); cmp(*self, *other) }) } } impl PartialOrd for Index { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } iddqd-0.3.17/src/support/daft_utils.rs000064400000000000000000000060251046102023000160130ustar 00000000000000use core::ops::{Deref, DerefMut}; use daft::{Diffable, Leaf}; /// A leaf type similar to [`daft::Leaf`], which statically guarantees that the /// before and after values have the same key or keys. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct IdLeaf { before: T, after: T, } impl IdLeaf { pub(crate) fn new(before: T, after: T) -> Self { IdLeaf { before, after } } /// Returns the `before` value. #[inline] pub fn before(&self) -> &T { &self.before } /// Returns the `after` value. #[inline] pub fn after(&self) -> &T { &self.after } /// Converts self into a [`daft::Leaf`]. #[inline] pub fn into_leaf(self) -> Leaf { Leaf { before: self.before, after: self.after } } /// Converts from `&IdLeaf` to `IdLeaf<&T>`. #[inline] pub fn as_ref(&self) -> IdLeaf<&T> { IdLeaf { before: &self.before, after: &self.after } } /// Converts from `&mut IdLeaf` to `IdLeaf<&mut T>`. #[inline] pub fn as_mut(&mut self) -> IdLeaf<&mut T> { IdLeaf { before: &mut self.before, after: &mut self.after } } /// Converts from `IdLeaf` or `&IdLeaf` to `IdLeaf<&T::Target>`. #[inline] pub fn as_deref(&self) -> IdLeaf<&T::Target> where T: Deref, { IdLeaf { before: &*self.before, after: &*self.after } } /// Converts from `IdLeaf` or `&mut IdLeaf` to `IdLeaf<&mut /// T::Target>`. #[inline] pub fn as_deref_mut(&mut self) -> IdLeaf<&mut T::Target> where T: DerefMut, { IdLeaf { before: &mut *self.before, after: &mut *self.after } } /// Return true if before is the same as after. /// /// This is the same as `self.before() == self.after()`, but is easier to /// use in a chained series of method calls. #[inline] pub fn is_unchanged(&self) -> bool where T: Eq, { self.before == self.after } /// Return true if before is different from after. /// /// This is the same as `self.before != self.after`, but is easier to use in /// a chained series of method calls. #[inline] pub fn is_modified(&self) -> bool where T: Eq, { self.before != self.after } } impl<'daft, T: ?Sized + Diffable> IdLeaf<&'daft T> { /// Perform a diff on [`before`][Self::before] and [`after`][Self::after], /// returning `T::Diff`. /// /// This is useful when `T::Diff` is not a leaf node. #[inline] pub fn diff_pair(self) -> T::Diff<'daft> { self.before.diff(self.after) } } impl IdLeaf<&T> { /// Create a clone of the `IdLeaf` with owned values. #[inline] pub fn cloned(self) -> IdLeaf where T: Clone, { IdLeaf { before: self.before.clone(), after: self.after.clone() } } /// Create a copy of the leaf with owned values. #[inline] pub fn copied(self) -> IdLeaf where T: Copy, { IdLeaf { before: *self.before, after: *self.after } } } iddqd-0.3.17/src/support/fmt_utils.rs000064400000000000000000000005401046102023000156570ustar 00000000000000use core::fmt; /// Debug impl for a static string without quotes. pub(crate) struct StrDisplayAsDebug(pub(crate) &'static str); impl fmt::Debug for StrDisplayAsDebug { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Use the Display formatter to write the string without quotes. fmt::Display::fmt(&self.0, f) } } iddqd-0.3.17/src/support/hash_builder.rs000064400000000000000000000020521046102023000163020ustar 00000000000000/// Default hasher for hash map types. /// /// To disable this hasher, disable the `default-hasher` feature. #[cfg(feature = "default-hasher")] pub type DefaultHashBuilder = foldhash::fast::RandomState; #[cfg(not(feature = "default-hasher"))] mod dummy { use core::hash::{BuildHasher, Hasher}; /// Dummy default hasher for hash map types. /// /// The `default-hasher` feature is currently disabled. #[derive(Clone, Copy, Debug)] pub enum DefaultHashBuilder {} impl BuildHasher for DefaultHashBuilder { type Hasher = Self; fn build_hasher(&self) -> Self::Hasher { unreachable!("this is an empty enum so self cannot exist") } } impl Hasher for DefaultHashBuilder { fn write(&mut self, _bytes: &[u8]) { unreachable!("this is an empty enum so self cannot exist") } fn finish(&self) -> u64 { unreachable!("this is an empty enum so self cannot exist") } } } #[cfg(not(feature = "default-hasher"))] pub use dummy::DefaultHashBuilder; iddqd-0.3.17/src/support/hash_table.rs000064400000000000000000000132731046102023000157520ustar 00000000000000//! A wrapper around a hash table with some random state. use super::{ alloc::{AllocWrapper, Allocator}, map_hash::MapHash, }; use crate::internal::{TableValidationError, ValidateCompact}; use alloc::{collections::BTreeSet, vec::Vec}; use core::{ borrow::Borrow, fmt, hash::{BuildHasher, Hash}, }; use equivalent::Equivalent; use hashbrown::{ HashTable, hash_table::{AbsentEntry, Entry, OccupiedEntry}, }; #[derive(Clone, Default)] pub(crate) struct MapHashTable { pub(super) items: HashTable>, } impl fmt::Debug for MapHashTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MapHashTable").field("items", &self.items).finish() } } impl MapHashTable { pub(crate) const fn new_in(alloc: A) -> Self { Self { items: HashTable::new_in(AllocWrapper(alloc)) } } pub(crate) fn with_capacity_in(capacity: usize, alloc: A) -> Self { Self { items: HashTable::with_capacity_in(capacity, AllocWrapper(alloc)), } } pub(crate) fn len(&self) -> usize { self.items.len() } pub(crate) fn validate( &self, expected_len: usize, compactness: ValidateCompact, ) -> Result<(), TableValidationError> { if self.len() != expected_len { return Err(TableValidationError::new(format!( "expected length {expected_len}, was {}", self.len() ))); } match compactness { ValidateCompact::Compact => { // All items between 0 (inclusive) and self.len() (exclusive) // are expected to be present, and there are no duplicates. let mut values: Vec<_> = self.items.iter().copied().collect(); values.sort_unstable(); for (i, value) in values.iter().enumerate() { if *value != i { return Err(TableValidationError::new(format!( "expected value at index {i} to be {i}, was {value}" ))); } } } ValidateCompact::NonCompact => { // There should be no duplicates. let values: Vec<_> = self.items.iter().copied().collect(); let value_set: BTreeSet<_> = values.iter().copied().collect(); if value_set.len() != values.len() { return Err(TableValidationError::new(format!( "expected no duplicates, but found {} duplicates \ (values: {:?})", values.len() - value_set.len(), values, ))); } } } Ok(()) } pub(crate) fn compute_hash( &self, state: &S, key: K, ) -> MapHash { MapHash { hash: state.hash_one(key) } } // Ensure that K has a consistent hash. pub(crate) fn find_index( &self, state: &S, key: &Q, lookup: F, ) -> Option where F: Fn(usize) -> K, Q: ?Sized + Hash + Equivalent, { let hash = state.hash_one(key); self.items.find(hash, |index| key.equivalent(&lookup(*index))).copied() } pub(crate) fn entry( &mut self, state: &S, key: K, lookup: F, ) -> Entry<'_, usize, AllocWrapper> where F: Fn(usize) -> K, { let hash = state.hash_one(&key); self.items.entry( hash, |index| lookup(*index) == key, |v| state.hash_one(lookup(*v)), ) } pub(crate) fn find_entry( &mut self, state: &S, key: &Q, lookup: F, ) -> Result< OccupiedEntry<'_, usize, AllocWrapper>, AbsentEntry<'_, usize, AllocWrapper>, > where F: Fn(usize) -> K, K: Hash + Eq + Borrow, Q: ?Sized + Hash + Eq, { let hash = state.hash_one(key); self.items.find_entry(hash, |index| lookup(*index).borrow() == key) } pub(crate) fn find_entry_by_hash( &mut self, hash: u64, mut f: F, ) -> Result< OccupiedEntry<'_, usize, AllocWrapper>, AbsentEntry<'_, usize, AllocWrapper>, > where F: FnMut(usize) -> bool, { self.items.find_entry(hash, |index| f(*index)) } pub(crate) fn retain(&mut self, mut f: F) where F: FnMut(usize) -> bool, { self.items.retain(|index| f(*index)); } /// Clears the hash table, removing all items. #[inline] pub(crate) fn clear(&mut self) { self.items.clear(); } /// Reserves capacity for at least `additional` more items. #[inline] pub(crate) fn reserve(&mut self, additional: usize) { self.items.reserve(additional, |_| 0); } /// Shrinks the capacity of the hash table as much as possible. #[inline] pub(crate) fn shrink_to_fit(&mut self) { self.items.shrink_to_fit(|_| 0); } /// Shrinks the capacity of the hash table with a lower limit. #[inline] pub(crate) fn shrink_to(&mut self, min_capacity: usize) { self.items.shrink_to(min_capacity, |_| 0); } /// Tries to reserve capacity for at least `additional` more items. #[inline] pub(crate) fn try_reserve( &mut self, additional: usize, ) -> Result<(), hashbrown::TryReserveError> { self.items.try_reserve(additional, |_| 0) } } iddqd-0.3.17/src/support/item_set.rs000064400000000000000000000170221046102023000154650ustar 00000000000000use super::alloc::AllocWrapper; use crate::{ internal::{ValidateCompact, ValidationError}, support::alloc::{Allocator, Global, global_alloc}, }; use core::{ fmt, ops::{Index, IndexMut}, }; use hashbrown::{HashMap, hash_map}; use rustc_hash::FxBuildHasher; /// A map of items stored by integer index. #[derive(Clone)] pub(crate) struct ItemSet { // rustc-hash's FxHashMap is custom-designed for compact-ish integer keys. items: HashMap>, // The next index to use. This only ever goes up, not down. // // An alternative might be to use a free list of indexes, but that's // unnecessarily complex. next_index: usize, } impl fmt::Debug for ItemSet { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ItemSet") .field("items", &self.items) .field("next_index", &self.next_index) .finish() } } impl ItemSet { #[inline] pub(crate) const fn new() -> Self { Self::new_in(global_alloc()) } } impl ItemSet { #[inline] pub(crate) const fn new_in(alloc: A) -> Self { Self { items: HashMap::with_hasher_in(FxBuildHasher, AllocWrapper(alloc)), next_index: 0, } } pub(crate) fn with_capacity_in(capacity: usize, alloc: A) -> Self { Self { items: HashMap::with_capacity_and_hasher_in( capacity, Default::default(), AllocWrapper(alloc), ), next_index: 0, } } pub(crate) fn allocator(&self) -> &A { &self.items.allocator().0 } /// Validates the item set. pub(crate) fn validate( &self, compactness: ValidateCompact, ) -> Result<(), ValidationError> { // If the map is expected to be compact, then ensure that all keys // between 0 and next_index are present. match compactness { ValidateCompact::Compact => { for i in 0..self.next_index { if !self.items.contains_key(&i) { return Err(ValidationError::General(format!( "ItemSet is not compact: missing index {i}" ))); } } } ValidateCompact::NonCompact => { // No real checks can be done in this case. } } Ok(()) } pub(crate) fn capacity(&self) -> usize { self.items.capacity() } #[inline] pub(crate) fn is_empty(&self) -> bool { self.items.is_empty() } #[inline] pub(crate) fn len(&self) -> usize { self.items.len() } #[inline] pub(crate) fn iter(&self) -> hash_map::Iter<'_, usize, T> { self.items.iter() } #[inline] #[expect(dead_code)] pub(crate) fn iter_mut(&mut self) -> hash_map::IterMut<'_, usize, T> { self.items.iter_mut() } #[inline] pub(crate) fn values(&self) -> hash_map::Values<'_, usize, T> { self.items.values() } #[inline] pub(crate) fn values_mut(&mut self) -> hash_map::ValuesMut<'_, usize, T> { self.items.values_mut() } #[inline] pub(crate) fn into_values( self, ) -> hash_map::IntoValues> { self.items.into_values() } #[inline] pub(crate) fn get(&self, index: usize) -> Option<&T> { self.items.get(&index) } #[inline] pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut T> { self.items.get_mut(&index) } #[inline] pub(crate) fn get_disjoint_mut( &mut self, indexes: [&usize; N], ) -> [Option<&mut T>; N] { self.items.get_many_mut(indexes) } // This is only used by IdOrdMap. #[cfg_attr(not(feature = "std"), expect(dead_code))] #[inline] pub(crate) fn next_index(&self) -> usize { self.next_index } #[inline] pub(crate) fn insert_at_next_index(&mut self, value: T) -> usize { let index = self.next_index; self.items.insert(index, value); self.next_index += 1; index } #[inline] pub(crate) fn remove(&mut self, index: usize) -> Option { let entry = self.items.remove(&index); if entry.is_some() && index == self.next_index - 1 { // If we removed the last entry, decrement next_index. Not strictly // necessary but a nice optimization. // // This does not guarantee compactness, since it's possible for the // following set of operations to occur: // // 0. start at next_index = 0 // 1. insert 0, next_index = 1 // 2. insert 1, next_index = 2 // 3. remove 0, next_index = 2 // 4. remove 1, next_index = 1 (not 0, even though the map is empty) // // Compactness would require a heap acting as a free list. But that // seems generally unnecessary. self.next_index -= 1; } entry } /// Clears the item set, removing all items. #[inline] pub(crate) fn clear(&mut self) { self.items.clear(); self.next_index = 0; } // This method assumes that value has the same ID. It also asserts that // `index` is valid (and panics if it isn't). #[inline] pub(crate) fn replace(&mut self, index: usize, value: T) -> T { self.items .insert(index, value) .unwrap_or_else(|| panic!("EntrySet index not found: {index}")) } /// Reserves capacity for at least `additional` more items. #[inline] pub(crate) fn reserve(&mut self, additional: usize) { self.items.reserve(additional); } /// Shrinks the capacity of the item set as much as possible. #[inline] pub(crate) fn shrink_to_fit(&mut self) { self.items.shrink_to_fit(); } /// Shrinks the capacity of the item set with a lower limit. #[inline] pub(crate) fn shrink_to(&mut self, min_capacity: usize) { self.items.shrink_to(min_capacity); } /// Tries to reserve capacity for at least `additional` more items. #[inline] pub(crate) fn try_reserve( &mut self, additional: usize, ) -> Result<(), hashbrown::TryReserveError> { self.items.try_reserve(additional) } } #[cfg(feature = "serde")] mod serde_impls { use super::ItemSet; use crate::support::alloc::Allocator; use serde_core::{Serialize, Serializer}; impl Serialize for ItemSet { fn serialize(&self, serializer: S) -> Result where S: Serializer, { // Serialize just the items -- don't serialize the map keys. We'll // rebuild the map keys on deserialization. serializer.collect_seq(self.items.values()) } } } impl Index for ItemSet { type Output = T; #[inline] fn index(&self, index: usize) -> &Self::Output { self.items .get(&index) .unwrap_or_else(|| panic!("ItemSet index not found: {index}")) } } impl IndexMut for ItemSet { #[inline] fn index_mut(&mut self, index: usize) -> &mut Self::Output { self.items .get_mut(&index) .unwrap_or_else(|| panic!("ItemSet index not found: {index}")) } } iddqd-0.3.17/src/support/map_hash.rs000064400000000000000000000013061046102023000154320ustar 00000000000000use core::{ fmt, hash::{BuildHasher, Hash}, }; /// Packages up a hash for later validation. #[derive(Clone)] pub(crate) struct MapHash { pub(super) hash: u64, } impl MapHash { pub(crate) fn new(hash: u64) -> Self { Self { hash } } pub(crate) fn hash(&self) -> u64 { self.hash } pub(crate) fn is_same_hash( &self, state: &S, key: K, ) -> bool { self.hash == state.hash_one(key) } } impl fmt::Debug for MapHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MapHash") .field("hash", &self.hash) .finish_non_exhaustive() } } iddqd-0.3.17/src/support/mod.rs000064400000000000000000000005271046102023000144350ustar 00000000000000pub(crate) mod alloc; pub(crate) mod borrow; #[cfg(feature = "std")] pub(crate) mod btree_table; #[cfg(feature = "daft")] pub(crate) mod daft_utils; pub(crate) mod fmt_utils; pub(crate) mod hash_builder; pub(crate) mod hash_table; pub(crate) mod item_set; pub(crate) mod map_hash; #[cfg(feature = "schemars08")] pub(crate) mod schemars_utils; iddqd-0.3.17/src/support/schemars_utils.rs000064400000000000000000000104571046102023000167060ustar 00000000000000//! Utilities for schemars support. use alloc::{ boxed::Box, string::{String, ToString}, }; use schemars::schema::{ ArrayValidation, InstanceType, Metadata, ObjectValidation, Schema, SchemaObject, SingleOrVec, }; /// The crate name for iddqd, used in the x-rust-type extensions. pub(crate) static IDDQD_CRATE_NAME: &str = "iddqd"; /// The crate version for iddqd, used in the x-rust-type extensions. /// /// We use * here because we assume map types are going to stay the same /// across breaking changes. pub(crate) static IDDQD_CRATE_VERSION: &str = "*"; /// Helper function to create array validation for map types. /// All iddqd map types serialize as arrays of their values. pub(crate) fn array_validation( generator: &mut schemars::gen::SchemaGenerator, ) -> Box where T: schemars::JsonSchema, { Box::new(ArrayValidation { items: Some(SingleOrVec::Single(Box::new( generator.subschema_for::(), ))), // Setting unique_items to true here requires a bit of reasoning. For // two items T1 and T2: // // * If T1 == T2 (schema validation fails), then for all keys Key, // T1::Key == T2::Key (would be rejected by the map). The map's // behavior is consistent with the schema. // // * If T1 != T2 (schema validation succeeds), then there are two // cases: // 1. For all keys Key, T1::Key != T2::Key. In this case, the map // accepts the key. The map's behavior is consistent with the // schema. // 2. There is at least one key for which T1::Key == T2::Key. In // this case, the map will reject the key. // // Overall, the map's validation is strictly stronger than the schema. // This is normal in cases where JSON Schema cannot represent a // particular kind of validation. unique_items: Some(true), ..Default::default() }) } /// Helper function to create the `extension` table for a given path and /// type parameter. pub(crate) fn make_extension_table( path: &'static str, generator: &mut schemars::gen::SchemaGenerator, ) -> schemars::Map where T: schemars::JsonSchema, { [( "x-rust-type".to_string(), serde_json::json!({ "crate": IDDQD_CRATE_NAME, "version": IDDQD_CRATE_VERSION, "path": path, "parameters": [generator.subschema_for::()] }), )] .into_iter() .collect() } /// Creates a schema object with common properties for iddqd map types. pub(crate) fn create_map_schema( title: &str, rust_type_path: &'static str, generator: &mut schemars::gen::SchemaGenerator, ) -> schemars::schema::Schema where T: schemars::JsonSchema, { Schema::Object(SchemaObject { instance_type: Some(InstanceType::Array.into()), array: Some(array_validation::(generator)), metadata: Some(Box::new(Metadata { title: Some(title.to_string()), ..Default::default() })), extensions: make_extension_table::(rust_type_path, generator), ..Default::default() }) } /// Helper function to create object validation for map types serialized as objects. pub(crate) fn object_validation( generator: &mut schemars::gen::SchemaGenerator, ) -> Box where V: schemars::JsonSchema, { Box::new(ObjectValidation { additional_properties: Some(Box::new(generator.subschema_for::())), ..Default::default() }) } /// Creates a schema object for iddqd map types serialized as JSON objects. /// This is used by the AsMap wrapper types. pub(crate) fn create_object_schema( title: &str, rust_type_path: &'static str, generator: &mut schemars::gen::SchemaGenerator, ) -> schemars::schema::Schema where V: schemars::JsonSchema, { Schema::Object(SchemaObject { instance_type: Some(InstanceType::Object.into()), object: Some(object_validation::(generator)), metadata: Some(Box::new(Metadata { title: Some(title.to_string()), ..Default::default() })), extensions: make_extension_table::(rust_type_path, generator), ..Default::default() }) } iddqd-0.3.17/src/tri_hash_map/daft_impls.rs000064400000000000000000000450721046102023000167260ustar 00000000000000// `Diffable` implementation. use super::{TriHashItem, TriHashMap}; use crate::{ DefaultHashBuilder, IdHashItem, id_hash_map, support::{ alloc::{Allocator, Global}, daft_utils::IdLeaf, }, }; use core::{ fmt, hash::{BuildHasher, Hash}, }; use daft::Diffable; use equivalent::Equivalent; use ref_cast::RefCast; impl Diffable for TriHashMap { type Diff<'a> = MapLeaf<'a, T, S, A> where T: 'a, S: 'a, A: 'a; fn diff<'daft>(&'daft self, other: &'daft Self) -> Self::Diff<'daft> { MapLeaf { before: self, after: other } } } /// A leaf diff of two [`TriHashMap`]s. /// /// This diff is lazy and has not been evaluated yet. To evaluate the diff, /// call: /// /// * [`Self::by_key1`] to get a diff indexed by `key1`. /// * [`Self::by_key2`] to get a diff indexed by `key2`. /// * [`Self::by_key3`] to get a diff indexed by `key3`. /// * [`Self::by_unique`] to get a diff indexed by `key1`, `key2`, and `key3`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use daft::Diffable; /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Eq, PartialEq)] /// struct Item { /// id: u32, /// name: String, /// email: String, /// value: u32, /// } /// /// impl TriHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// /// fn key3(&self) -> Self::K3<'_> { /// &self.email /// } /// /// tri_upcast!(); /// } /// /// // Create two TriHashMaps with overlapping items. /// let mut map1 = TriHashMap::new(); /// map1.insert_unique(Item { /// id: 1, /// name: "alice".to_string(), /// email: "alice@example.com".to_string(), /// value: 10, /// }); /// map1.insert_unique(Item { /// id: 2, /// name: "bob".to_string(), /// email: "bob@example.com".to_string(), /// value: 20, /// }); /// /// let mut map2 = TriHashMap::new(); /// map2.insert_unique(Item { /// id: 2, /// name: "bob".to_string(), /// email: "bob@example.com".to_string(), /// value: 30, /// }); /// map2.insert_unique(Item { /// id: 3, /// name: "charlie".to_string(), /// email: "charlie@example.com".to_string(), /// value: 40, /// }); /// /// // Compute the diff between the two maps. /// let map_leaf = map1.diff(&map2); /// /// // Get diff by key1 (id). /// let diff_by_id = map_leaf.by_key1(); /// // alice removed, bob modified, charlie added. /// assert!(diff_by_id.removed.contains_key(&1)); /// assert!(diff_by_id.is_modified(&2)); /// assert!(diff_by_id.added.contains_key(&3)); /// /// // Get diff by key2 (name). /// let diff_by_name = map_leaf.by_key2(); /// // alice removed, bob modified, charlie added. /// assert!(diff_by_name.removed.contains_key("alice")); /// assert!(diff_by_name.is_modified("bob")); /// assert!(diff_by_name.added.contains_key("charlie")); /// /// // Get diff by key3 (email). /// let diff_by_email = map_leaf.by_key3(); /// // alice's email removed, bob's email modified, charlie's email added. /// assert!(diff_by_email.removed.contains_key("alice@example.com")); /// assert!(diff_by_email.is_modified("bob@example.com")); /// assert!(diff_by_email.added.contains_key("charlie@example.com")); /// /// // Get diff by unique combination of all three keys. /// let diff_unique = map_leaf.by_unique(); /// // alice removed (by id, name and email) /// assert!(diff_unique.removed.contains_key1(&1)); /// assert!(diff_unique.removed.contains_key2("alice")); /// assert!(diff_unique.removed.contains_key3("alice@example.com")); /// // bob modified (by id, name and email) /// assert!(diff_unique.is_modified1(&2)); /// assert!(diff_unique.is_modified2("bob")); /// assert!(diff_unique.is_modified3("bob@example.com")); /// // charlie added (by id, name and email) /// assert!(diff_unique.added.contains_key1(&3)); /// assert!(diff_unique.added.contains_key2("charlie")); /// assert!(diff_unique.added.contains_key3("charlie@example.com")); /// # } /// ``` pub struct MapLeaf< 'daft, T: TriHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { /// The before map. pub before: &'daft TriHashMap, /// The after map. pub after: &'daft TriHashMap, } impl<'a, 'daft, T: TriHashItem + fmt::Debug, S, A: Allocator> fmt::Debug for MapLeaf<'daft, T, S, A> where T::K1<'a>: fmt::Debug, T::K2<'a>: fmt::Debug, T::K3<'a>: fmt::Debug, T: 'a, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MapLeaf") .field("before", &self.before) .field("after", &self.after) .finish() } } impl<'daft, T: TriHashItem, S, A: Allocator> Clone for MapLeaf<'daft, T, S, A> { fn clone(&self) -> Self { *self } } impl<'daft, T: TriHashItem, S, A: Allocator> Copy for MapLeaf<'daft, T, S, A> {} impl<'daft, T: TriHashItem + PartialEq, S: Clone + BuildHasher, A: Allocator> PartialEq for MapLeaf<'daft, T, S, A> { fn eq(&self, other: &Self) -> bool { self.before == other.before && self.after == other.after } } impl<'daft, T: TriHashItem + Eq, S: Clone + BuildHasher, A: Allocator> Eq for MapLeaf<'daft, T, S, A> { } impl<'daft, T: TriHashItem, S: Clone + BuildHasher, A: Clone + Allocator> MapLeaf<'daft, T, S, A> { /// Returns a diff of two [`TriHashMap`]s, indexed by `key1`. /// /// Note that the return type is a [`Diff`]. pub fn by_key1(self) -> id_hash_map::Diff<'daft, ByK1, S, A> { impl_diff_ref_cast!( self, id_hash_map::Diff::<'daft, ByK1, S, A>, key1, get1, contains_key1, ByK1 ) } /// Returns a diff of two [`TriHashMap`]s, indexed by `key2`. /// /// Note that the return type is a [`Diff`]. pub fn by_key2(self) -> id_hash_map::Diff<'daft, ByK2, S, A> { impl_diff_ref_cast!( self, id_hash_map::Diff::<'daft, ByK2, S, A>, key2, get2, contains_key2, ByK2 ) } /// Returns a diff of two [`TriHashMap`]s, indexed by `key3`. /// /// Note that the return type is a [`Diff`]. pub fn by_key3(self) -> id_hash_map::Diff<'daft, ByK3, S, A> { impl_diff_ref_cast!( self, id_hash_map::Diff::<'daft, ByK3, S, A>, key3, get3, contains_key3, ByK3 ) } /// Returns a diff of two [`TriHashMap`]s, indexed by `key1`, `key2`, and `key3`. /// /// The return type is a [`Diff`]. pub fn by_unique(self) -> Diff<'daft, T, S, A> { let mut diff = Diff::with_hasher_in( self.before.hasher().clone(), self.before.allocator().clone(), ); for item in self.before { if let Some(after_item) = self.after.get_unique(&item.key1(), &item.key2(), &item.key3()) { diff.common.insert_overwrite(IdLeaf::new(item, after_item)); } else { diff.removed.insert_overwrite(item); } } for item in self.after { if !self.before.contains_key_unique( &item.key1(), &item.key2(), &item.key3(), ) { diff.added.insert_overwrite(item); } } diff } } /// A diff of two [`TriHashMap`]s, indexed by `key1`, `key2`, and `key3`. pub struct Diff< 'daft, T: ?Sized + TriHashItem, S = DefaultHashBuilder, A: Allocator = Global, > { /// Entries common to both maps. /// /// Items are stored as [`IdLeaf`]s to references. pub common: TriHashMap, S, A>, /// Added entries. pub added: TriHashMap<&'daft T, S, A>, /// Removed entries. pub removed: TriHashMap<&'daft T, S, A>, } impl<'daft, T: ?Sized + TriHashItem, S: Default, A: Allocator + Default> Default for Diff<'daft, T, S, A> { fn default() -> Self { Self { common: TriHashMap::default(), added: TriHashMap::default(), removed: TriHashMap::default(), } } } impl<'a, 'daft, T, S, A: Allocator> fmt::Debug for Diff<'daft, T, S, A> where T: ?Sized + TriHashItem + fmt::Debug, T::K1<'a>: fmt::Debug, T::K2<'a>: fmt::Debug, T::K3<'a>: fmt::Debug, T: 'a, 'daft: 'a, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Diff") .field("common", &self.common) .field("added", &self.added) .field("removed", &self.removed) .finish() } } #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] impl<'daft, T: ?Sized + TriHashItem> Diff<'daft, T> { /// Creates a new, empty `Diff`. pub fn new() -> Self { Self { common: TriHashMap::new(), added: TriHashMap::new(), removed: TriHashMap::new(), } } } #[cfg(feature = "allocator-api2")] impl<'daft, T: ?Sized + TriHashItem, S: Clone + BuildHasher> Diff<'daft, T, S> { /// Creates a new, empty `Diff` with the given hasher. pub fn with_hasher(hasher: S) -> Self { Self { common: TriHashMap::with_hasher(hasher.clone()), added: TriHashMap::with_hasher(hasher.clone()), removed: TriHashMap::with_hasher(hasher), } } } impl< 'daft, T: ?Sized + TriHashItem, S: Clone + BuildHasher, A: Clone + Allocator, > Diff<'daft, T, S, A> { /// Creates a new, empty `Diff` with the given hasher and allocator. pub fn with_hasher_in(hasher: S, alloc: A) -> Self { Self { common: TriHashMap::with_hasher_in(hasher.clone(), alloc.clone()), added: TriHashMap::with_hasher_in(hasher.clone(), alloc.clone()), removed: TriHashMap::with_hasher_in(hasher, alloc), } } } impl<'daft, T: ?Sized + TriHashItem + Eq, S: Clone + BuildHasher, A: Allocator> Diff<'daft, T, S, A> { /// Returns an iterator over unchanged keys and values. pub fn unchanged(&self) -> impl Iterator + '_ { self.common .iter() .filter_map(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns true if the item corresponding to `key1` is unchanged. pub fn is_unchanged1<'a, Q>(&'a self, key1: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get1(key1).is_some_and(|leaf| leaf.is_unchanged()) } /// Returns true if the item corresponding to `key2` is unchanged. pub fn is_unchanged2<'a, Q>(&'a self, key2: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get2(key2).is_some_and(|leaf| leaf.is_unchanged()) } /// Returns true if the item corresponding to `key3` is unchanged. pub fn is_unchanged3<'a, Q>(&'a self, key3: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get3(key3).is_some_and(|leaf| leaf.is_unchanged()) } /// Returns the value associated with `key1` if it is unchanged, /// otherwise `None`. pub fn get_unchanged1<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> where Q: ?Sized + Hash + Equivalent>, { self.common .get1(key) .and_then(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns the value associated with `key2` if it is unchanged, /// otherwise `None`. pub fn get_unchanged2<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> where Q: ?Sized + Hash + Equivalent>, { self.common .get2(key) .and_then(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns the value associated with `key3` if it is unchanged, /// otherwise `None`. pub fn get_unchanged3<'a, Q>(&'a self, key: &Q) -> Option<&'daft T> where Q: ?Sized + Hash + Equivalent>, { self.common .get3(key) .and_then(|leaf| leaf.is_unchanged().then_some(*leaf.before())) } /// Returns an iterator over modified keys and values. pub fn modified(&self) -> impl Iterator> + '_ { self.common .iter() .filter_map(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns true if the value corresponding to `key1` is modified. pub fn is_modified1<'a, Q>(&'a self, key1: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get1(key1).is_some_and(|leaf| leaf.is_modified()) } /// Returns true if the value corresponding to `key2` is modified. pub fn is_modified2<'a, Q>(&'a self, key2: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get2(key2).is_some_and(|leaf| leaf.is_modified()) } /// Returns true if the value corresponding to `key3` is modified. pub fn is_modified3<'a, Q>(&'a self, key3: &Q) -> bool where Q: ?Sized + Hash + Equivalent>, { self.common.get3(key3).is_some_and(|leaf| leaf.is_modified()) } /// Returns the [`IdLeaf`] associated with `key1` if it is modified, /// otherwise `None`. pub fn get_modified1<'a, Q>(&'a self, key: &Q) -> Option> where Q: ?Sized + Hash + Equivalent>, { self.common .get1(key) .and_then(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns the [`IdLeaf`] associated with `key2` if it is modified, /// otherwise `None`. pub fn get_modified2<'a, Q>(&'a self, key: &Q) -> Option> where Q: ?Sized + Hash + Equivalent>, { self.common .get2(key) .and_then(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns the [`IdLeaf`] associated with `key3` if it is modified, /// otherwise `None`. pub fn get_modified3<'a, Q>(&'a self, key: &Q) -> Option> where Q: ?Sized + Hash + Equivalent>, { self.common .get3(key) .and_then(|leaf| leaf.is_modified().then_some(*leaf)) } /// Returns an iterator over modified keys and values, performing a diff on /// the values. /// /// This is useful when `T::Diff` is a complex type, not just a /// [`daft::Leaf`]. pub fn modified_diff(&self) -> impl Iterator> + '_ where T: Diffable, { self.modified().map(|leaf| leaf.diff_pair()) } } impl TriHashItem for IdLeaf { type K1<'a> = T::K1<'a> where T: 'a; type K2<'a> = T::K2<'a> where T: 'a; type K3<'a> = T::K3<'a> where T: 'a; fn key1(&self) -> Self::K1<'_> { let before_key = self.before().key1(); if before_key != self.after().key1() { panic!("key1 is different between before and after"); } before_key } fn key2(&self) -> Self::K2<'_> { let before_key = self.before().key2(); if before_key != self.after().key2() { panic!("key2 is different between before and after"); } before_key } fn key3(&self) -> Self::K3<'_> { let before_key = self.before().key3(); if before_key != self.after().key3() { panic!("key3 is different between before and after"); } before_key } #[inline] fn upcast_key1<'short, 'long: 'short>( long: Self::K1<'long>, ) -> Self::K1<'short> { T::upcast_key1(long) } #[inline] fn upcast_key2<'short, 'long: 'short>( long: Self::K2<'long>, ) -> Self::K2<'short> { T::upcast_key2(long) } #[inline] fn upcast_key3<'short, 'long: 'short>( long: Self::K3<'long>, ) -> Self::K3<'short> { T::upcast_key3(long) } } /// Maps a [`TriHashItem`] to an [`IdHashItem`], indexed by `key1`. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, RefCast)] #[repr(transparent)] pub struct ByK1(pub T); impl ByK1 { /// Converts a `&T` to a `&ByK1`. #[inline] pub fn ref_cast(item: &T) -> &Self { RefCast::ref_cast(item) } /// Converts a `&mut T` to a `&mut ByK1`. #[inline] pub fn ref_cast_mut(item: &mut T) -> &mut Self { RefCast::ref_cast_mut(item) } } impl IdHashItem for ByK1 { type Key<'a> = T::K1<'a> where T: 'a; #[inline] fn key(&self) -> Self::Key<'_> { self.0.key1() } #[inline] fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> { T::upcast_key1(long) } } /// Maps a [`TriHashItem`] to an [`IdHashItem`], indexed by `key2`. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, RefCast)] #[repr(transparent)] pub struct ByK2(pub T); impl ByK2 { /// Converts a `&T` to a `&ByK2`. #[inline] pub fn ref_cast(item: &T) -> &Self { RefCast::ref_cast(item) } /// Converts a `&mut T` to a `&mut ByK2`. #[inline] pub fn ref_cast_mut(item: &mut T) -> &mut Self { RefCast::ref_cast_mut(item) } } impl IdHashItem for ByK2 { type Key<'a> = T::K2<'a> where T: 'a; #[inline] fn key(&self) -> Self::Key<'_> { self.0.key2() } #[inline] fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> { T::upcast_key2(long) } } /// Maps a [`TriHashItem`] to an [`IdHashItem`], indexed by `key3`. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, RefCast)] #[repr(transparent)] pub struct ByK3(pub T); impl ByK3 { /// Converts a `&T` to a `&ByK3`. #[inline] pub fn ref_cast(item: &T) -> &Self { RefCast::ref_cast(item) } /// Converts a `&mut T` to a `&mut ByK3`. #[inline] pub fn ref_cast_mut(item: &mut T) -> &mut Self { RefCast::ref_cast_mut(item) } } impl IdHashItem for ByK3 { type Key<'a> = T::K3<'a> where T: 'a; #[inline] fn key(&self) -> Self::Key<'_> { self.0.key3() } #[inline] fn upcast_key<'short, 'long: 'short>( long: Self::Key<'long>, ) -> Self::Key<'short> { T::upcast_key3(long) } } iddqd-0.3.17/src/tri_hash_map/imp.rs000064400000000000000000002525361046102023000153760ustar 00000000000000use super::{IntoIter, Iter, IterMut, RefMut, tables::TriHashMapTables}; use crate::{ DefaultHashBuilder, TriHashItem, errors::DuplicateItem, internal::ValidationError, support::{ alloc::{AllocWrapper, Allocator, Global, global_alloc}, borrow::DormantMutRef, fmt_utils::StrDisplayAsDebug, item_set::ItemSet, map_hash::MapHash, }, }; use alloc::{collections::BTreeSet, vec::Vec}; use core::{ fmt, hash::{BuildHasher, Hash}, }; use equivalent::Equivalent; use hashbrown::hash_table::{Entry, VacantEntry}; /// A 1:1:1 (trijective) map for three keys and a value. /// /// The storage mechanism is a fast hash table of integer indexes to items, with /// these indexes stored in three hashmaps. This allows for efficient lookups by /// any of the three keys, while preventing duplicates. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// // Implement TriHashItem to define the three key types. /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// /// tri_upcast!(); /// } /// /// // Create a TriHashMap and insert items. /// let mut people = TriHashMap::new(); /// people /// .insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// // Lookup by any of the three keys. /// let person = people.get1(&1).unwrap(); /// assert_eq!(person.name, "Alice"); /// /// let person = people.get2("alice@example.com").unwrap(); /// assert_eq!(person.id, 1); /// /// let person = people.get3("555-1234").unwrap(); /// assert_eq!(person.email, "alice@example.com"); /// # } /// ``` #[derive(Clone)] pub struct TriHashMap { pub(super) items: ItemSet, // Invariant: the values (usize) in these tables are valid indexes into // `items`, and are a 1:1 mapping. tables: TriHashMapTables, } impl Default for TriHashMap { fn default() -> Self { Self { items: ItemSet::with_capacity_in(0, A::default()), tables: TriHashMapTables::default(), } } } #[cfg(feature = "default-hasher")] impl TriHashMap { /// Creates a new, empty `TriHashMap`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let map: TriHashMap = TriHashMap::new(); /// assert!(map.is_empty()); /// assert_eq!(map.len(), 0); /// # } /// ``` #[inline] pub fn new() -> Self { Self { items: ItemSet::new(), tables: TriHashMapTables::default() } } /// Creates a new `TriHashMap` with the given capacity. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let map: TriHashMap = TriHashMap::with_capacity(10); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_capacity(capacity: usize) -> Self { Self { items: ItemSet::with_capacity_in(capacity, global_alloc()), tables: TriHashMapTables::with_capacity_and_hasher_in( capacity, DefaultHashBuilder::default(), global_alloc(), ), } } } impl TriHashMap { /// Creates a new, empty `TriHashMap` with the given hasher. /// /// # Examples /// /// ``` /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let map: TriHashMap = /// TriHashMap::with_hasher(RandomState::new()); /// assert!(map.is_empty()); /// ``` pub const fn with_hasher(hasher: S) -> Self { Self { items: ItemSet::new(), tables: TriHashMapTables::with_hasher(hasher), } } /// Creates a new `TriHashMap` with the given capacity and hasher. /// /// # Examples /// /// ``` /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let map: TriHashMap = /// TriHashMap::with_capacity_and_hasher(10, RandomState::new()); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// ``` pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self { Self { items: ItemSet::with_capacity_in(capacity, global_alloc()), tables: TriHashMapTables::with_capacity_and_hasher_in( capacity, hasher, global_alloc(), ), } } } #[cfg(feature = "default-hasher")] impl TriHashMap { /// Creates a new empty `TriHashMap` using the given allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] { /// use iddqd::{TriHashMap, TriHashItem, tri_upcast}; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// // Create a new TriHashMap using the allocator. /// let map: TriHashMap = TriHashMap::new_in(&bump); /// assert!(map.is_empty()); /// # } /// ``` pub fn new_in(alloc: A) -> Self { Self { items: ItemSet::with_capacity_in(0, alloc.clone()), tables: TriHashMapTables::with_capacity_and_hasher_in( 0, DefaultHashBuilder::default(), alloc, ), } } /// Creates an empty `TriHashMap` with the specified capacity using the given /// allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] { /// use iddqd::{TriHashMap, TriHashItem, tri_upcast}; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// // Create a new TriHashMap with capacity using the allocator. /// let map: TriHashMap = TriHashMap::with_capacity_in(10, &bump); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_capacity_in(capacity: usize, alloc: A) -> Self { Self { items: ItemSet::with_capacity_in(capacity, alloc.clone()), tables: TriHashMapTables::with_capacity_and_hasher_in( capacity, DefaultHashBuilder::default(), alloc, ), } } } impl TriHashMap { /// Creates a new, empty `TriHashMap` with the given hasher and allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(feature = "allocator-api2")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// use std::collections::hash_map::RandomState; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// let hasher = RandomState::new(); /// // Create a new TriHashMap with hasher using the allocator. /// let map: TriHashMap = /// TriHashMap::with_hasher_in(hasher, &bump); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_hasher_in(hasher: S, alloc: A) -> Self { Self { items: ItemSet::with_capacity_in(0, alloc.clone()), tables: TriHashMapTables::with_capacity_and_hasher_in( 0, hasher.clone(), alloc, ), } } /// Creates a new `TriHashMap` with the given capacity, hasher, and /// allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(feature = "allocator-api2")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// use std::collections::hash_map::RandomState; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// let hasher = RandomState::new(); /// // Create a new TriHashMap with capacity and hasher using the allocator. /// let map: TriHashMap = /// TriHashMap::with_capacity_and_hasher_in(10, hasher, &bump); /// assert!(map.capacity() >= 10); /// assert!(map.is_empty()); /// # } /// ``` pub fn with_capacity_and_hasher_in( capacity: usize, hasher: S, alloc: A, ) -> Self { Self { items: ItemSet::with_capacity_in(capacity, alloc.clone()), tables: TriHashMapTables::with_capacity_and_hasher_in( capacity, hasher, alloc, ), } } } impl TriHashMap { /// Returns the hasher. #[cfg(feature = "daft")] #[inline] pub(crate) fn hasher(&self) -> &S { self.tables.hasher() } /// Returns the allocator. /// /// Requires the `allocator-api2` feature to be enabled. /// /// # Examples /// /// Using the [`bumpalo`](https://docs.rs/bumpalo) allocator: /// /// ``` /// # #[cfg(all(feature = "default-hasher", feature = "allocator-api2"))] { /// use iddqd::{TriHashMap, TriHashItem, tri_upcast}; /// # use iddqd_test_utils::bumpalo; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// // Define a new allocator. /// let bump = bumpalo::Bump::new(); /// // Create a new TriHashMap using the allocator. /// let map: TriHashMap = TriHashMap::new_in(&bump); /// // Access the allocator. /// let allocator = map.allocator(); /// # } /// ``` #[inline] pub fn allocator(&self) -> &A { self.items.allocator() } /// Returns the currently allocated capacity of the map. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let map: TriHashMap = TriHashMap::with_capacity(10); /// assert!(map.capacity() >= 10); /// # } /// ``` pub fn capacity(&self) -> usize { // items and tables.capacity might theoretically diverge: use // items.capacity. self.items.capacity() } /// Returns true if the map is empty. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// assert!(map.is_empty()); /// /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// assert!(!map.is_empty()); /// # } /// ``` #[inline] pub fn is_empty(&self) -> bool { self.items.is_empty() } /// Returns the number of items in the map. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// assert_eq!(map.len(), 0); /// /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// map.insert_unique(Person { /// id: 2, /// email: "bob@example.com".to_string(), /// phone: "555-5678".to_string(), /// name: "Bob".to_string(), /// }) /// .unwrap(); /// assert_eq!(map.len(), 2); /// # } /// ``` #[inline] pub fn len(&self) -> usize { self.items.len() } /// Clears the map, removing all items. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// assert_eq!(map.len(), 1); /// /// map.clear(); /// assert!(map.is_empty()); /// assert_eq!(map.len(), 0); /// # } /// ``` pub fn clear(&mut self) { self.items.clear(); self.tables.k1_to_item.clear(); self.tables.k2_to_item.clear(); self.tables.k3_to_item.clear(); } /// Reserves capacity for at least `additional` more elements to be inserted /// in the `TriHashMap`. The collection may reserve more space to /// speculatively avoid frequent reallocations. After calling `reserve`, /// capacity will be greater than or equal to `self.len() + additional`. /// Does nothing if capacity is already sufficient. /// /// # Panics /// /// Panics if the new capacity overflows [`isize::MAX`] bytes, and /// [`abort`]s the program in case of an allocation error. Use /// [`try_reserve`](Self::try_reserve) instead if you want to handle memory /// allocation failure. /// /// [`isize::MAX`]: https://doc.rust-lang.org/std/primitive.isize.html /// [`abort`]: https://doc.rust-lang.org/alloc/alloc/fn.handle_alloc_error.html /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: u32, /// name: String, /// email: String, /// } /// /// impl TriHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.email /// } /// tri_upcast!(); /// } /// /// let mut map: TriHashMap = TriHashMap::new(); /// map.reserve(100); /// assert!(map.capacity() >= 100); /// # } /// ``` pub fn reserve(&mut self, additional: usize) { self.items.reserve(additional); self.tables.k1_to_item.reserve(additional); self.tables.k2_to_item.reserve(additional); self.tables.k3_to_item.reserve(additional); } /// Tries to reserve capacity for at least `additional` more elements to be /// inserted in the `TriHashMap`. The collection may reserve more space to /// speculatively avoid frequent reallocations. After calling `try_reserve`, /// capacity will be greater than or equal to `self.len() + additional` if /// it returns `Ok(())`. Does nothing if capacity is already sufficient. /// /// # Errors /// /// If the capacity overflows, or the allocator reports a failure, then an /// error is returned. /// /// # Notes /// /// If reservation fails partway through, some internal structures may have /// already increased their capacity. The map remains in a valid state but /// may have uneven capacities across its internal structures. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: u32, /// name: String, /// email: String, /// } /// /// impl TriHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.email /// } /// tri_upcast!(); /// } /// /// let mut map: TriHashMap = TriHashMap::new(); /// map.try_reserve(100).expect("allocation should succeed"); /// assert!(map.capacity() >= 100); /// # } /// ``` pub fn try_reserve( &mut self, additional: usize, ) -> Result<(), crate::errors::TryReserveError> { self.items .try_reserve(additional) .map_err(crate::errors::TryReserveError::from_hashbrown)?; self.tables .k1_to_item .try_reserve(additional) .map_err(crate::errors::TryReserveError::from_hashbrown)?; self.tables .k2_to_item .try_reserve(additional) .map_err(crate::errors::TryReserveError::from_hashbrown)?; self.tables .k3_to_item .try_reserve(additional) .map_err(crate::errors::TryReserveError::from_hashbrown)?; Ok(()) } /// Shrinks the capacity of the map as much as possible. It will drop /// down as much as possible while maintaining the internal rules /// and possibly leaving some space in accordance with the resize policy. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: u32, /// name: String, /// email: String, /// } /// /// impl TriHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.email /// } /// tri_upcast!(); /// } /// /// let mut map: TriHashMap = TriHashMap::with_capacity(100); /// map.insert_unique(Item { /// id: 1, /// name: "foo".to_string(), /// email: "foo@example.com".to_string(), /// }) /// .unwrap(); /// map.insert_unique(Item { /// id: 2, /// name: "bar".to_string(), /// email: "bar@example.com".to_string(), /// }) /// .unwrap(); /// assert!(map.capacity() >= 100); /// map.shrink_to_fit(); /// assert!(map.capacity() >= 2); /// # } /// ``` pub fn shrink_to_fit(&mut self) { self.items.shrink_to_fit(); self.tables.k1_to_item.shrink_to_fit(); self.tables.k2_to_item.shrink_to_fit(); self.tables.k3_to_item.shrink_to_fit(); } /// Shrinks the capacity of the map with a lower limit. It will drop /// down no lower than the supplied limit while maintaining the internal /// rules and possibly leaving some space in accordance with the resize /// policy. /// /// If the current capacity is less than the lower limit, this is a no-op. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: u32, /// name: String, /// email: String, /// } /// /// impl TriHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.email /// } /// tri_upcast!(); /// } /// /// let mut map: TriHashMap = TriHashMap::with_capacity(100); /// map.insert_unique(Item { /// id: 1, /// name: "foo".to_string(), /// email: "foo@example.com".to_string(), /// }) /// .unwrap(); /// map.insert_unique(Item { /// id: 2, /// name: "bar".to_string(), /// email: "bar@example.com".to_string(), /// }) /// .unwrap(); /// assert!(map.capacity() >= 100); /// map.shrink_to(10); /// assert!(map.capacity() >= 10); /// map.shrink_to(0); /// assert!(map.capacity() >= 2); /// # } /// ``` pub fn shrink_to(&mut self, min_capacity: usize) { self.items.shrink_to(min_capacity); self.tables.k1_to_item.shrink_to(min_capacity); self.tables.k2_to_item.shrink_to(min_capacity); self.tables.k3_to_item.shrink_to(min_capacity); } /// Iterates over the items in the map. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not /// guaranteed to be stable. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// map.insert_unique(Person { /// id: 2, /// email: "bob@example.com".to_string(), /// phone: "555-5678".to_string(), /// name: "Bob".to_string(), /// }) /// .unwrap(); /// /// let mut count = 0; /// for person in map.iter() { /// assert!(person.id == 1 || person.id == 2); /// count += 1; /// } /// assert_eq!(count, 2); /// # } /// ``` /// /// [`HashMap`]: std::collections::HashMap #[inline] pub fn iter(&self) -> Iter<'_, T> { Iter::new(&self.items) } /// Iterates over the items in the map, allowing for mutation. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not /// guaranteed to be stable. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// for mut person in map.iter_mut() { /// person.name.push_str(" Updated"); /// } /// /// let person = map.get1(&1).unwrap(); /// assert_eq!(person.name, "Alice Updated"); /// # } /// ``` /// /// [`HashMap`]: std::collections::HashMap #[inline] pub fn iter_mut(&mut self) -> IterMut<'_, T, S, A> { IterMut::new(&self.tables, &mut self.items) } /// Checks general invariants of the map. /// /// The code below always upholds these invariants, but it's useful to have /// an explicit check for tests. #[doc(hidden)] pub fn validate( &self, compactness: crate::internal::ValidateCompact, ) -> Result<(), ValidationError> where T: fmt::Debug, { self.items.validate(compactness)?; self.tables.validate(self.len(), compactness)?; // Check that the indexes are all correct. for (&ix, item) in self.items.iter() { let key1 = item.key1(); let key2 = item.key2(); let key3 = item.key3(); let Some(ix1) = self.find1_index(&key1) else { return Err(ValidationError::general(format!( "item at index {ix} has no key1 index" ))); }; let Some(ix2) = self.find2_index(&key2) else { return Err(ValidationError::general(format!( "item at index {ix} has no key2 index" ))); }; let Some(ix3) = self.find3_index(&key3) else { return Err(ValidationError::general(format!( "item at index {ix} has no key3 index" ))); }; if ix1 != ix || ix2 != ix || ix3 != ix { return Err(ValidationError::general(format!( "item at index {ix} has inconsistent indexes: {ix1}/{ix2}/{ix3}" ))); } } Ok(()) } /// Inserts a value into the map, removing any conflicting items and /// returning a list of those items. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// /// // First insertion - no conflicts /// let overwritten = map.insert_overwrite(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }); /// assert!(overwritten.is_empty()); /// /// // Overwrite with same id - returns the old item /// let overwritten = map.insert_overwrite(Person { /// id: 1, /// email: "alice.new@example.com".to_string(), /// phone: "555-9999".to_string(), /// name: "Alice New".to_string(), /// }); /// assert_eq!(overwritten.len(), 1); /// assert_eq!(overwritten[0].name, "Alice"); /// # } /// ``` #[doc(alias = "insert")] pub fn insert_overwrite(&mut self, value: T) -> Vec { // Trying to write this function for maximal efficiency can get very // tricky, requiring delicate handling of indexes. We follow a very // simple approach instead: // // 1. Remove items corresponding to keys that are already in the map. // 2. Add the item to the map. let mut duplicates = Vec::new(); duplicates.extend(self.remove1(&value.key1())); duplicates.extend(self.remove2(&value.key2())); duplicates.extend(self.remove3(&value.key3())); if self.insert_unique(value).is_err() { // We should never get here, because we just removed all the // duplicates. panic!("insert_unique failed after removing duplicates"); } duplicates } /// Inserts a value into the set, returning an error if any duplicates were /// added. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// /// // Successful insertion /// assert!( /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .is_ok() /// ); /// assert!( /// map.insert_unique(Person { /// id: 2, /// email: "bob@example.com".to_string(), /// phone: "555-5678".to_string(), /// name: "Bob".to_string(), /// }) /// .is_ok() /// ); /// /// // Duplicate key1 /// assert!( /// map.insert_unique(Person { /// id: 1, /// email: "charlie@example.com".to_string(), /// phone: "555-9999".to_string(), /// name: "Charlie".to_string(), /// }) /// .is_err() /// ); /// /// // Duplicate key2 /// assert!( /// map.insert_unique(Person { /// id: 3, /// email: "alice@example.com".to_string(), /// phone: "555-7777".to_string(), /// name: "Alice2".to_string(), /// }) /// .is_err() /// ); /// /// // Duplicate key3 /// assert!( /// map.insert_unique(Person { /// id: 4, /// email: "dave@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Dave".to_string(), /// }) /// .is_err() /// ); /// # } /// ``` pub fn insert_unique( &mut self, value: T, ) -> Result<(), DuplicateItem> { let mut duplicates = BTreeSet::new(); // Check for duplicates *before* inserting the new item, because we // don't want to partially insert the new item and then have to roll // back. let state = &self.tables.state; let (e1, e2, e3) = { let k1 = value.key1(); let k2 = value.key2(); let k3 = value.key3(); let e1 = detect_dup_or_insert( self.tables .k1_to_item .entry(state, k1, |index| self.items[index].key1()), &mut duplicates, ); let e2 = detect_dup_or_insert( self.tables .k2_to_item .entry(state, k2, |index| self.items[index].key2()), &mut duplicates, ); let e3 = detect_dup_or_insert( self.tables .k3_to_item .entry(state, k3, |index| self.items[index].key3()), &mut duplicates, ); (e1, e2, e3) }; if !duplicates.is_empty() { return Err(DuplicateItem::__internal_new( value, duplicates.iter().map(|ix| &self.items[*ix]).collect(), )); } let next_index = self.items.insert_at_next_index(value); // e1, e2 and e3 are all Some because if they were None, duplicates // would be non-empty, and we'd have bailed out earlier. e1.unwrap().insert(next_index); e2.unwrap().insert(next_index); e3.unwrap().insert(next_index); Ok(()) } /// Returns true if the map contains a single item that matches all three /// keys. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }).unwrap(); /// map.insert_unique(Person { /// id: 2, /// email: "bob@example.com".to_string(), /// phone: "555-5678".to_string(), /// name: "Bob".to_string(), /// }).unwrap(); /// /// assert!(map.contains_key_unique(&1, &"alice@example.com", &"555-1234")); /// assert!(map.contains_key_unique(&2, &"bob@example.com", &"555-5678")); /// assert!(!map.contains_key_unique(&1, &"bob@example.com", &"555-1234")); // key1 exists but key2 doesn't match /// assert!(!map.contains_key_unique(&3, &"charlie@example.com", &"555-9999")); // none of the keys exist /// # } /// ``` pub fn contains_key_unique<'a, Q1, Q2, Q3>( &'a self, key1: &Q1, key2: &Q2, key3: &Q3, ) -> bool where Q1: Hash + Equivalent> + ?Sized, Q2: Hash + Equivalent> + ?Sized, Q3: Hash + Equivalent> + ?Sized, { self.get_unique(key1, key2, key3).is_some() } /// Gets a reference to the unique item associated with the given `key1`, /// `key2`, and `key3`, if it exists. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// // All three keys must match /// assert_eq!( /// map.get_unique(&1, &"alice@example.com", &"555-1234").unwrap().name, /// "Alice" /// ); /// /// // If any key doesn't match, returns None /// assert!(map.get_unique(&1, &"wrong@example.com", &"555-1234").is_none()); /// assert!(map.get_unique(&2, &"alice@example.com", &"555-1234").is_none()); /// # } /// ``` pub fn get_unique<'a, Q1, Q2, Q3>( &'a self, key1: &Q1, key2: &Q2, key3: &Q3, ) -> Option<&'a T> where Q1: Hash + Equivalent> + ?Sized, Q2: Hash + Equivalent> + ?Sized, Q3: Hash + Equivalent> + ?Sized, { let index = self.find1_index(key1)?; let item = &self.items[index]; if key2.equivalent(&item.key2()) && key3.equivalent(&item.key3()) { Some(item) } else { None } } /// Gets a mutable reference to the unique item associated with the given /// `key1`, `key2`, and `key3`, if it exists. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// // Modify the item through the mutable reference /// if let Some(mut person) = /// map.get_mut_unique(&1, &"alice@example.com", &"555-1234") /// { /// person.name = "Alice Updated".to_string(); /// } /// /// // Verify the change /// assert_eq!(map.get1(&1).unwrap().name, "Alice Updated"); /// # } /// ``` pub fn get_mut_unique<'a, Q1, Q2, Q3>( &'a mut self, key1: &Q1, key2: &Q2, key3: &Q3, ) -> Option> where Q1: Hash + Equivalent> + ?Sized, Q2: Hash + Equivalent> + ?Sized, Q3: Hash + Equivalent> + ?Sized, { let (dormant_map, index) = { let (map, dormant_map) = DormantMutRef::new(self); let index = map.find1_index(key1)?; let item = &map.items[index]; if !key2.equivalent(&item.key2()) || !key3.equivalent(&item.key3()) { return None; } (dormant_map, index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; let item = &mut awakened_map.items[index]; let state = awakened_map.tables.state.clone(); let hashes = awakened_map.tables.make_hashes(&item); Some(RefMut::new(state, hashes, item)) } /// Removes the item uniquely identified by `key1`, `key2`, and `key3`, if /// it exists. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// // Remove the item using all three keys /// let removed = map.remove_unique(&1, &"alice@example.com", &"555-1234"); /// assert!(removed.is_some()); /// assert_eq!(removed.unwrap().name, "Alice"); /// /// // Map is now empty /// assert!(map.is_empty()); /// /// // Trying to remove again returns None /// assert!(map.remove_unique(&1, &"alice@example.com", &"555-1234").is_none()); /// # } /// ``` pub fn remove_unique<'a, Q1, Q2, Q3>( &'a mut self, key1: &Q1, key2: &Q2, key3: &Q3, ) -> Option where Q1: Hash + Equivalent> + ?Sized, Q2: Hash + Equivalent> + ?Sized, Q3: Hash + Equivalent> + ?Sized, { let (dormant_map, remove_index) = { let (map, dormant_map) = DormantMutRef::new(self); let remove_index = map.find1_index(key1)?; let item = &map.items[remove_index]; if !key2.equivalent(&item.key2()) && !key3.equivalent(&item.key3()) { return None; } (dormant_map, remove_index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; awakened_map.remove_by_index(remove_index) } /// Returns true if the map contains the given `key1`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// assert!(map.contains_key1(&1)); /// assert!(!map.contains_key1(&2)); /// # } /// ``` pub fn contains_key1<'a, Q>(&'a self, key1: &Q) -> bool where Q: Hash + Equivalent> + ?Sized, { self.find1_index(key1).is_some() } /// Gets a reference to the value associated with the given `key1`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// assert_eq!(map.get1(&1).unwrap().name, "Alice"); /// assert!(map.get1(&2).is_none()); /// # } /// ``` pub fn get1<'a, Q>(&'a self, key1: &Q) -> Option<&'a T> where Q: Hash + Equivalent> + ?Sized, { self.find1(key1) } /// Gets a mutable reference to the value associated with the given `key1`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// if let Some(mut person) = map.get1_mut(&1) { /// person.name = "Alice Updated".to_string(); /// } /// /// assert_eq!(map.get1(&1).unwrap().name, "Alice Updated"); /// # } /// ``` pub fn get1_mut<'a, Q>(&'a mut self, key1: &Q) -> Option> where Q: Hash + Equivalent> + ?Sized, { let (dormant_map, index) = { let (map, dormant_map) = DormantMutRef::new(self); let index = map.find1_index(key1)?; (dormant_map, index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; let item = &mut awakened_map.items[index]; let state = awakened_map.tables.state.clone(); let hashes = awakened_map.tables.make_hashes(&item); Some(RefMut::new(state, hashes, item)) } /// Removes an item from the map by its `key1`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// let removed = map.remove1(&1); /// assert!(removed.is_some()); /// assert_eq!(removed.unwrap().name, "Alice"); /// assert!(map.is_empty()); /// # } /// ``` pub fn remove1<'a, Q>(&'a mut self, key1: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { let (dormant_map, remove_index) = { let (map, dormant_map) = DormantMutRef::new(self); let remove_index = map.find1_index(key1)?; (dormant_map, remove_index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; awakened_map.remove_by_index(remove_index) } /// Returns true if the map contains the given `key2`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// assert!(map.contains_key2("alice@example.com")); /// assert!(!map.contains_key2("bob@example.com")); /// # } /// ``` pub fn contains_key2<'a, Q>(&'a self, key2: &Q) -> bool where Q: Hash + Equivalent> + ?Sized, { self.find2_index(key2).is_some() } /// Gets a reference to the value associated with the given `key2`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// assert_eq!(map.get2("alice@example.com").unwrap().name, "Alice"); /// assert!(map.get2("bob@example.com").is_none()); /// # } /// ``` pub fn get2<'a, Q>(&'a self, key2: &Q) -> Option<&'a T> where Q: Hash + Equivalent> + ?Sized, { self.find2(key2) } /// Gets a mutable reference to the value associated with the given `key2`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// if let Some(mut person) = map.get2_mut("alice@example.com") { /// person.name = "Alice Updated".to_string(); /// } /// /// assert_eq!(map.get2("alice@example.com").unwrap().name, "Alice Updated"); /// # } /// ``` pub fn get2_mut<'a, Q>(&'a mut self, key2: &Q) -> Option> where Q: Hash + Equivalent> + ?Sized, { let (dormant_map, index) = { let (map, dormant_map) = DormantMutRef::new(self); let index = map.find2_index(key2)?; (dormant_map, index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; let item = &mut awakened_map.items[index]; let state = awakened_map.tables.state.clone(); let hashes = awakened_map.tables.make_hashes(&item); Some(RefMut::new(state, hashes, item)) } /// Removes an item from the map by its `key2`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// let removed = map.remove2("alice@example.com"); /// assert!(removed.is_some()); /// assert_eq!(removed.unwrap().name, "Alice"); /// assert!(map.is_empty()); /// # } /// ``` pub fn remove2<'a, Q>(&'a mut self, key2: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { let (dormant_map, remove_index) = { let (map, dormant_map) = DormantMutRef::new(self); let remove_index = map.find2_index(key2)?; (dormant_map, remove_index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; awakened_map.remove_by_index(remove_index) } /// Returns true if the map contains the given `key3`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// assert!(map.contains_key3("555-1234")); /// assert!(!map.contains_key3("555-5678")); /// # } /// ``` pub fn contains_key3<'a, Q>(&'a self, key3: &Q) -> bool where Q: Hash + Equivalent> + ?Sized, { self.find3_index(key3).is_some() } /// Gets a reference to the value associated with the given `key3`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// assert_eq!(map.get3("555-1234").unwrap().name, "Alice"); /// assert!(map.get3("555-5678").is_none()); /// # } /// ``` pub fn get3<'a, Q>(&'a self, key3: &Q) -> Option<&'a T> where Q: Hash + Equivalent> + ?Sized, { self.find3(key3) } /// Gets a mutable reference to the value associated with the given `key3`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// if let Some(mut person) = map.get3_mut("555-1234") { /// person.name = "Alice Updated".to_string(); /// } /// /// assert_eq!(map.get3("555-1234").unwrap().name, "Alice Updated"); /// # } /// ``` pub fn get3_mut<'a, Q>(&'a mut self, key3: &Q) -> Option> where Q: Hash + Equivalent> + ?Sized, { let (dormant_map, index) = { let (map, dormant_map) = DormantMutRef::new(self); let index = map.find3_index(key3)?; (dormant_map, index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; let item = &mut awakened_map.items[index]; let state = awakened_map.tables.state.clone(); let hashes = awakened_map.tables.make_hashes(&item); Some(RefMut::new(state, hashes, item)) } /// Removes an item from the map by its `key3`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// phone: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.phone /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// email: "alice@example.com".to_string(), /// phone: "555-1234".to_string(), /// name: "Alice".to_string(), /// }) /// .unwrap(); /// /// let removed = map.remove3("555-1234"); /// assert!(removed.is_some()); /// assert_eq!(removed.unwrap().name, "Alice"); /// assert!(map.is_empty()); /// # } /// ``` pub fn remove3<'a, Q>(&'a mut self, key3: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { let (dormant_map, remove_index) = { let (map, dormant_map) = DormantMutRef::new(self); let remove_index = map.find3_index(key3)?; (dormant_map, remove_index) }; // SAFETY: `map` is not used after this point. let awakened_map = unsafe { dormant_map.awaken() }; awakened_map.remove_by_index(remove_index) } /// Retains only the elements specified by the predicate. /// /// In other words, remove all items `T` for which `f(RefMut)` returns /// false. The elements are visited in an arbitrary order. /// /// The `RefMut` wrapper allows mutable access to the item while /// enforcing that the three keys (`K1`, `K2`, `K3`) remain unchanged. If /// a key is modified during iteration, the method will panic. /// /// # Performance considerations /// /// This method may leave the internal storage fragmented. If you need /// compact storage afterward, call `make_compact()`. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Item { /// id: u32, /// name: String, /// code: String, /// value: u32, /// } /// /// impl TriHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.code /// } /// /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::new(); /// map.insert_unique(Item { /// id: 1, /// name: "foo".to_string(), /// code: "x".to_string(), /// value: 42, /// }) /// .unwrap(); /// map.insert_unique(Item { /// id: 2, /// name: "bar".to_string(), /// code: "y".to_string(), /// value: 20, /// }) /// .unwrap(); /// map.insert_unique(Item { /// id: 3, /// name: "baz".to_string(), /// code: "z".to_string(), /// value: 99, /// }) /// .unwrap(); /// /// // Retain only items where value is greater than 30 /// map.retain(|item| item.value > 30); /// /// assert_eq!(map.len(), 2); /// assert_eq!(map.get1(&1).unwrap().value, 42); /// assert_eq!(map.get1(&3).unwrap().value, 99); /// assert!(map.get1(&2).is_none()); /// # } /// ``` pub fn retain<'a, F>(&'a mut self, mut f: F) where F: FnMut(RefMut<'a, T, S>) -> bool, { let hash_state = self.tables.state.clone(); let (_, mut dormant_items) = DormantMutRef::new(&mut self.items); self.tables.k1_to_item.retain(|index| { let (item, dormant_items) = { // SAFETY: All uses of `items` ended in the previous iteration. let items = unsafe { dormant_items.reborrow() }; let (items, dormant_items) = DormantMutRef::new(items); let item: &'a mut T = items .get_mut(index) .expect("all indexes are present in self.items"); (item, dormant_items) }; let (hashes, dormant_item) = { let (item, dormant_item): (&'a mut T, _) = DormantMutRef::new(item); // Use T::k1(item) rather than item.key() to force the key // trait function to be called for T rather than &mut T. let key1 = T::key1(item); let key2 = T::key2(item); let key3 = T::key3(item); let hash1 = hash_state.hash_one(key1); let hash2 = hash_state.hash_one(key2); let hash3 = hash_state.hash_one(key3); ( [ MapHash::new(hash1), MapHash::new(hash2), MapHash::new(hash3), ], dormant_item, ) }; let hash2 = hashes[1].hash(); let hash3 = hashes[2].hash(); let retain = { // SAFETY: The original item is no longer used after the second // block above. dormant_items, from which item is derived, is // currently dormant. let item = unsafe { dormant_item.awaken() }; let ref_mut = RefMut::new(hash_state.clone(), hashes, item); f(ref_mut) }; if retain { true } else { // SAFETY: The original items is no longer used after the first // block above, and item + dormant_item have been dropped after // being used above. let items = unsafe { dormant_items.awaken() }; items.remove(index); let k2_entry = self .tables .k2_to_item .find_entry_by_hash(hash2, |map2_index| { map2_index == index }); let k3_entry = self .tables .k3_to_item .find_entry_by_hash(hash3, |map3_index| { map3_index == index }); let mut is_inconsistent = false; if let Ok(k2_entry) = k2_entry { k2_entry.remove(); } else { is_inconsistent = true; } if let Ok(k3_entry) = k3_entry { k3_entry.remove(); } else { is_inconsistent = true; } if is_inconsistent { // This happening means there's an inconsistency among // the maps. panic!( "inconsistency among k1_to_item, k2_to_item, k3_to_item" ); } false } }); } fn find1<'a, Q>(&'a self, k: &Q) -> Option<&'a T> where Q: Hash + Equivalent> + ?Sized, { self.find1_index(k).map(|ix| &self.items[ix]) } fn find1_index<'a, Q>(&'a self, k: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { self.tables .k1_to_item .find_index(&self.tables.state, k, |index| self.items[index].key1()) } fn find2<'a, Q>(&'a self, k: &Q) -> Option<&'a T> where Q: Hash + Equivalent> + ?Sized, { self.find2_index(k).map(|ix| &self.items[ix]) } fn find2_index<'a, Q>(&'a self, k: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { self.tables .k2_to_item .find_index(&self.tables.state, k, |index| self.items[index].key2()) } fn find3<'a, Q>(&'a self, k: &Q) -> Option<&'a T> where Q: Hash + Equivalent> + ?Sized, { self.find3_index(k).map(|ix| &self.items[ix]) } fn find3_index<'a, Q>(&'a self, k: &Q) -> Option where Q: Hash + Equivalent> + ?Sized, { self.tables .k3_to_item .find_index(&self.tables.state, k, |index| self.items[index].key3()) } pub(super) fn remove_by_index(&mut self, remove_index: usize) -> Option { let value = self.items.remove(remove_index)?; // Remove the value from the tables. let state = &self.tables.state; let Ok(item1) = self.tables.k1_to_item.find_entry(state, &value.key1(), |index| { if index == remove_index { value.key1() } else { self.items[index].key1() } }) else { // The item was not found. panic!("remove_index {remove_index} not found in k1_to_item"); }; let Ok(item2) = self.tables.k2_to_item.find_entry(state, &value.key2(), |index| { if index == remove_index { value.key2() } else { self.items[index].key2() } }) else { // The item was not found. panic!("remove_index {remove_index} not found in k2_to_item") }; let Ok(item3) = self.tables.k3_to_item.find_entry(state, &value.key3(), |index| { if index == remove_index { value.key3() } else { self.items[index].key3() } }) else { // The item was not found. panic!("remove_index {remove_index} not found in k3_to_item") }; item1.remove(); item2.remove(); item3.remove(); Some(value) } } impl<'a, T, S, A: Allocator> fmt::Debug for TriHashMap where T: TriHashItem + fmt::Debug, T::K1<'a>: fmt::Debug, T::K2<'a>: fmt::Debug, T::K3<'a>: fmt::Debug, T: 'a, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut map = f.debug_map(); for item in self.items.values() { let key: KeyMap<'_, T> = KeyMap { key1: item.key1(), key2: item.key2(), key3: item.key3(), }; // SAFETY: // // * Lifetime extension: for a type T and two lifetime params 'a and // 'b, T<'a> and T<'b> aren't guaranteed to have the same layout, // but (a) that is true today and (b) it would be shocking and // break half the Rust ecosystem if that were to change in the // future. // * We only use key within the scope of this block before immediately // dropping it. In particular, map.entry calls key.fmt() without // holding a reference to it. let key: KeyMap<'a, T> = unsafe { core::mem::transmute::, KeyMap<'a, T>>(key) }; map.entry(&key, item); } map.finish() } } struct KeyMap<'a, T: TriHashItem + 'a> { key1: T::K1<'a>, key2: T::K2<'a>, key3: T::K3<'a>, } impl<'a, T: TriHashItem> fmt::Debug for KeyMap<'a, T> where T::K1<'a>: fmt::Debug, T::K2<'a>: fmt::Debug, T::K3<'a>: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // We don't want to show key1 and key2 as a tuple since it's // misleading (suggests maps of tuples). The best we can do // instead is to show "{k1: abc, k2: xyz, k3: def}" f.debug_map() .entry(&StrDisplayAsDebug("k1"), &self.key1) .entry(&StrDisplayAsDebug("k2"), &self.key2) .entry(&StrDisplayAsDebug("k3"), &self.key3) .finish() } } impl PartialEq for TriHashMap { fn eq(&self, other: &Self) -> bool { // Implementing PartialEq for TriHashMap is tricky because TriHashMap is // not semantically like an IndexMap: two maps are equivalent even if // their items are in a different order. In other words, any permutation // of items is equivalent. // // We also can't sort the items because they're not necessarily Ord. // // So we write a custom equality check that checks that each key in one // map points to the same item as in the other map. if self.items.len() != other.items.len() { return false; } // Walk over all the items in the first map and check that they point to // the same item in the second map. for item in self.items.values() { let k1 = item.key1(); let k2 = item.key2(); let k3 = item.key3(); // Check that the indexes are the same in the other map. let Some(other_ix1) = other.find1_index(&k1) else { return false; }; let Some(other_ix2) = other.find2_index(&k2) else { return false; }; let Some(other_ix3) = other.find3_index(&k3) else { return false; }; if other_ix1 != other_ix2 || other_ix1 != other_ix3 { // All the keys were present but they didn't point to the same // item. return false; } // Check that the other map's item is the same as this map's // item. (This is what we use the `PartialEq` bound on T for.) // // Because we've checked that other_ix1, other_ix2 and other_ix3 are // Some, we know that it is valid and points to the expected item. let other_item = &other.items[other_ix1]; if item != other_item { return false; } } true } } // The Eq bound on T ensures that the TriHashMap forms an equivalence class. impl Eq for TriHashMap { } /// The `Extend` implementation overwrites duplicates. In the future, there will /// also be an `extend_unique` method that will return an error. impl Extend for TriHashMap { fn extend>(&mut self, iter: I) { // Keys may already be present in the map, or multiple times in the // iterator. Reserve the entire hint lower bound if the map is empty. // Otherwise reserve half the hint (rounded up), so the map will only // resize twice in the worst case. let iter = iter.into_iter(); let reserve = if self.is_empty() { iter.size_hint().0 } else { iter.size_hint().0.div_ceil(2) }; self.reserve(reserve); for item in iter { self.insert_overwrite(item); } } } fn detect_dup_or_insert<'a, A: Allocator>( item: Entry<'a, usize, AllocWrapper>, duplicates: &mut BTreeSet, ) -> Option>> { match item { Entry::Vacant(slot) => Some(slot), Entry::Occupied(slot) => { duplicates.insert(*slot.get()); None } } } impl<'a, T: TriHashItem, S: Clone + BuildHasher, A: Allocator> IntoIterator for &'a TriHashMap { type Item = &'a T; type IntoIter = Iter<'a, T>; #[inline] fn into_iter(self) -> Self::IntoIter { self.iter() } } impl<'a, T: TriHashItem, S: Clone + BuildHasher, A: Allocator> IntoIterator for &'a mut TriHashMap { type Item = RefMut<'a, T, S>; type IntoIter = IterMut<'a, T, S, A>; #[inline] fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl IntoIterator for TriHashMap { type Item = T; type IntoIter = IntoIter; #[inline] fn into_iter(self) -> Self::IntoIter { IntoIter::new(self.items) } } /// The `FromIterator` implementation for `TriHashMap` overwrites duplicate /// items. impl FromIterator for TriHashMap { fn from_iter>(iter: I) -> Self { let mut map = TriHashMap::default(); for item in iter { map.insert_overwrite(item); } map } } iddqd-0.3.17/src/tri_hash_map/iter.rs000064400000000000000000000077661046102023000155570ustar 00000000000000use super::{RefMut, tables::TriHashMapTables}; use crate::{ DefaultHashBuilder, TriHashItem, support::{ alloc::{AllocWrapper, Allocator, Global}, item_set::ItemSet, }, }; use core::{hash::BuildHasher, iter::FusedIterator}; use hashbrown::hash_map; /// An iterator over the elements of a [`TriHashMap`] by shared reference. /// Created by [`TriHashMap::iter`]. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed /// to be stable. /// /// [`TriHashMap`]: crate::TriHashMap /// [`TriHashMap::iter`]: crate::TriHashMap::iter /// [`HashMap`]: std::collections::HashMap #[derive(Clone, Debug, Default)] pub struct Iter<'a, T: TriHashItem> { inner: hash_map::Values<'a, usize, T>, } impl<'a, T: TriHashItem> Iter<'a, T> { pub(crate) fn new(items: &'a ItemSet) -> Self { Self { inner: items.values() } } } impl<'a, T: TriHashItem> Iterator for Iter<'a, T> { type Item = &'a T; #[inline] fn next(&mut self) -> Option { self.inner.next() } } impl ExactSizeIterator for Iter<'_, T> { #[inline] fn len(&self) -> usize { self.inner.len() } } // hash_map::Iter is a FusedIterator, so Iter is as well. impl FusedIterator for Iter<'_, T> {} /// An iterator over the elements of a [`TriHashMap`] by mutable reference. /// Created by [`TriHashMap::iter_mut`]. /// /// This iterator returns [`RefMut`] instances. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed /// to be stable. /// /// [`TriHashMap`]: crate::TriHashMap /// [`TriHashMap::iter_mut`]: crate::TriHashMap::iter_mut /// [`HashMap`]: std::collections::HashMap #[derive(Debug)] pub struct IterMut< 'a, T: TriHashItem, S: Clone + BuildHasher = DefaultHashBuilder, A: Allocator = Global, > { tables: &'a TriHashMapTables, inner: hash_map::ValuesMut<'a, usize, T>, } impl<'a, T: TriHashItem, S: Clone + BuildHasher, A: Allocator> IterMut<'a, T, S, A> { pub(super) fn new( tables: &'a TriHashMapTables, items: &'a mut ItemSet, ) -> Self { Self { tables, inner: items.values_mut() } } } impl<'a, T: TriHashItem, S: Clone + BuildHasher, A: Allocator> Iterator for IterMut<'a, T, S, A> { type Item = RefMut<'a, T, S>; #[inline] fn next(&mut self) -> Option { let next = self.inner.next()?; let hashes = self.tables.make_hashes(next); Some(RefMut::new(self.tables.state.clone(), hashes, next)) } } impl ExactSizeIterator for IterMut<'_, T, S, A> { #[inline] fn len(&self) -> usize { self.inner.len() } } // hash_map::IterMut is a FusedIterator, so IterMut is as well. impl FusedIterator for IterMut<'_, T, S, A> { } /// An iterator over the elements of a [`TriHashMap`] by ownership. Created by /// [`TriHashMap::into_iter`]. /// /// Similar to [`HashMap`], the iteration order is arbitrary and not guaranteed /// to be stable. /// /// [`TriHashMap`]: crate::TriHashMap /// [`TriHashMap::into_iter`]: crate::TriHashMap::into_iter /// [`HashMap`]: std::collections::HashMap #[derive(Debug)] pub struct IntoIter { inner: hash_map::IntoValues>, } impl IntoIter { pub(crate) fn new(items: ItemSet) -> Self { Self { inner: items.into_values() } } } impl Iterator for IntoIter { type Item = T; #[inline] fn next(&mut self) -> Option { self.inner.next() } } impl ExactSizeIterator for IntoIter { #[inline] fn len(&self) -> usize { self.inner.len() } } // hash_map::IterMut is a FusedIterator, so IterMut is as well. impl FusedIterator for IntoIter {} iddqd-0.3.17/src/tri_hash_map/mod.rs000064400000000000000000000016231046102023000153550ustar 00000000000000//! A hash map where values are uniquely indexed by three keys. //! //! For more information, see [`TriHashMap`]. #[cfg(feature = "daft")] mod daft_impls; pub(crate) mod imp; mod iter; #[cfg(feature = "proptest")] mod proptest_impls; mod ref_mut; #[cfg(feature = "schemars08")] mod schemars_impls; #[cfg(feature = "serde")] mod serde_impls; mod tables; pub(crate) mod trait_defs; #[cfg(feature = "daft")] pub use daft_impls::{ByK1, ByK2, ByK3, Diff, MapLeaf}; pub use imp::TriHashMap; pub use iter::{IntoIter, Iter, IterMut}; #[cfg(all(feature = "proptest", feature = "default-hasher"))] pub use proptest_impls::prop_strategy; #[cfg(feature = "proptest")] pub use proptest_impls::{ TriHashMapStrategy, TriHashMapValueTree, prop_strategy_with_hasher, prop_strategy_with_hasher_in, }; pub use ref_mut::RefMut; #[cfg(feature = "serde")] pub use serde_impls::TriHashMapAsMap; pub use trait_defs::TriHashItem; iddqd-0.3.17/src/tri_hash_map/proptest_impls.rs000064400000000000000000000215751046102023000176720ustar 00000000000000//! Proptest strategies for generating [`TriHashMap`]s with random inputs. use crate::{ TriHashItem, support::{ alloc::{Allocator, Global}, hash_builder::DefaultHashBuilder, }, tri_hash_map::TriHashMap, }; use core::{fmt, hash::BuildHasher}; use proptest::{ arbitrary::{Arbitrary, StrategyFor, any_with}, collection::{SizeRange, VecStrategy, VecValueTree}, strategy::{NewTree, Strategy, ValueTree}, test_runner::TestRunner, }; /// Strategy to create [`TriHashMap`]s with a length in a certain range. /// /// Created by the [`prop_strategy()`] function. #[must_use = "strategies do nothing unless used"] #[derive(Clone)] pub struct TriHashMapStrategy where T: Strategy, { inner: VecStrategy, hasher: S, allocator: A, } impl fmt::Debug for TriHashMapStrategy where T: Strategy, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TriHashMapStrategy") .field("inner", &self.inner) .finish_non_exhaustive() } } /// Creates a strategy to generate [`TriHashMap`]s containing items drawn from /// `element` and with a size within the given range. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_hash_map, tri_upcast}; /// use proptest::{ /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, /// }; /// /// #[derive(Debug, Clone, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.name /// } /// tri_upcast!(); /// } /// /// // Create a strategy using a tuple and mapping it to Person. /// let strategy = tri_hash_map::prop_strategy( /// (any::(), any::(), any::()) /// .prop_map(|(id, email, name)| Person { id, email, name }), /// 0..=5, /// ); /// /// // The strategy can be used in proptest contexts. /// let mut runner = TestRunner::default(); /// let _tree = strategy.new_tree(&mut runner).unwrap(); /// # } /// ``` #[cfg(feature = "default-hasher")] pub fn prop_strategy( element: T, size: impl Into, ) -> TriHashMapStrategy { TriHashMapStrategy { inner: proptest::collection::vec(element, size), hasher: DefaultHashBuilder::default(), allocator: crate::support::alloc::global_alloc(), } } /// Creates a strategy to generate [`TriHashMap`]s with a custom hasher. /// /// # Examples /// /// ``` /// use iddqd::{TriHashItem, TriHashMap, tri_hash_map, tri_upcast}; /// use proptest::{ /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, /// }; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, Clone, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.name /// } /// tri_upcast!(); /// } /// /// // Create a strategy with a custom hasher. /// let hasher = RandomState::new(); /// let strategy = tri_hash_map::prop_strategy_with_hasher( /// (any::(), any::(), any::()) /// .prop_map(|(id, email, name)| Person { id, email, name }), /// 0..=3, /// hasher, /// ); /// /// // The strategy can be used in proptest contexts. /// let mut runner = TestRunner::default(); /// let _tree = strategy.new_tree(&mut runner).unwrap(); /// ``` pub fn prop_strategy_with_hasher( element: T, size: impl Into, hasher: S, ) -> TriHashMapStrategy { let size = size.into(); TriHashMapStrategy { inner: proptest::collection::vec(element, size), hasher, allocator: crate::support::alloc::global_alloc(), } } /// Creates a strategy to generate [`TriHashMap`]s with a custom hasher and /// allocator. /// /// # Examples /// /// ``` /// # #[cfg(feature = "allocator-api2")] { /// use allocator_api2::alloc::Global; /// use iddqd::{TriHashItem, TriHashMap, tri_hash_map, tri_upcast}; /// use proptest::{ /// arbitrary::any, strategy::Strategy, test_runner::TestRunner, /// }; /// use std::collections::hash_map::RandomState; /// /// #[derive(Debug, Clone, PartialEq, Eq)] /// struct Person { /// id: u32, /// email: String, /// name: String, /// } /// /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.email /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.name /// } /// tri_upcast!(); /// } /// /// // Create a strategy with custom hasher and allocator. /// let hasher = RandomState::new(); /// let allocator = Global; /// let strategy = tri_hash_map::prop_strategy_with_hasher_in( /// (any::(), any::(), any::()) /// .prop_map(|(id, email, name)| Person { id, email, name }), /// 1..=4, /// hasher, /// allocator, /// ); /// /// // The strategy can be used in proptest contexts. /// let mut runner = TestRunner::default(); /// let _tree = strategy.new_tree(&mut runner).unwrap(); /// # } /// ``` pub fn prop_strategy_with_hasher_in( element: T, size: impl Into, hasher: S, allocator: A, ) -> TriHashMapStrategy { let size = size.into(); TriHashMapStrategy { inner: proptest::collection::vec(element, size), hasher, allocator, } } impl<'a, T, S, A> Strategy for TriHashMapStrategy where T: Strategy, T::Value: 'a + TriHashItem, ::K1<'a>: fmt::Debug, ::K2<'a>: fmt::Debug, ::K3<'a>: fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Tree = TriHashMapValueTree; type Value = TriHashMap; fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let inner = self.inner.new_tree(runner)?; Ok(TriHashMapValueTree { inner, hasher: self.hasher.clone(), allocator: self.allocator.clone(), }) } } /// `ValueTree` corresponding to [`TriHashMapStrategy`]. #[derive(Clone)] pub struct TriHashMapValueTree where T: ValueTree, { inner: VecValueTree, hasher: S, allocator: A, } impl fmt::Debug for TriHashMapValueTree where T: ValueTree + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TriHashMapValueTree") .field("inner", &self.inner) .finish_non_exhaustive() } } impl<'a, T, S, A> ValueTree for TriHashMapValueTree where T: ValueTree, T::Value: 'a + TriHashItem, ::K1<'a>: fmt::Debug, ::K2<'a>: fmt::Debug, ::K3<'a>: fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Value = TriHashMap; fn current(&self) -> Self::Value { let items = self.inner.current(); let mut map = TriHashMap::with_hasher_in( self.hasher.clone(), self.allocator.clone(), ); for item in items { // Use insert_overwrite to handle duplicate keys. map.insert_overwrite(item); } map } fn simplify(&mut self) -> bool { self.inner.simplify() } fn complicate(&mut self) -> bool { self.inner.complicate() } } impl<'a, T, S, A> Arbitrary for TriHashMap where T: 'a + TriHashItem + Arbitrary, ::K1<'a>: fmt::Debug, ::K2<'a>: fmt::Debug, ::K3<'a>: fmt::Debug, S: Clone + BuildHasher + Default, A: Clone + Allocator + Default, { type Parameters = (SizeRange, T::Parameters); type Strategy = TriHashMapStrategy, S, A>; fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { let (size, element_args) = args; prop_strategy_with_hasher_in( any_with::(element_args), size, S::default(), A::default(), ) } } iddqd-0.3.17/src/tri_hash_map/ref_mut.rs000064400000000000000000000111231046102023000162330ustar 00000000000000use crate::{DefaultHashBuilder, TriHashItem, support::map_hash::MapHash}; use core::{ fmt, hash::BuildHasher, ops::{Deref, DerefMut}, }; /// A mutable reference to a [`TriHashMap`] item. /// /// This is a wrapper around a `&mut T` that panics when dropped, if the /// borrowed value's keys have changed since the wrapper was created. /// /// # Change detection /// /// It is illegal to change the keys of a borrowed `&mut T`. `RefMut` attempts /// to enforce this invariant. /// /// `RefMut` stores the `Hash` output of keys at creation time, and recomputes /// these hashes when it is dropped or when [`Self::into_ref`] is called. If a /// key changes, there's a small but non-negligible chance that its hash value /// stays the same[^collision-chance]. In that case, as long as the new key is /// not the same as another existing one, internal invariants are not violated /// and the [`TriHashMap`] will continue to work correctly. (But don't rely on /// this!) /// /// It is also possible to deliberately write pathological `Hash` /// implementations that collide more often. (Don't do this either.) /// /// Also, `RefMut`'s hash detection will not function if [`mem::forget`] is /// called on it. If a key is changed and `mem::forget` is then called on the /// `RefMut`, the `TriHashMap` will stop functioning correctly. This will not /// introduce memory safety issues, however. /// /// The issues here are similar to using interior mutability (e.g. `RefCell` or /// `Mutex`) to mutate keys in a regular `HashMap`. /// /// [`mem::forget`]: std::mem::forget /// /// [^collision-chance]: The output of `Hash` is a [`u64`], so the probability /// of an individual hash colliding by chance is 1/2⁶⁴. Due to the [birthday /// problem], the probability of a collision by chance reaches 10⁻⁶ within /// around 6 × 10⁶ elements. /// /// [`TriHashMap`]: crate::TriHashMap /// [birthday problem]: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table pub struct RefMut< 'a, T: TriHashItem, S: Clone + BuildHasher = DefaultHashBuilder, > { inner: Option>, } impl<'a, T: TriHashItem, S: Clone + BuildHasher> RefMut<'a, T, S> { pub(super) fn new( state: S, hashes: [MapHash; 3], borrowed: &'a mut T, ) -> Self { Self { inner: Some(RefMutInner { state, hashes, borrowed }) } } /// Borrows self into a shorter-lived `RefMut`. /// /// This `RefMut` will also check hash equality on drop. pub fn reborrow(&mut self) -> RefMut<'_, T, S> { let inner = self.inner.as_mut().unwrap(); let borrowed = &mut *inner.borrowed; RefMut::new(inner.state.clone(), inner.hashes.clone(), borrowed) } /// Converts this `RefMut` into a `&'a T`. pub fn into_ref(mut self) -> &'a T { let inner = self.inner.take().unwrap(); inner.into_ref() } } impl Drop for RefMut<'_, T, S> { fn drop(&mut self) { if let Some(inner) = self.inner.take() { inner.into_ref(); } } } impl Deref for RefMut<'_, T, S> { type Target = T; fn deref(&self) -> &Self::Target { self.inner.as_ref().unwrap().borrowed } } impl DerefMut for RefMut<'_, T, S> { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.as_mut().unwrap().borrowed } } impl fmt::Debug for RefMut<'_, T, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.inner { Some(ref inner) => inner.fmt(f), None => { f.debug_struct("RefMut").field("borrowed", &"missing").finish() } } } } struct RefMutInner<'a, T: TriHashItem, S> { state: S, hashes: [MapHash; 3], borrowed: &'a mut T, } impl<'a, T: TriHashItem, S: BuildHasher> RefMutInner<'a, T, S> { fn into_ref(self) -> &'a T { if !self.hashes[0].is_same_hash(&self.state, self.borrowed.key1()) { panic!("key1 changed during RefMut borrow"); } if !self.hashes[1].is_same_hash(&self.state, self.borrowed.key2()) { panic!("key2 changed during RefMut borrow"); } if !self.hashes[2].is_same_hash(&self.state, self.borrowed.key3()) { panic!("key3 changed during RefMut borrow"); } self.borrowed } } impl fmt::Debug for RefMutInner<'_, T, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.borrowed.fmt(f) } } iddqd-0.3.17/src/tri_hash_map/schemars_impls.rs000064400000000000000000000023721046102023000176110ustar 00000000000000//! Schemars implementations for TriHashMap. use crate::{ support::{ alloc::Allocator, schemars_utils::{create_map_schema, create_object_schema}, }, tri_hash_map::{ imp::TriHashMap, serde_impls::TriHashMapAsMap, trait_defs::TriHashItem, }, }; use alloc::string::String; use schemars::{JsonSchema, gen::SchemaGenerator, schema::Schema}; impl JsonSchema for TriHashMap where T: JsonSchema + TriHashItem, A: Allocator, { fn schema_name() -> String { alloc::format!("TriHashMap_of_{}", T::schema_name()) } fn json_schema(generator: &mut SchemaGenerator) -> Schema { create_map_schema::("TriHashMap", "iddqd::TriHashMap", generator) } fn is_referenceable() -> bool { false } } impl JsonSchema for TriHashMapAsMap where T: JsonSchema + TriHashItem, A: Allocator, { fn schema_name() -> String { alloc::format!("TriHashMapAsMap_of_{}", T::schema_name()) } fn json_schema(generator: &mut SchemaGenerator) -> Schema { create_object_schema::( "TriHashMapAsMap", "iddqd::TriHashMap", generator, ) } fn is_referenceable() -> bool { false } } iddqd-0.3.17/src/tri_hash_map/serde_impls.rs000064400000000000000000000255351046102023000171140ustar 00000000000000use crate::{ DefaultHashBuilder, TriHashItem, TriHashMap, support::alloc::{Allocator, Global}, }; use core::{fmt, hash::BuildHasher, marker::PhantomData}; use serde_core::{ Deserialize, Deserializer, Serialize, Serializer, de::{MapAccess, SeqAccess, Visitor}, ser::SerializeMap, }; /// A `TriHashMap` serializes to the list of items. Items are serialized in /// arbitrary order. /// /// Serializing as a list of items rather than as a map works around the lack of /// non-string keys in formats like JSON. /// /// To serialize as a map instead, see [`TriHashMapAsMap`]. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// # use iddqd_test_utils::serde_json; /// use serde::{Deserialize, Serialize}; /// /// #[derive(Debug, Serialize)] /// struct Item { /// id: u32, /// name: String, /// email: String, /// value: usize, /// } /// /// // This is a complex key, so it can't be a JSON map key. /// #[derive(Eq, Hash, PartialEq)] /// struct ComplexKey<'a> { /// name: &'a str, /// email: &'a str, /// } /// /// impl TriHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = ComplexKey<'a>; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// fn key3(&self) -> Self::K3<'_> { /// ComplexKey { name: &self.name, email: &self.email } /// } /// tri_upcast!(); /// } /// /// let mut map = TriHashMap::::new(); /// map.insert_unique(Item { /// id: 1, /// name: "Alice".to_string(), /// email: "alice@example.com".to_string(), /// value: 42, /// }) /// .unwrap(); /// /// // The map is serialized as a list of items. /// let serialized = serde_json::to_string(&map).unwrap(); /// assert_eq!( /// serialized, /// r#"[{"id":1,"name":"Alice","email":"alice@example.com","value":42}]"#, /// ); /// # } /// ``` impl Serialize for TriHashMap where T: Serialize, { fn serialize( &self, serializer: Ser, ) -> Result { // Serialize just the items -- don't serialize the indexes. We'll // rebuild the indexes on deserialization. self.items.serialize(serializer) } } /// The `Deserialize` impl for `TriHashMap` deserializes from either a sequence /// or a map of items, then rebuilds the indexes and produces an error if there /// are any duplicates. /// /// In case a map is deserialized, the key is not deserialized or verified /// against the value. (In general, verification is not possible because the key /// type has a lifetime parameter embedded in it.) /// /// The `fmt::Debug` bound on `T` ensures better error reporting. impl< 'de, T: TriHashItem + fmt::Debug, S: Clone + BuildHasher + Default, A: Default + Clone + Allocator, > Deserialize<'de> for TriHashMap where T: Deserialize<'de>, { fn deserialize>( deserializer: D, ) -> Result { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher: S::default(), alloc: A::default(), }) } } impl< 'de, T: TriHashItem + fmt::Debug + Deserialize<'de>, S: Clone + BuildHasher, A: Clone + Allocator, > TriHashMap { /// Deserializes from a list of items, allocating new storage within the /// provided allocator. pub fn deserialize_in>( deserializer: D, alloc: A, ) -> Result where S: Default, { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher: S::default(), alloc, }) } /// Deserializes from a list of items, with the given hasher, using the /// default allocator. pub fn deserialize_with_hasher>( deserializer: D, hasher: S, ) -> Result where A: Default, { deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher, alloc: A::default(), }) } /// Deserializes from a list of items, with the given hasher, and allocating /// new storage within the provided allocator. pub fn deserialize_with_hasher_in>( deserializer: D, hasher: S, alloc: A, ) -> Result { // First, deserialize the items. deserializer.deserialize_any(SeqVisitor { _marker: PhantomData, hasher, alloc, }) } } struct SeqVisitor { _marker: PhantomData T>, hasher: S, alloc: A, } impl<'de, T, S, A> Visitor<'de> for SeqVisitor where T: TriHashItem + Deserialize<'de> + fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Value = TriHashMap; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .write_str("a sequence or map of items representing a TriHashMap") } fn visit_seq( self, mut seq: Access, ) -> Result where Access: SeqAccess<'de>, { let mut map = match seq.size_hint() { Some(size) => TriHashMap::with_capacity_and_hasher_in( size, self.hasher, self.alloc, ), None => TriHashMap::with_hasher_in(self.hasher, self.alloc), }; while let Some(element) = seq.next_element()? { map.insert_unique(element) .map_err(serde_core::de::Error::custom)?; } Ok(map) } fn visit_map( self, mut map_access: Access, ) -> Result where Access: MapAccess<'de>, { let mut map = match map_access.size_hint() { Some(size) => TriHashMap::with_capacity_and_hasher_in( size, self.hasher, self.alloc, ), None => TriHashMap::with_hasher_in(self.hasher, self.alloc), }; while let Some((_, value)) = map_access.next_entry::()? { map.insert_unique(value).map_err(serde_core::de::Error::custom)?; } Ok(map) } } /// Marker type for [`TriHashMap`] serialized as a map, for use with serde's /// `with` attribute. /// /// The key type [`Self::K1`](TriHashItem::K1) is used as the map key. /// /// # Examples /// /// Use with serde's `with` attribute: /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{ /// TriHashItem, TriHashMap, tri_hash_map::TriHashMapAsMap, tri_upcast, /// }; /// use serde::{Deserialize, Serialize}; /// /// #[derive(Debug, Serialize, Deserialize)] /// struct Item { /// id: u32, /// name: String, /// email: String, /// } /// /// impl TriHashItem for Item { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// fn key3(&self) -> Self::K3<'_> { /// &self.email /// } /// tri_upcast!(); /// } /// /// #[derive(Serialize, Deserialize)] /// struct Config { /// #[serde(with = "TriHashMapAsMap")] /// items: TriHashMap, /// } /// # } /// ``` /// /// # Requirements /// /// - For serialization, the key type `K1` must implement [`Serialize`]. /// - For JSON serialization, `K1` should be string-like or convertible to a string key. pub struct TriHashMapAsMap { #[expect(clippy::type_complexity)] _marker: PhantomData (T, S, A)>, } struct MapVisitorAsMap { _marker: PhantomData T>, hasher: S, alloc: A, } impl<'de, T, S, A> Visitor<'de> for MapVisitorAsMap where T: TriHashItem + Deserialize<'de> + fmt::Debug, S: Clone + BuildHasher, A: Clone + Allocator, { type Value = TriHashMap; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a map with items representing a TriHashMap") } fn visit_map( self, mut map_access: Access, ) -> Result where Access: MapAccess<'de>, { let mut map = match map_access.size_hint() { Some(size) => TriHashMap::with_capacity_and_hasher_in( size, self.hasher, self.alloc, ), None => TriHashMap::with_hasher_in(self.hasher, self.alloc), }; while let Some((_, value)) = map_access.next_entry::()? { map.insert_unique(value).map_err(serde_core::de::Error::custom)?; } Ok(map) } } impl TriHashMapAsMap where S: Clone + BuildHasher, A: Allocator, { /// Serializes a `TriHashMap` as a JSON object/map using `key1()` as keys. pub fn serialize<'a, Ser>( map: &TriHashMap, serializer: Ser, ) -> Result where T: 'a + TriHashItem + Serialize, T::K1<'a>: Serialize, Ser: Serializer, { let mut ser_map = serializer.serialize_map(Some(map.len()))?; for item in map.iter() { let key1 = item.key1(); // SAFETY: // // * Lifetime extension: for a type T and two lifetime params 'a and // 'b, T<'a> and T<'b> aren't guaranteed to have the same layout, // but (a) that is true today and (b) it would be shocking and // break half the Rust ecosystem if that were to change in the // future. // * We only use key within the scope of this block before // immediately dropping it. In particular, ser_map.serialize_entry // serializes the key without holding a reference to it. let key1 = unsafe { core::mem::transmute::, T::K1<'a>>(key1) }; ser_map.serialize_entry(&key1, item)?; } ser_map.end() } /// Deserializes a `TriHashMap` from a JSON object/map. pub fn deserialize<'de, D>( deserializer: D, ) -> Result, D::Error> where T: TriHashItem + Deserialize<'de> + fmt::Debug, S: Default, A: Clone + Default, D: Deserializer<'de>, { deserializer.deserialize_map(MapVisitorAsMap { _marker: PhantomData, hasher: S::default(), alloc: A::default(), }) } } iddqd-0.3.17/src/tri_hash_map/tables.rs000064400000000000000000000050411046102023000160460ustar 00000000000000use crate::{ TriHashItem, internal::{ValidateCompact, ValidationError}, support::{ alloc::{Allocator, Global, global_alloc}, hash_table::MapHashTable, map_hash::MapHash, }, }; use core::hash::BuildHasher; #[derive(Clone, Debug, Default)] pub(super) struct TriHashMapTables { pub(super) state: S, pub(super) k1_to_item: MapHashTable, pub(super) k2_to_item: MapHashTable, pub(super) k3_to_item: MapHashTable, } impl TriHashMapTables { pub(super) const fn with_hasher(hasher: S) -> Self { Self { state: hasher, k1_to_item: MapHashTable::new_in(global_alloc()), k2_to_item: MapHashTable::new_in(global_alloc()), k3_to_item: MapHashTable::new_in(global_alloc()), } } } impl TriHashMapTables { pub(super) fn with_capacity_and_hasher_in( capacity: usize, hasher: S, alloc: A, ) -> Self { Self { state: hasher, k1_to_item: MapHashTable::with_capacity_in(capacity, alloc.clone()), k2_to_item: MapHashTable::with_capacity_in(capacity, alloc.clone()), k3_to_item: MapHashTable::with_capacity_in(capacity, alloc), } } } impl TriHashMapTables { #[cfg(feature = "daft")] pub(super) fn hasher(&self) -> &S { &self.state } pub(super) fn validate( &self, expected_len: usize, compactness: ValidateCompact, ) -> Result<(), ValidationError> { // Check that all the maps are of the right size. self.k1_to_item.validate(expected_len, compactness).map_err( |error| ValidationError::Table { name: "k1_to_table", error }, )?; self.k2_to_item.validate(expected_len, compactness).map_err( |error| ValidationError::Table { name: "k2_to_table", error }, )?; self.k3_to_item.validate(expected_len, compactness).map_err( |error| ValidationError::Table { name: "k3_to_table", error }, )?; Ok(()) } pub(super) fn make_hashes(&self, item: &T) -> [MapHash; 3] { let k1 = item.key1(); let k2 = item.key2(); let k3 = item.key3(); let h1 = self.k1_to_item.compute_hash(&self.state, k1); let h2 = self.k2_to_item.compute_hash(&self.state, k2); let h3 = self.k3_to_item.compute_hash(&self.state, k3); [h1, h2, h3] } } iddqd-0.3.17/src/tri_hash_map/trait_defs.rs000064400000000000000000000131101046102023000167140ustar 00000000000000//! Trait definitions for `TriHashMap`. use alloc::{boxed::Box, rc::Rc, sync::Arc}; use core::hash::Hash; /// An item in a [`TriHashMap`]. /// /// This trait is used to define the keys. /// /// # Examples /// /// ``` /// # #[cfg(feature = "default-hasher")] { /// use iddqd::{TriHashItem, TriHashMap, tri_upcast}; /// /// // Define a struct with three keys. /// #[derive(Debug, PartialEq, Eq, Hash)] /// struct Person { /// id: u32, /// name: String, /// email: String, /// } /// /// // Implement TriHashItem for the struct. /// impl TriHashItem for Person { /// type K1<'a> = u32; /// type K2<'a> = &'a str; /// type K3<'a> = &'a str; /// /// fn key1(&self) -> Self::K1<'_> { /// self.id /// } /// /// fn key2(&self) -> Self::K2<'_> { /// &self.name /// } /// /// fn key3(&self) -> Self::K3<'_> { /// &self.email /// } /// /// tri_upcast!(); /// } /// /// // Create a TriHashMap and insert items. /// let mut map = TriHashMap::new(); /// map.insert_unique(Person { /// id: 1, /// name: "Alice".to_string(), /// email: "alice@example.com".to_string(), /// }) /// .unwrap(); /// map.insert_unique(Person { /// id: 2, /// name: "Bob".to_string(), /// email: "bob@example.com".to_string(), /// }) /// .unwrap(); /// # } /// ``` /// /// [`TriHashMap`]: crate::TriHashMap pub trait TriHashItem { /// The first key type. type K1<'a>: Eq + Hash where Self: 'a; /// The second key type. type K2<'a>: Eq + Hash where Self: 'a; /// The third key type. type K3<'a>: Eq + Hash where Self: 'a; /// Retrieves the first key. fn key1(&self) -> Self::K1<'_>; /// Retrieves the second key. fn key2(&self) -> Self::K2<'_>; /// Retrieves the third key. fn key3(&self) -> Self::K3<'_>; /// Upcasts the first key to a shorter lifetime, in effect asserting that /// the lifetime `'a` on [`TriHashItem::K1`] is covariant. /// /// Typically implemented via the [`tri_upcast`] macro. /// /// [`tri_upcast`]: crate::tri_upcast fn upcast_key1<'short, 'long: 'short>( long: Self::K1<'long>, ) -> Self::K1<'short>; /// Upcasts the second key to a shorter lifetime, in effect asserting that /// the lifetime `'a` on [`TriHashItem::K2`] is covariant. /// /// Typically implemented via the [`tri_upcast`] macro. /// /// [`tri_upcast`]: crate::tri_upcast fn upcast_key2<'short, 'long: 'short>( long: Self::K2<'long>, ) -> Self::K2<'short>; /// Upcasts the third key to a shorter lifetime, in effect asserting that /// the lifetime `'a` on [`TriHashItem::K3`] is covariant. /// /// Typically implemented via the [`tri_upcast`] macro. /// /// [`tri_upcast`]: crate::tri_upcast fn upcast_key3<'short, 'long: 'short>( long: Self::K3<'long>, ) -> Self::K3<'short>; } macro_rules! impl_for_ref { ($type:ty) => { impl<'b, T: 'b + ?Sized + TriHashItem> TriHashItem for $type { type K1<'a> = T::K1<'a> where Self: 'a; type K2<'a> = T::K2<'a> where Self: 'a; type K3<'a> = T::K3<'a> where Self: 'a; fn key1(&self) -> Self::K1<'_> { (**self).key1() } fn key2(&self) -> Self::K2<'_> { (**self).key2() } fn key3(&self) -> Self::K3<'_> { (**self).key3() } fn upcast_key1<'short, 'long: 'short>( long: Self::K1<'long>, ) -> Self::K1<'short> where Self: 'long, { T::upcast_key1(long) } fn upcast_key2<'short, 'long: 'short>( long: Self::K2<'long>, ) -> Self::K2<'short> where Self: 'long, { T::upcast_key2(long) } fn upcast_key3<'short, 'long: 'short>( long: Self::K3<'long>, ) -> Self::K3<'short> where Self: 'long, { T::upcast_key3(long) } } }; } impl_for_ref!(&'b T); impl_for_ref!(&'b mut T); macro_rules! impl_for_box { ($type:ty) => { impl TriHashItem for $type { type K1<'a> = T::K1<'a> where Self: 'a; type K2<'a> = T::K2<'a> where Self: 'a; type K3<'a> = T::K3<'a> where Self: 'a; fn key1(&self) -> Self::K1<'_> { (**self).key1() } fn key2(&self) -> Self::K2<'_> { (**self).key2() } fn key3(&self) -> Self::K3<'_> { (**self).key3() } fn upcast_key1<'short, 'long: 'short>( long: Self::K1<'long>, ) -> Self::K1<'short> { T::upcast_key1(long) } fn upcast_key2<'short, 'long: 'short>( long: Self::K2<'long>, ) -> Self::K2<'short> { T::upcast_key2(long) } fn upcast_key3<'short, 'long: 'short>( long: Self::K3<'long>, ) -> Self::K3<'short> { T::upcast_key3(long) } } }; } impl_for_box!(Box); impl_for_box!(Rc); impl_for_box!(Arc); iddqd-0.3.17/tests/integration/bi_hash_map.rs000064400000000000000000001035501046102023000172720ustar 00000000000000use iddqd::{ BiHashItem, BiHashMap, bi_hash_map, bi_upcast, internal::ValidateCompact, }; use iddqd_test_utils::{ borrowed_item::BorrowedItem, eq_props::{assert_eq_props, assert_ne_props}, naive_map::NaiveMap, test_item::{ Alloc, HashBuilder, ItemMap, TestItem, TestKey1, TestKey2, assert_iter_eq, test_item_permutation_strategy, }, }; use proptest::prelude::*; use std::{borrow::Cow, path::Path}; use test_strategy::{Arbitrary, proptest}; #[derive(Clone, Debug)] struct SimpleItem { key1: u32, key2: char, } impl BiHashItem for SimpleItem { type K1<'a> = u32; type K2<'a> = char; fn key1(&self) -> Self::K1<'_> { self.key1 } fn key2(&self) -> Self::K2<'_> { self.key2 } bi_upcast!(); } #[test] fn debug_impls() { let mut map = BiHashMap::::make_new(); map.insert_unique(SimpleItem { key1: 1, key2: 'a' }).unwrap(); map.insert_unique(SimpleItem { key1: 20, key2: 'b' }).unwrap(); map.insert_unique(SimpleItem { key1: 10, key2: 'c' }).unwrap(); assert_eq!( format!("{map:?}"), // This is a small-enough map that the order of iteration is // deterministic. "{{k1: 1, k2: 'a'}: SimpleItem { key1: 1, key2: 'a' }, \ {k1: 10, k2: 'c'}: SimpleItem { key1: 10, key2: 'c' }, \ {k1: 20, k2: 'b'}: SimpleItem { key1: 20, key2: 'b' }}", ); assert_eq!( format!("{:?}", map.get1_mut(&1).unwrap()), "SimpleItem { key1: 1, key2: 'a' }" ); } #[test] fn debug_impls_borrowed() { let before = bi_hash_map! { HashBuilder; BorrowedItem { key1: "a", key2: Cow::Borrowed(b"b0"), key3: Path::new("path0") }, BorrowedItem { key1: "b", key2: Cow::Borrowed(b"b1"), key3: Path::new("path1") }, BorrowedItem { key1: "c", key2: Cow::Borrowed(b"b2"), key3: Path::new("path2") }, }; assert_eq!( format!("{before:?}"), r#"{{k1: "a", k2: [98, 48]}: BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" }, {k1: "c", k2: [98, 50]}: BorrowedItem { key1: "c", key2: [98, 50], key3: "path2" }, {k1: "b", k2: [98, 49]}: BorrowedItem { key1: "b", key2: [98, 49], key3: "path1" }}"# ); #[cfg(feature = "daft")] { use daft::Diffable; let after = bi_hash_map! { HashBuilder; BorrowedItem { key1: "a", key2: Cow::Borrowed(b"b0"), key3: Path::new("path0") }, BorrowedItem { key1: "c", key2: Cow::Borrowed(b"b3"), key3: Path::new("path3") }, BorrowedItem { key1: "d", key2: Cow::Borrowed(b"b4"), key3: Path::new("path4") }, }; let diff = before.diff(&after).by_unique(); assert_eq!( format!("{diff:?}"), r#"Diff { common: {{k1: "a", k2: [98, 48]}: IdLeaf { before: BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" }, after: BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" } }}, added: {{k1: "d", k2: [98, 52]}: BorrowedItem { key1: "d", key2: [98, 52], key3: "path4" }, {k1: "c", k2: [98, 51]}: BorrowedItem { key1: "c", key2: [98, 51], key3: "path3" }}, removed: {{k1: "c", k2: [98, 50]}: BorrowedItem { key1: "c", key2: [98, 50], key3: "path2" }, {k1: "b", k2: [98, 49]}: BorrowedItem { key1: "b", key2: [98, 49], key3: "path1" }} }"# ); } } #[test] fn with_capacity() { let map = BiHashMap::::with_capacity_and_hasher( 1024, HashBuilder::default(), ); assert!(map.capacity() >= 1024); } #[test] fn test_insert_unique() { let mut map = BiHashMap::::make_new(); // Add an element. let v1 = TestItem::new(0, 'a', "x", "v"); map.insert_unique(v1.clone()).unwrap(); // Add an exact duplicate, which should error out. let error = map.insert_unique(v1.clone()).unwrap_err(); assert_eq!(error.new_item(), &v1); assert_eq!(error.duplicates(), vec![&v1]); // Add a duplicate against just key1, which should error out. let v2 = TestItem::new(0, 'b', "x", "v"); let error = map.insert_unique(v2.clone()).unwrap_err(); assert_eq!(error.new_item(), &v2); assert_eq!(error.duplicates(), vec![&v1]); // Add a duplicate against just key2, which should error out. let v3 = TestItem::new(1, 'a', "x", "v"); let error = map.insert_unique(v3.clone()).unwrap_err(); assert_eq!(error.new_item(), &v3); // Add an item that doesn't have any conflicts. (key3 is the same, but // BiHashMap doesn't index on it.) let v4 = TestItem::new(1, 'b', "x", "v"); map.insert_unique(v4.clone()).unwrap(); // Iterate over the items mutably. This ensures that miri detects UB if it // exists. { let mut items: Vec> = map.iter_mut().collect(); items.sort_by(|a, b| a.key1().cmp(&b.key1())); let e1 = &items[0]; assert_eq!(**e1, v1); // Test that the RefMut Debug impl looks good. assert!( format!("{e1:?}").starts_with( r#"TestItem { key1: 0, key2: 'a', key3: "x", value: "v""#, ), "RefMut Debug impl should forward to TestItem" ); let e2 = &*items[1]; assert_eq!(*e2, v4); } // Check that the *unique methods work. assert!(map.contains_key_unique(&v4.key1(), &v4.key2())); assert_eq!(map.get_unique(&v4.key1(), &v4.key2()), Some(&v4)); assert_eq!(*map.get_mut_unique(&v4.key1(), &v4.key2()).unwrap(), &v4); assert_eq!(map.remove_unique(&v4.key1(), &v4.key2()), Some(v4)); } #[test] fn test_extend() { let mut map = BiHashMap::::make_new(); let items = vec![ TestItem::new(1, 'a', "x", "v"), TestItem::new(2, 'b', "y", "w"), TestItem::new(3, 'c', "a", "b"), TestItem::new(1, 'c', "z", "overwrote key1"), TestItem::new(3, 'b', "q", "overwrote key1 and key2"), TestItem::new(4, 'x', "y", "z"), ]; map.extend(items.clone()); assert_eq!(map.len(), 3); assert_eq!(map.get1(&TestKey1::new(&1)).unwrap().value, "overwrote key1"); assert_eq!(map.get1(&TestKey1::new(&2)), None); assert_eq!( map.get1(&TestKey1::new(&3)).unwrap().value, "overwrote key1 and key2" ); assert_eq!(map.get1(&TestKey1::new(&4)).unwrap().value, "z"); } // Example-based test for insert_overwrite. // // Can be used to write down examples seen from the property-based operation // test, for easier debugging. #[test] fn test_insert_overwrite() { let mut map = BiHashMap::::make_new(); // Add an element. let v1 = TestItem::new(20, 'a', "x", "v"); assert_eq!(map.insert_overwrite(v1.clone()), Vec::::new()); // Add an element with the same keys but a different value. let v2 = TestItem::new(20, 'a', "y", "w"); assert_eq!(map.insert_overwrite(v2.clone()), vec![v1]); map.validate(ValidateCompact::NonCompact).expect("validation failed"); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum CompactnessChange { /// The operation makes the map non-compact. NoLongerCompact, /// The operation makes the map compact. BecomesCompact, /// The operation doesn't change compactness. NoChange, } impl CompactnessChange { /// Applies this compactness change to the given compactness state. fn apply(self, compactness: ValidateCompact) -> ValidateCompact { match (compactness, self) { (ValidateCompact::Compact, CompactnessChange::NoLongerCompact) => { ValidateCompact::NonCompact } ( ValidateCompact::NonCompact, CompactnessChange::BecomesCompact, ) => ValidateCompact::Compact, _ => compactness, } } } #[derive(Debug, Arbitrary)] enum Operation { // Make inserts a bit more common to try and fill up the map. #[weight(4)] InsertUnique(TestItem), #[weight(3)] InsertOverwrite(TestItem), #[weight(2)] Get1(u8), #[weight(2)] Get2(char), #[weight(2)] Remove1(u8), #[weight(2)] Remove2(char), #[weight(2)] RetainValueContains(char, bool), #[weight(2)] RetainModulo(#[strategy(0..3_u8)] u8, #[strategy(1..4_u8)] u8, bool), Clear, } impl Operation { fn compactness_change(&self) -> CompactnessChange { match self { Operation::InsertUnique(_) | Operation::Get1(_) | Operation::Get2(_) => CompactnessChange::NoChange, // The act of removing items, including calls to insert_overwrite, // can make the map non-compact. Operation::InsertOverwrite(_) | Operation::Remove1(_) | Operation::Remove2(_) | Operation::RetainValueContains(_, _) | Operation::RetainModulo(_, _, _) => { CompactnessChange::NoLongerCompact } // Clear always makes the map compact (empty). Operation::Clear => CompactnessChange::BecomesCompact, } } } #[proptest(cases = 16)] fn proptest_ops( #[strategy(prop::collection::vec(any::(), 0..1024))] ops: Vec< Operation, >, ) { let mut map = BiHashMap::::make_new(); let mut naive_map = NaiveMap::new_key12(); let mut compactness = ValidateCompact::Compact; // Now perform the operations on both maps. for op in ops.into_iter() { compactness = op.compactness_change().apply(compactness); match op { Operation::InsertUnique(item) => { let map_res = map.insert_unique(item.clone()); let naive_res = naive_map.insert_unique(item.clone()); assert_eq!( map_res.is_ok(), naive_res.is_ok(), "map and naive map should agree on insert result" ); if let Err(map_err) = map_res { let naive_err = naive_res.unwrap_err(); assert_eq!(map_err.new_item(), naive_err.new_item()); // The duplicates may be in any order, so sort them before // comparing. let mut map_err_dups = map_err.duplicates().to_vec(); let mut naive_err_dups = naive_err.duplicates().to_vec(); map_err_dups.sort(); naive_err_dups.sort(); assert_eq!(map_err_dups, naive_err_dups); } map.validate(compactness).expect("map should be valid"); } Operation::InsertOverwrite(item) => { let mut map_dups = map.insert_overwrite(item.clone()); map_dups.sort(); let mut naive_dups = naive_map.insert_overwrite(item.clone()); naive_dups.sort(); assert_eq!( map_dups, naive_dups, "map and naive map should agree on insert_overwrite dups" ); map.validate(compactness).expect("map should be valid"); } Operation::Get1(key1) => { let map_res = map.get1(&TestKey1::new(&key1)); let naive_res = naive_map.get1(key1); assert_eq!(map_res, naive_res); } Operation::Get2(key2) => { let map_res = map.get2(&TestKey2::new(key2)); let naive_res = naive_map.get2(key2); assert_eq!(map_res, naive_res); } Operation::Remove1(key1) => { let map_res = map.remove1(&TestKey1::new(&key1)); let naive_res = naive_map.remove1(key1); assert_eq!(map_res, naive_res); map.validate(compactness).expect("map should be valid"); } Operation::Remove2(key2) => { let map_res = map.remove2(&TestKey2::new(key2)); let naive_res = naive_map.remove2(key2); assert_eq!(map_res, naive_res); map.validate(compactness).expect("map should be valid"); } Operation::RetainValueContains(ch, equals) => { map.retain(|item| { let contains = item.value.contains(ch); if equals { contains } else { !contains } }); naive_map.retain(|item| { let contains = item.value.contains(ch); if equals { contains } else { !contains } }); map.validate(compactness).expect("map should be valid"); } Operation::RetainModulo(a, b, equals) => { let modulo = a + b; let remainder = a; map.retain(|item| { let matches = item.key1 % modulo == remainder; if equals { matches } else { !matches } }); naive_map.retain(|item| { let matches = item.key1 % modulo == remainder; if equals { matches } else { !matches } }); map.validate(compactness).expect("map should be valid"); } Operation::Clear => { map.clear(); naive_map.clear(); map.validate(compactness).expect("map should be valid"); } } // Check that the iterators work correctly. let mut naive_items = naive_map.iter().collect::>(); naive_items.sort_by(|a, b| a.key1().cmp(&b.key1())); assert_iter_eq(map.clone(), naive_items); } } #[proptest(cases = 64)] fn proptest_permutation_eq( #[strategy(test_item_permutation_strategy::>(0..256))] items: (Vec, Vec), ) { let (items1, items2) = items; let mut map1 = BiHashMap::::make_new(); let mut map2 = BiHashMap::::make_new(); for item in items1 { map1.insert_unique(item.clone()).unwrap(); } for item in items2 { map2.insert_unique(item.clone()).unwrap(); } assert_eq_props(map1, map2); } // Test various conditions for non-equality. // // It's a bit difficult to capture mutations in a proptest, so this is a small // example-based test. #[test] fn test_permutation_eq_examples() { let mut map1 = BiHashMap::::make_new(); let mut map2 = BiHashMap::::make_new(); // Two empty maps are equal. assert_eq!(map1, map2); // Insert a single item into one map. let item = TestItem::new(0, 'a', "x", "v"); map1.insert_unique(item.clone()).unwrap(); // The maps are not equal. assert_ne_props(&map1, &map2); // Insert the same item into the other map. map2.insert_unique(item.clone()).unwrap(); // The maps are now equal. assert_eq_props(&map1, &map2); { // Insert an item with a different key1. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(2, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item with a different key2. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'c', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item where all the keys are the same, but the value is // different. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "w")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'b', "y", "x")).unwrap(); assert_ne_props(&map1, &map2); } } #[test] #[should_panic(expected = "key1 changed during RefMut borrow")] fn get_mut_panics_if_key1_changes() { let mut map = BiHashMap::::make_new(); map.insert_unique(TestItem::new(128, 'b', "y", "x")).unwrap(); map.get1_mut(&TestKey1::new(&128)).unwrap().key1 = 2; } #[test] #[should_panic(expected = "key2 changed during RefMut borrow")] fn get_mut_panics_if_key2_changes() { let mut map = BiHashMap::::make_new(); map.insert_unique(TestItem::new(128, 'b', "y", "x")).unwrap(); map.get1_mut(&TestKey1::new(&128)).unwrap().key2 = 'c'; } #[test] fn entry_examples() { let mut map = BiHashMap::::make_new(); let item1 = TestItem::new(0, 'a', "x", "v"); let bi_hash_map::Entry::Vacant(entry) = map.entry(item1.key1(), item1.key2()) else { panic!("expected VacantEntry") }; let mut entry = entry.insert_entry(item1.clone()); assert!(entry.is_unique()); assert!(!entry.is_non_unique()); assert_eq!(entry.get().as_unique(), Some(&item1)); assert_eq!(entry.get().by_key1(), Some(&item1)); assert_eq!(entry.get().by_key2(), Some(&item1)); assert_eq!(entry.get_mut().as_unique().unwrap().into_ref(), &item1); assert_eq!(entry.get_mut().by_key1().unwrap().into_ref(), &item1); assert_eq!(entry.get_mut().by_key2().unwrap().into_ref(), &item1); assert_eq!(entry.into_ref().as_unique(), Some(&item1)); // Test a non-unique item. let item2 = TestItem::new(0, 'b', "x", "v"); let bi_hash_map::Entry::Occupied(mut entry) = map.entry(item2.key1(), item2.key2()) else { panic!("expected OccupiedEntry") }; assert!(!entry.is_unique()); assert!(entry.is_non_unique()); assert_eq!(entry.get().as_unique(), None); assert_eq!(entry.get().by_key1(), Some(&item1)); assert_eq!(entry.get().by_key2(), None); assert!(entry.get_mut().as_unique().is_none()); assert_eq!(entry.get_mut().by_key1().unwrap().into_ref(), &item1); assert!(entry.get_mut().by_key2().is_none()); // Try inserting item2 into the map. let old_items = entry.insert(item2.clone()); assert_eq!(old_items, vec![item1]); // The entry should now be unique. assert!(entry.is_unique()); assert!(!entry.is_non_unique()); assert_eq!(entry.get().as_unique(), Some(&item2)); assert_eq!(entry.get().by_key1(), Some(&item2)); assert_eq!(entry.get().by_key2(), Some(&item2)); // Try removing the entry. let removed = entry.remove(); assert_eq!(removed, vec![item2]); // There should be no items left in the map. assert_eq!(map.len(), 0); // Try adding an item with or_insert_with. let item3 = TestItem::new(1, 'c', "x", "v"); { let mut item3_mut = map .entry(item3.key1(), item3.key2()) .or_insert_with(|| item3.clone()); assert_eq!(item3_mut.as_unique().unwrap().into_ref(), &item3); } // item4 has some conflicts so it should *not* be inserted via the // or_insert_with path. let item4 = TestItem::new(1, 'd', "x", "v"); { let mut item3_mut = map .entry(item4.key1(), item4.key2()) .or_insert_with(|| item4.clone()); assert_eq!(item3_mut.by_key1().unwrap().into_ref(), &item3); } // item5 has no conflicts. let item5 = TestItem::new(2, 'e', "x", "v"); { let mut item5_mut = map .entry(item5.key1(), item5.key2()) .or_insert_with(|| item5.clone()); assert_eq!(item5_mut.as_unique().unwrap().into_ref(), &item5); } // item6 conflicts with both item3 and item5, so `and_modify` should be // called twice. let item6 = TestItem::new(2, 'c', "x", "v"); { let mut item3_seen = false; let mut item5_seen = false; let entry = map.entry(item6.key1(), item6.key2()).and_modify(|item| { if *item == item3 { item3_seen = true; } else if *item == item5 { item5_seen = true; } }); assert!(matches!(entry, bi_hash_map::Entry::Occupied(_))); assert!(item3_seen); assert!(item5_seen); } } #[test] #[should_panic = "key1 hashes do not match"] fn insert_panics_for_non_matching_key1() { let v1 = TestItem::new(0, 'a', "foo", "value"); let mut map = BiHashMap::::make_new(); map.insert_unique(v1.clone()).expect("insert_unique succeeded"); let v2 = TestItem::new(1, 'b', "bar", "value"); let entry = map.entry(TestKey1::new(&2), TestKey2::new('b')); assert!(matches!(entry, bi_hash_map::Entry::Vacant(_))); // Try inserting v2 which matches v1's key2 but not key1. entry.or_insert(v2); } #[test] #[should_panic = "key2 hashes do not match"] fn insert_panics_for_non_matching_key2() { let v1 = TestItem::new(0, 'a', "foo", "value"); let mut map = BiHashMap::::make_new(); map.insert_unique(v1.clone()).expect("insert_unique succeeded"); let v2 = TestItem::new(1, 'b', "bar", "value"); let entry = map.entry(TestKey1::new(&1), TestKey2::new('c')); assert!(matches!(entry, bi_hash_map::Entry::Vacant(_))); // Try inserting v2 which matches v1's key1 but not key2. entry.or_insert(v2); } #[test] fn entry_insert_non_matching_key1() { let v1 = TestItem::new(0, 'a', "foo", "value"); let mut map = BiHashMap::::make_new(); map.insert_unique(v1.clone()).expect("insert_unique succeeded"); let v2 = TestItem::new(1, 'a', "bar", "value"); let entry = map.entry(v2.key1(), v2.key2()); let bi_hash_map::Entry::Occupied(mut entry) = entry else { panic!("expected OccupiedEntry"); }; // Try inserting v1, which is present in the map. assert!(!entry.is_unique(), "only key1 matches"); let old_items = entry.insert(v2); assert_eq!(old_items, vec![v1]); assert!(entry.is_unique(), "entry is now unique"); } #[test] fn entry_insert_non_matching_key2() { let v1 = TestItem::new(0, 'a', "foo", "value"); let mut map = BiHashMap::::make_new(); map.insert_unique(v1.clone()).expect("insert_unique succeeded"); let v2 = TestItem::new(0, 'b', "bar", "value"); let entry = map.entry(v2.key1(), v2.key2()); let bi_hash_map::Entry::Occupied(mut entry) = entry else { panic!("expected OccupiedEntry"); }; // Try inserting v1, which is present in the map. assert!(!entry.is_unique(), "only key1 matches"); let old_items = entry.insert(v2); assert_eq!(old_items, vec![v1]); assert!(entry.is_unique(), "entry is now unique"); } #[test] #[should_panic = "key1 hashes do not match"] fn insert_entry_panics_for_non_matching_keys() { let v1 = TestItem::new(0, 'a', "foo", "value"); let mut map = BiHashMap::<_, HashBuilder, Alloc>::make_new(); map.insert_unique(v1.clone()).expect("insert_unique succeeded"); let v2 = TestItem::new(1, 'b', "bar", "value"); let entry = map.entry(v2.key1(), v2.key2()); assert!(matches!(entry, bi_hash_map::Entry::Vacant(_))); // Try inserting v1, which is present in the map. if let bi_hash_map::Entry::Vacant(entry) = entry { entry.insert_entry(v1); } else { panic!("expected VacantEntry"); } } #[test] fn borrowed_item() { let mut map = BiHashMap::::default(); let item1 = BorrowedItem { key1: "foo", key2: Cow::Borrowed(b"foo"), key3: Path::new("foo"), }; let item2 = BorrowedItem { key1: "bar", key2: Cow::Borrowed(b"bar"), key3: Path::new("bar"), }; // Insert items. map.insert_unique(item1.clone()).unwrap(); map.insert_unique(item2.clone()).unwrap(); // Check that we can retrieve them. assert_eq!(map.get1("foo").unwrap().key1, "foo"); assert_eq!(map.get1("bar").unwrap().key1, "bar"); // Check that we can iterate over them. let keys: Vec<_> = map.iter().map(|item| item.key1()).collect(); assert_eq!(keys, vec!["foo", "bar"]); // Check that we can print a Debug representation, even within a function // (supporting this requires a little bit of unsafe code to get the // lifetimes to line up). fn fmt_debug( map: &BiHashMap, HashBuilder, Alloc>, ) -> String { format!("{map:?}") } #[cfg(feature = "serde")] fn serialize_as_map( map: &BiHashMap, HashBuilder, Alloc>, ) -> Result { let mut out: Vec = Vec::new(); let mut ser = iddqd_test_utils::serde_json::Serializer::new(&mut out); bi_hash_map::BiHashMapAsMap::serialize(map, &mut ser)?; Ok(String::from_utf8(out) .expect("serde_json should always emit valid UTF-8")) } static DEBUG_OUTPUT: &str = "{{k1: \"foo\", k2: [102, 111, 111]}: BorrowedItem { \ key1: \"foo\", key2: [102, 111, 111], key3: \"foo\" }, \ {k1: \"bar\", k2: [98, 97, 114]}: BorrowedItem { \ key1: \"bar\", key2: [98, 97, 114], key3: \"bar\" }}"; assert_eq!(format!("{map:?}"), DEBUG_OUTPUT); assert_eq!(fmt_debug(&map), DEBUG_OUTPUT); #[cfg(feature = "serde")] { let map_string = serialize_as_map(&map).unwrap(); let deserialized: BiHashMap, HashBuilder, Alloc> = iddqd_test_utils::serde_json::from_str(&map_string).unwrap(); assert_eq!(map, deserialized); } } #[test] fn test_retain_all() { let mut map = BiHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); let original_len = map.len(); map.retain(|_| true); assert_eq!(map.len(), original_len); assert_eq!(map.len(), 3); map.get1(&TestKey1::new(&1)).expect("key1=1 should be present"); map.get1(&TestKey1::new(&2)).expect("key1=2 should be present"); map.get1(&TestKey1::new(&3)).expect("key1=3 should be present"); } #[test] fn test_retain_none() { let mut map = BiHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); map.retain(|_| false); assert_eq!(map.len(), 0); assert!(map.is_empty()); } #[test] fn test_retain_value_contains() { let mut map = BiHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); map.insert_unique(TestItem::new(4, 'd', "w", "qux")).unwrap(); map.retain(|item| item.value.contains('a')); assert_eq!(map.len(), 2); map.get1(&TestKey1::new(&2)).expect("key1=2 (bar) should be present"); map.get1(&TestKey1::new(&3)).expect("key1=3 (baz) should be present"); assert!( map.get1(&TestKey1::new(&1)).is_none(), "key1=1 (foo) should be removed" ); assert!( map.get1(&TestKey1::new(&4)).is_none(), "key1=4 (qux) should be removed" ); } #[test] fn test_retain_modulo() { let mut map = BiHashMap::::make_new(); map.insert_unique(TestItem::new(0, 'a', "x", "v0")).unwrap(); map.insert_unique(TestItem::new(1, 'b', "y", "v1")).unwrap(); map.insert_unique(TestItem::new(2, 'c', "z", "v2")).unwrap(); map.insert_unique(TestItem::new(3, 'd', "w", "v3")).unwrap(); map.insert_unique(TestItem::new(4, 'e', "u", "v4")).unwrap(); map.insert_unique(TestItem::new(5, 'f', "t", "v5")).unwrap(); map.retain(|item| item.key1 % 3 == 1); assert_eq!(map.len(), 2); map.get1(&TestKey1::new(&1)).expect("key1=1 should be present"); map.get1(&TestKey1::new(&4)).expect("key1=4 should be present"); assert!(map.get1(&TestKey1::new(&0)).is_none(), "key1=0 should be removed"); assert!(map.get1(&TestKey1::new(&2)).is_none(), "key1=2 should be removed"); assert!(map.get1(&TestKey1::new(&3)).is_none(), "key1=3 should be removed"); assert!(map.get1(&TestKey1::new(&5)).is_none(), "key1=5 should be removed"); // Test with a larger map for miri coverage. let mut large_map = BiHashMap::::make_new(); for i in 0..32_u8 { large_map .insert_unique(TestItem::new(i, char::from(b'a' + i), "y", "z")) .unwrap(); } large_map.retain(|item| item.key1 % 7 == 3); for i in 0..32_u8 { if i % 7 == 3 { large_map .get1(&TestKey1::new(&i)) .unwrap_or_else(|| panic!("key1={} should be present", i)); } else { assert!( large_map.get1(&TestKey1::new(&i)).is_none(), "key1={} should be removed", i ); } } } #[test] fn test_retain_empty_map() { let mut map = BiHashMap::::make_new(); map.retain(|_| true); assert!(map.is_empty()); } #[test] fn test_clear_empty_map() { let mut map = BiHashMap::::make_new(); map.clear(); assert!(map.is_empty()); map.validate(ValidateCompact::Compact) .expect("empty cleared map should be compact"); } #[test] fn test_clear_makes_compact() { let mut map = BiHashMap::::make_new(); // Add items map.insert_unique(TestItem::new(1, 'a', "x", "v1")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "v2")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "v3")).unwrap(); // Remove an item to make it non-compact map.remove1(&TestKey1::new(&2)); map.validate(ValidateCompact::NonCompact) .expect("map should be valid but non-compact"); // Clear should make it compact again map.clear(); assert!(map.is_empty()); map.validate(ValidateCompact::Compact) .expect("cleared map should be compact"); } #[test] fn test_retain_verifies_both_keys() { let mut map = BiHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); // Retain only key1=2 map.retain(|item| item.key1 == 2); // Verify both key1 and key2 work map.get1(&TestKey1::new(&2)).expect("key1=2 should be present"); map.get2(&TestKey2::new('b')).expect("key2='b' should be present"); assert!(map.get1(&TestKey1::new(&1)).is_none()); assert!(map.get2(&TestKey2::new('a')).is_none()); } mod macro_tests { use super::*; #[derive(Debug, PartialEq)] struct User { id: u32, name: String, } impl BiHashItem for User { type K1<'a> = u32; type K2<'a> = &'a str; fn key1(&self) -> Self::K1<'_> { self.id } fn key2(&self) -> Self::K2<'_> { &self.name } bi_upcast!(); } #[cfg(feature = "default-hasher")] #[test] fn macro_basic() { let map = bi_hash_map! { User { id: 1, name: "Alice".to_string() }, User { id: 2, name: "Bob".to_string() }, }; assert_eq!(map.len(), 2); assert_eq!(map.get1(&1).unwrap().name, "Alice"); assert_eq!(map.get2("Bob").unwrap().id, 2); } #[test] fn macro_with_hasher() { let map = bi_hash_map! { HashBuilder; User { id: 3, name: "Charlie".to_string() }, User { id: 4, name: "David".to_string() }, }; assert_eq!(map.len(), 2); assert_eq!(map.get1(&3).unwrap().name, "Charlie"); assert_eq!(map.get2("David").unwrap().id, 4); } #[cfg(feature = "default-hasher")] #[test] fn macro_empty() { let empty_map: BiHashMap = bi_hash_map! {}; assert!(empty_map.is_empty()); } #[cfg(feature = "default-hasher")] #[test] fn macro_without_trailing_comma() { let map = bi_hash_map! { User { id: 1, name: "Alice".to_string() } }; assert_eq!(map.len(), 1); } #[cfg(feature = "default-hasher")] #[test] #[should_panic(expected = "DuplicateItem")] fn macro_duplicate_key1() { let _map = bi_hash_map! { User { id: 1, name: "Alice".to_string() }, User { id: 1, name: "Bob".to_string() }, }; } #[cfg(feature = "default-hasher")] #[test] #[should_panic(expected = "DuplicateItem")] fn macro_duplicate_key2() { let _map = bi_hash_map! { User { id: 1, name: "Alice".to_string() }, User { id: 2, name: "Alice".to_string() }, }; } } #[cfg(feature = "serde")] mod serde_tests { use iddqd::BiHashMap; use iddqd_test_utils::{ serde_utils::assert_serialize_roundtrip, test_item::{Alloc, HashBuilder, TestItem}, }; use test_strategy::proptest; #[proptest] fn proptest_serialize_roundtrip(values: Vec) { assert_serialize_roundtrip::>( values, ); } } #[cfg(feature = "proptest")] #[proptest(cases = 16)] fn proptest_arbitrary_map(map: BiHashMap) { // Test that the arbitrarily generated map is valid. map.validate(ValidateCompact::NonCompact).expect("map should be valid"); // Test that we can perform basic operations on the generated map. let len = map.len(); assert_eq!(map.is_empty(), len == 0); // Test that we can iterate over the map. let mut count = 0; for item in &map { count += 1; // Each item should be findable by both keys. assert_eq!(map.get1(&item.key1()), Some(item)); assert_eq!(map.get2(&item.key2()), Some(item)); } assert_eq!(count, len); } iddqd-0.3.17/tests/integration/id_hash_map.rs000064400000000000000000000644231046102023000173010ustar 00000000000000use iddqd::{ IdHashItem, IdHashMap, id_hash_map, id_upcast, internal::ValidateCompact, }; use iddqd_test_utils::{ borrowed_item::BorrowedItem, eq_props::{assert_eq_props, assert_ne_props}, naive_map::NaiveMap, test_item::{ Alloc, HashBuilder, ItemMap, TestItem, TestKey1, assert_iter_eq, test_item_permutation_strategy, }, }; use proptest::prelude::*; use std::{borrow::Cow, path::Path}; use test_strategy::{Arbitrary, proptest}; #[derive(Clone, Debug)] struct SimpleItem { key: u32, } impl IdHashItem for SimpleItem { type Key<'a> = u32; fn key(&self) -> Self::Key<'_> { self.key } id_upcast!(); } #[test] fn debug_impls() { let mut map = IdHashMap::::make_new(); map.insert_unique(SimpleItem { key: 1 }).unwrap(); map.insert_unique(SimpleItem { key: 20 }).unwrap(); map.insert_unique(SimpleItem { key: 10 }).unwrap(); assert_eq!( format!("{map:?}"), // This is a small-enough map that the order of iteration is // deterministic. r#"{1: SimpleItem { key: 1 }, 10: SimpleItem { key: 10 }, 20: SimpleItem { key: 20 }}"# ); assert_eq!( format!("{:?}", map.get_mut(&1).unwrap()), "SimpleItem { key: 1 }" ); } #[test] fn debug_impls_borrowed() { let before = id_hash_map! { HashBuilder; BorrowedItem { key1: "a", key2: Cow::Borrowed(b"b0"), key3: Path::new("path0") }, BorrowedItem { key1: "b", key2: Cow::Borrowed(b"b1"), key3: Path::new("path1") }, BorrowedItem { key1: "c", key2: Cow::Borrowed(b"b2"), key3: Path::new("path2") }, }; assert_eq!( format!("{before:?}"), r#"{"a": BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" }, "c": BorrowedItem { key1: "c", key2: [98, 50], key3: "path2" }, "b": BorrowedItem { key1: "b", key2: [98, 49], key3: "path1" }}"# ); #[cfg(feature = "daft")] { use daft::Diffable; let after = id_hash_map! { HashBuilder; BorrowedItem { key1: "a", key2: Cow::Borrowed(b"b0"), key3: Path::new("path0") }, BorrowedItem { key1: "c", key2: Cow::Borrowed(b"b3"), key3: Path::new("path3") }, BorrowedItem { key1: "d", key2: Cow::Borrowed(b"b4"), key3: Path::new("path4") }, }; let diff = before.diff(&after); assert_eq!( format!("{diff:?}"), r#"Diff { common: {"a": IdLeaf { before: BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" }, after: BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" } }, "c": IdLeaf { before: BorrowedItem { key1: "c", key2: [98, 50], key3: "path2" }, after: BorrowedItem { key1: "c", key2: [98, 51], key3: "path3" } }}, added: {"d": BorrowedItem { key1: "d", key2: [98, 52], key3: "path4" }}, removed: {"b": BorrowedItem { key1: "b", key2: [98, 49], key3: "path1" }} }"# ); } } #[test] fn with_capacity() { let map = IdHashMap::::with_capacity_and_hasher( 1024, HashBuilder::default(), ); assert!(map.capacity() >= 1024); } #[test] fn test_insert_unique() { let mut map = IdHashMap::::make_new(); // Add an element. let v1 = TestItem::new(20, 'a', "x", "v"); map.insert_unique(v1.clone()).unwrap(); // Add an exact duplicate, which should error out. let error = map.insert_unique(v1.clone()).unwrap_err(); assert_eq!(error.new_item(), &v1); assert_eq!(error.duplicates(), vec![&v1]); // Add a duplicate against just key1, which should error out. let v2 = TestItem::new(20, 'b', "y", "v"); let error = map.insert_unique(v2.clone()).unwrap_err(); assert_eq!(error.new_item(), &v2); assert_eq!(error.duplicates(), vec![&v1]); // Add a duplicate against key2. IdHashMap only uses key1 here, so this // should be allowed. let v3 = TestItem::new(5, 'a', "y", "v"); map.insert_unique(v3.clone()).unwrap(); // Add a duplicate against key1, which should error out. let v4 = TestItem::new(5, 'b', "x", "v"); let error = map.insert_unique(v4.clone()).unwrap_err(); assert_eq!(error.new_item(), &v4); // Iterate over the items mutably. This ensures that miri detects UB if it // exists. let mut items: Vec> = map.iter_mut().collect(); items.sort_by(|a, b| a.key().cmp(&b.key())); let e1 = &items[0]; assert_eq!(**e1, v3); // Test that the RefMut Debug impl looks good. assert!( format!("{e1:?}").starts_with( r#"TestItem { key1: 5, key2: 'a', key3: "y", value: "v""#, ), "RefMut Debug impl should forward to TestItem", ); let e2 = &*items[1]; assert_eq!(*e2, v1); } #[test] fn test_extend() { let mut map = IdHashMap::::make_new(); let items = vec![ TestItem::new(1, 'a', "x", "v"), TestItem::new(2, 'b', "y", "w"), TestItem::new(1, 'c', "z", "overwritten"), // duplicate key, should overwrite ]; map.extend(items.clone()); assert_eq!(map.len(), 2); assert_eq!(map.get(&TestKey1::new(&1)).unwrap().value, "overwritten"); assert_eq!(map.get(&TestKey1::new(&2)).unwrap().value, "w"); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum CompactnessChange { /// The operation makes the map non-compact. NoLongerCompact, /// The operation makes the map compact. BecomesCompact, /// The operation doesn't change compactness. NoChange, } impl CompactnessChange { /// Applies this compactness change to the given compactness state. fn apply(self, compactness: ValidateCompact) -> ValidateCompact { match (compactness, self) { (ValidateCompact::Compact, CompactnessChange::NoLongerCompact) => { ValidateCompact::NonCompact } ( ValidateCompact::NonCompact, CompactnessChange::BecomesCompact, ) => ValidateCompact::Compact, _ => compactness, } } } #[derive(Debug, Arbitrary)] enum Operation { // Make inserts a bit more common to try and fill up the map. #[weight(4)] InsertUnique(TestItem), #[weight(3)] InsertOverwrite(TestItem), #[weight(2)] Get(u8), #[weight(2)] Remove(u8), #[weight(2)] RetainValueContains(char, bool), #[weight(2)] RetainModulo(#[strategy(0..3_u8)] u8, #[strategy(1..4_u8)] u8, bool), Clear, } impl Operation { fn compactness_change(&self) -> CompactnessChange { match self { Operation::InsertUnique(_) | Operation::Get(_) => { CompactnessChange::NoChange } // The act of removing items, including calls to insert_overwrite, // can make the map non-compact. Operation::InsertOverwrite(_) | Operation::Remove(_) | Operation::RetainValueContains(_, _) | Operation::RetainModulo(_, _, _) => { CompactnessChange::NoLongerCompact } // Clear always makes the map compact (empty). Operation::Clear => CompactnessChange::BecomesCompact, } } } #[proptest(cases = 16)] fn proptest_ops( #[strategy(prop::collection::vec(any::(), 0..1024))] ops: Vec< Operation, >, ) { let mut map = IdHashMap::::make_new(); let mut naive_map = NaiveMap::new_key1(); let mut compactness = ValidateCompact::Compact; // Now perform the operations on both maps. for op in ops { compactness = op.compactness_change().apply(compactness); match op { Operation::InsertUnique(item) => { let map_res = map.insert_unique(item.clone()); let naive_res = naive_map.insert_unique(item.clone()); assert_eq!(map_res.is_ok(), naive_res.is_ok()); if let Err(map_err) = map_res { let naive_err = naive_res.unwrap_err(); assert_eq!(map_err.new_item(), naive_err.new_item()); assert_eq!(map_err.duplicates(), naive_err.duplicates()); } map.validate(compactness).expect("map should be valid"); } Operation::InsertOverwrite(item) => { let map_dups = map.insert_overwrite(item.clone()); let mut naive_dups = naive_map.insert_overwrite(item.clone()); assert!(naive_dups.len() <= 1, "max one conflict"); let naive_dup = naive_dups.pop(); assert_eq!( map_dups, naive_dup, "map and naive map should agree on insert_overwrite dup" ); map.validate(compactness).expect("map should be valid"); } Operation::Get(key) => { let map_res = map.get(&TestKey1::new(&key)); let naive_res = naive_map.get1(key); assert_eq!(map_res, naive_res); } Operation::Remove(key) => { let map_res = map.remove(&TestKey1::new(&key)); let naive_res = naive_map.remove1(key); assert_eq!(map_res, naive_res); map.validate(compactness).expect("map should be valid"); } Operation::RetainValueContains(ch, equals) => { map.retain(|item| { let contains = item.value.contains(ch); if equals { contains } else { !contains } }); naive_map.retain(|item| { let contains = item.value.contains(ch); if equals { contains } else { !contains } }); map.validate(compactness).expect("map should be valid"); } Operation::RetainModulo(a, b, equals) => { let modulo = a + b; let remainder = a; map.retain(|item| { let matches = item.key1 % modulo == remainder; if equals { matches } else { !matches } }); naive_map.retain(|item| { let matches = item.key1 % modulo == remainder; if equals { matches } else { !matches } }); map.validate(compactness).expect("map should be valid"); } Operation::Clear => { map.clear(); naive_map.clear(); map.validate(compactness).expect("map should be valid"); } } // Check that the iterators work correctly. let mut naive_items = naive_map.iter().collect::>(); naive_items.sort_by(|a, b| a.key().cmp(&b.key())); assert_iter_eq(map.clone(), naive_items); } } #[proptest(cases = 64)] fn proptest_permutation_eq( #[strategy(test_item_permutation_strategy::>(0..256))] items: (Vec, Vec), ) { let (items1, items2) = items; let mut map1 = IdHashMap::::make_new(); let mut map2 = IdHashMap::::make_new(); for item in items1.clone() { map1.insert_unique(item.clone()).unwrap(); } for item in items2.clone() { map2.insert_unique(item.clone()).unwrap(); } assert_eq_props(&map1, &map2); } // Test various conditions for non-equality. #[test] fn test_permutation_eq_examples() { let mut map1 = IdHashMap::::make_new(); let mut map2 = IdHashMap::::make_new(); // Two empty maps are equal. assert_eq!(map1, map2); // Insert a single item into one map. let item = TestItem::new(0, 'a', "x", "v"); map1.insert_unique(item.clone()).unwrap(); // The maps are not equal. assert_ne_props(&map1, &map2); // Insert the same item into the other map. map2.insert_unique(item.clone()).unwrap(); // The maps are now equal. assert_eq_props(&map1, &map2); { // Insert an item with the same key2 and key3 but a different // key1. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(2, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item with the same key1 and key3 but a different key2. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'c', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item with the same key1 and key2 but a different key3. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'b', "z", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item where all the keys are the same, but the value is // different. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "w")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'b', "y", "x")).unwrap(); assert_ne_props(&map1, &map2); } } #[test] #[should_panic(expected = "key changed during RefMut borrow")] fn get_mut_panics_if_key_changes() { let mut map = IdHashMap::::make_new(); map.insert_unique(TestItem::new(128, 'b', "y", "x")).unwrap(); map.get_mut(&TestKey1::new(&128)).unwrap().key1 = 2; } #[test] fn entry_examples() { let mut map = IdHashMap::::make_new(); let item1 = TestItem::new(0, 'a', "x", "v"); let id_hash_map::Entry::Vacant(entry) = map.entry(item1.key()) else { panic!("expected VacantEntry") }; let mut entry = entry.insert_entry(item1.clone()); assert_eq!(entry.get(), &item1); assert_eq!(entry.get_mut().into_ref(), &item1); assert_eq!(entry.into_ref(), &item1); // Try looking up another item with the same key1. let item2 = TestItem::new(0, 'b', "y", "x"); let id_hash_map::Entry::Occupied(mut entry) = map.entry(item2.key()) else { panic!("expected OccupiedEntry"); }; assert_eq!(entry.insert(item2.clone()), item1); assert_eq!(entry.remove(), item2); // Put item2 back in via the Entry API. let item2_mut = map.entry(item2.key()).or_insert(item2.clone()); assert_eq!(item2_mut.into_ref(), &item2); // Add another item using or_insert_with. let item3 = TestItem::new(1, 'b', "y", "x"); let item3_mut = map.entry(item3.key()).or_insert_with(|| item3.clone()); assert_eq!(item3_mut.into_ref(), &item3); // item4 is similar to item3 except with a different value. let item4 = TestItem::new(1, 'b', "y", "some-other-value"); // item4 should *not* be inserted via this path. let item3_mut = map.entry(item4.key()).or_insert(item4.clone()); assert_eq!(item3_mut.into_ref(), &item3); // Similarly, item4 should *not* be inserted via the or_insert_with path. let item3_mut = map .entry(item4.key()) .or_insert_with(|| panic!("or_insert_with called for existing key")); assert_eq!(item3_mut.into_ref(), &item3); // The and_modify path should be called, however. let mut and_modify_called = false; map.entry(item4.key()).and_modify(|_| and_modify_called = true); assert!(and_modify_called); } #[test] #[should_panic = "key hashes do not match"] fn insert_panics_for_non_matching_key() { let v1 = TestItem::new(0, 'a', "foo", "value"); let mut map = IdHashMap::<_, HashBuilder, Alloc>::make_new(); map.insert_unique(v1.clone()).expect("insert_unique succeeded"); let v2 = TestItem::new(1, 'a', "bar", "value"); let entry = map.entry(v2.key()); assert!(matches!(entry, id_hash_map::Entry::Vacant(_))); // Try inserting v1, which is present in the map. entry.or_insert(v1); } #[test] #[should_panic = "key hashes do not match"] fn insert_entry_panics_for_non_matching_key() { let v1 = TestItem::new(0, 'a', "foo", "value"); let mut map = IdHashMap::<_, HashBuilder, Alloc>::make_new(); map.insert_unique(v1.clone()).expect("insert_unique succeeded"); let v2 = TestItem::new(1, 'a', "bar", "value"); let entry = map.entry(v2.key()); assert!(matches!(entry, id_hash_map::Entry::Vacant(_))); // Try inserting v1, which is present in the map. if let id_hash_map::Entry::Vacant(vacant_entry) = entry { vacant_entry.insert_entry(v1); } else { panic!("expected VacantEntry"); } } #[test] fn borrowed_item() { let mut map = IdHashMap::::default(); let item1 = BorrowedItem { key1: "foo", key2: Cow::Borrowed(b"foo"), key3: Path::new("foo"), }; let item2 = BorrowedItem { key1: "bar", key2: Cow::Borrowed(b"bar"), key3: Path::new("bar"), }; // Insert items. map.insert_unique(item1.clone()).unwrap(); map.insert_unique(item2.clone()).unwrap(); // Check that we can retrieve them. assert_eq!(map.get("foo").unwrap().key1, "foo"); assert_eq!(map.get("bar").unwrap().key1, "bar"); // Check that we can iterate over them. let keys: Vec<_> = map.iter().map(|item| item.key()).collect(); assert_eq!(keys, vec!["foo", "bar"]); // Check that we can print a Debug representation, even within a function // (supporting this requires a little bit of unsafe code to get the // lifetimes to line up). fn fmt_debug( map: &IdHashMap, HashBuilder, Alloc>, ) -> String { format!("{map:?}") } #[cfg(feature = "serde")] fn serialize_as_map( map: &IdHashMap, HashBuilder, Alloc>, ) -> Result { let mut out: Vec = Vec::new(); let mut ser = iddqd_test_utils::serde_json::Serializer::new(&mut out); id_hash_map::IdHashMapAsMap::serialize(map, &mut ser)?; Ok(String::from_utf8(out) .expect("serde_json should always emit valid UTF-8")) } static DEBUG_OUTPUT: &str = "{\"foo\": BorrowedItem { \ key1: \"foo\", key2: [102, 111, 111], key3: \"foo\" }, \ \"bar\": BorrowedItem { \ key1: \"bar\", key2: [98, 97, 114], key3: \"bar\" }}"; assert_eq!(format!("{map:?}"), DEBUG_OUTPUT); assert_eq!(fmt_debug(&map), DEBUG_OUTPUT); #[cfg(feature = "serde")] { let map_string = serialize_as_map(&map).unwrap(); let deserialized: IdHashMap, HashBuilder, Alloc> = iddqd_test_utils::serde_json::from_str(&map_string).unwrap(); assert_eq!(map, deserialized); } } #[test] fn test_retain_all() { let mut map = IdHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); let original_len = map.len(); map.retain(|_| true); assert_eq!(map.len(), original_len); assert_eq!(map.len(), 3); map.get(&TestKey1::new(&1)).expect("key 1 should be present"); map.get(&TestKey1::new(&2)).expect("key 2 should be present"); map.get(&TestKey1::new(&3)).expect("key 3 should be present"); } #[test] fn test_retain_none() { let mut map = IdHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); map.retain(|_| false); assert_eq!(map.len(), 0); assert!(map.is_empty()); } #[test] fn test_retain_value_contains() { let mut map = IdHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); map.insert_unique(TestItem::new(4, 'd', "w", "qux")).unwrap(); map.retain(|item| item.value.contains('a')); assert_eq!(map.len(), 2); map.get(&TestKey1::new(&2)).expect("key 2 (bar) should be present"); map.get(&TestKey1::new(&3)).expect("key 3 (baz) should be present"); assert!( map.get(&TestKey1::new(&1)).is_none(), "key 1 (foo) should be removed" ); assert!( map.get(&TestKey1::new(&4)).is_none(), "key 4 (qux) should be removed" ); } #[test] fn test_retain_modulo() { let mut map = IdHashMap::::make_new(); map.insert_unique(TestItem::new(0, 'a', "x", "v0")).unwrap(); map.insert_unique(TestItem::new(1, 'b', "y", "v1")).unwrap(); map.insert_unique(TestItem::new(2, 'c', "z", "v2")).unwrap(); map.insert_unique(TestItem::new(3, 'd', "w", "v3")).unwrap(); map.insert_unique(TestItem::new(4, 'e', "u", "v4")).unwrap(); map.insert_unique(TestItem::new(5, 'f', "t", "v5")).unwrap(); map.retain(|item| item.key1 % 3 == 1); assert_eq!(map.len(), 2); map.get(&TestKey1::new(&1)).expect("key 1 should be present"); map.get(&TestKey1::new(&4)).expect("key 4 should be present"); assert!(map.get(&TestKey1::new(&0)).is_none(), "key 0 should be removed"); assert!(map.get(&TestKey1::new(&2)).is_none(), "key 2 should be removed"); assert!(map.get(&TestKey1::new(&3)).is_none(), "key 3 should be removed"); assert!(map.get(&TestKey1::new(&5)).is_none(), "key 5 should be removed"); // Test with a larger map for miri coverage. let mut large_map = IdHashMap::::make_new(); for i in 0..32_u8 { large_map.insert_unique(TestItem::new(i, 'x', "y", "z")).unwrap(); } large_map.retain(|item| item.key1 % 7 == 3); for i in 0..32_u8 { if i % 7 == 3 { large_map .get(&TestKey1::new(&i)) .unwrap_or_else(|| panic!("key {} should be present", i)); } else { assert!( large_map.get(&TestKey1::new(&i)).is_none(), "key {} should be removed", i ); } } } #[test] fn test_retain_empty_map() { let mut map = IdHashMap::::make_new(); map.retain(|_| true); assert!(map.is_empty()); } #[test] fn test_clear_empty_map() { let mut map = IdHashMap::::make_new(); map.clear(); assert!(map.is_empty()); map.validate(ValidateCompact::Compact) .expect("empty cleared map should be compact"); } #[test] fn test_clear_makes_compact() { let mut map = IdHashMap::::make_new(); // Add items map.insert_unique(TestItem::new(1, 'a', "x", "v1")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "v2")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "v3")).unwrap(); // Remove an item to make it non-compact map.remove(&TestKey1::new(&2)); map.validate(ValidateCompact::NonCompact) .expect("map should be valid but non-compact"); // Clear should make it compact again map.clear(); assert!(map.is_empty()); map.validate(ValidateCompact::Compact) .expect("cleared map should be compact"); } mod macro_tests { use super::*; #[derive(Debug, PartialEq)] struct User { id: u32, name: String, } impl IdHashItem for User { type Key<'a> = u32; fn key(&self) -> Self::Key<'_> { self.id } id_upcast!(); } #[cfg(feature = "default-hasher")] #[test] fn macro_basic() { let map = id_hash_map! { User { id: 1, name: "Alice".to_string() }, User { id: 2, name: "Bob".to_string() }, }; assert_eq!(map.len(), 2); assert_eq!(map.get(&1).unwrap().name, "Alice"); assert_eq!(map.get(&2).unwrap().name, "Bob"); } #[test] fn macro_with_hasher() { let map = id_hash_map! { HashBuilder; User { id: 3, name: "Charlie".to_string() }, User { id: 4, name: "David".to_string() }, }; assert_eq!(map.len(), 2); assert_eq!(map.get(&3).unwrap().name, "Charlie"); assert_eq!(map.get(&4).unwrap().name, "David"); } #[cfg(feature = "default-hasher")] #[test] fn macro_empty() { let empty_map: IdHashMap = id_hash_map! {}; assert!(empty_map.is_empty()); } #[cfg(feature = "default-hasher")] #[test] fn macro_without_trailing_comma() { let map = id_hash_map! { User { id: 1, name: "Alice".to_string() } }; assert_eq!(map.len(), 1); } #[cfg(feature = "default-hasher")] #[test] #[should_panic(expected = "DuplicateItem")] fn macro_duplicate_key() { let _map = id_hash_map! { User { id: 1, name: "Alice".to_string() }, User { id: 1, name: "Bob".to_string() }, }; } } #[cfg(feature = "proptest")] #[proptest(cases = 16)] fn proptest_arbitrary_map(map: IdHashMap) { // Test that the arbitrarily generated map is valid. map.validate(ValidateCompact::NonCompact).expect("map should be valid"); // Test that we can perform basic operations on the generated map. let len = map.len(); assert_eq!(map.is_empty(), len == 0); // Test that we can iterate over the map. let mut count = 0; for item in &map { count += 1; // Each item should be findable by its key. assert_eq!(map.get(&item.key()), Some(item)); } assert_eq!(count, len); } #[cfg(feature = "serde")] mod serde_tests { use iddqd::IdHashMap; use iddqd_test_utils::{ serde_utils::assert_serialize_roundtrip, test_item::{Alloc, HashBuilder, TestItem}, }; use test_strategy::proptest; #[proptest] fn proptest_serialize_roundtrip(values: Vec) { assert_serialize_roundtrip::>( values, ); } } iddqd-0.3.17/tests/integration/id_ord_map.rs000064400000000000000000001035721046102023000171410ustar 00000000000000use iddqd::{ IdOrdItem, IdOrdMap, id_ord_map, id_upcast, internal::{ValidateChaos, ValidateCompact}, }; use iddqd_test_utils::{ borrowed_item::BorrowedItem, eq_props::{assert_eq_props, assert_ne_props}, naive_map::NaiveMap, test_item::{ ChaosEq, ChaosOrd, ItemMap, KeyChaos, TestItem, TestKey1, assert_iter_eq, test_item_permutation_strategy, without_chaos, }, unwind::catch_panic, }; use proptest::prelude::*; use std::{borrow::Cow, path::Path}; use test_strategy::{Arbitrary, proptest}; #[test] fn with_capacity() { let map = IdOrdMap::::with_capacity(1024); assert!(map.capacity() >= 1024); } #[test] fn test_extend() { let mut map = IdOrdMap::::make_new(); let items = vec![ TestItem::new(1, 'a', "x", "v"), TestItem::new(2, 'b', "y", "w"), TestItem::new(1, 'c', "z", "overwritten"), // duplicate key, should overwrite ]; map.extend(items.clone()); assert_eq!(map.len(), 2); assert_eq!(map.get(&TestKey1::new(&1)).unwrap().value, "overwritten"); assert_eq!(map.get(&TestKey1::new(&2)).unwrap().value, "w"); } #[derive(Clone, Debug)] struct SimpleItem { key: u32, } impl IdOrdItem for SimpleItem { type Key<'a> = u32; fn key(&self) -> Self::Key<'_> { self.key } id_upcast!(); } #[test] fn debug_impls() { let mut map = IdOrdMap::::make_new(); map.insert_unique(SimpleItem { key: 1 }).unwrap(); map.insert_unique(SimpleItem { key: 20 }).unwrap(); map.insert_unique(SimpleItem { key: 10 }).unwrap(); assert_eq!( format!("{map:?}"), r#"{1: SimpleItem { key: 1 }, 10: SimpleItem { key: 10 }, 20: SimpleItem { key: 20 }}"# ); assert_eq!( format!("{:?}", map.get_mut(&1).unwrap()), "SimpleItem { key: 1 }" ); } // Ensure that Debug impls work for borrowed items, including diff // implementations. #[test] fn debug_impls_borrowed() { let before = id_ord_map! { BorrowedItem { key1: "a", key2: Cow::Borrowed(b"b0"), key3: Path::new("path0") }, BorrowedItem { key1: "b", key2: Cow::Borrowed(b"b1"), key3: Path::new("path1") }, BorrowedItem { key1: "c", key2: Cow::Borrowed(b"b2"), key3: Path::new("path2") }, }; assert_eq!( format!("{before:?}"), r#"{"a": BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" }, "b": BorrowedItem { key1: "b", key2: [98, 49], key3: "path1" }, "c": BorrowedItem { key1: "c", key2: [98, 50], key3: "path2" }}"# ); #[cfg(feature = "daft")] { use daft::Diffable; let after = id_ord_map! { BorrowedItem { key1: "a", key2: Cow::Borrowed(b"b0"), key3: Path::new("path0") }, BorrowedItem { key1: "c", key2: Cow::Borrowed(b"b3"), key3: Path::new("path3") }, BorrowedItem { key1: "d", key2: Cow::Borrowed(b"b4"), key3: Path::new("path4") }, }; let diff = before.diff(&after); assert_eq!( format!("{diff:?}"), r#"Diff { common: {"a": IdLeaf { before: BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" }, after: BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" } }, "c": IdLeaf { before: BorrowedItem { key1: "c", key2: [98, 50], key3: "path2" }, after: BorrowedItem { key1: "c", key2: [98, 51], key3: "path3" } }}, added: {"d": BorrowedItem { key1: "d", key2: [98, 52], key3: "path4" }}, removed: {"b": BorrowedItem { key1: "b", key2: [98, 49], key3: "path1" }} }"# ); } } #[test] fn test_compact_chaos() { let mut map = IdOrdMap::::make_new(); let mut chaos_eq = ChaosEq::all_variants().into_iter().cycle(); let mut chaos_ord = ChaosOrd::all_variants().into_iter().cycle(); for i in 0..64 { eprintln!("iteration {i}"); let key1_chaos = KeyChaos::default() .with_eq(chaos_eq.next().unwrap()) .with_ord(chaos_ord.next().unwrap()); let item = TestItem::new(i, 'a', "x", "v").with_key1_chaos(key1_chaos); // This may or may not work, and may even panic; we care about two // things: // // 1. The map shouldn't be left in an invalid state. // 2. UB detection with Miri. catch_panic(|| map.insert_unique(item.clone())); // iter_mut can potentially cause mutable UB. catch_panic(|| map.iter_mut().collect::>()); catch_panic(|| match map.entry(item.key()) { id_ord_map::Entry::Vacant(_) => {} id_ord_map::Entry::Occupied(mut entry) => { // This can trigger some unsafe code. { let _mut1 = entry.get_mut(); } let _mut2 = entry.into_mut(); } }); without_chaos(|| { map.validate(ValidateCompact::Compact, ValidateChaos::Yes) .unwrap_or_else(|error| { panic!("iteration {i}: map invalid: {error}") }) }); } } #[test] fn test_insert_unique() { let mut map = IdOrdMap::::make_new(); // Add an element. let v1 = TestItem::new(20, 'a', "x", "v"); map.insert_unique(v1.clone()).unwrap(); // Add an exact duplicate, which should error out. let error = map.insert_unique(v1.clone()).unwrap_err(); assert_eq!(error.new_item(), &v1); assert_eq!(error.duplicates(), vec![&v1]); // Add a duplicate against just key1, which should error out. let v2 = TestItem::new(20, 'b', "y", "v"); let error = map.insert_unique(v2.clone()).unwrap_err(); assert_eq!(error.new_item(), &v2); assert_eq!(error.duplicates(), vec![&v1]); // Add a duplicate against key2. IdOrdMap only uses key1 here, so this // should be allowed. let v3 = TestItem::new(5, 'a', "y", "v"); map.insert_unique(v3.clone()).unwrap(); // Add a duplicate against key1, which should error out. let v4 = TestItem::new(5, 'b', "x", "v"); let error = map.insert_unique(v4.clone()).unwrap_err(); assert_eq!(error.new_item(), &v4); // Iterate over the items mutably. This ensures that miri detects UB if it // exists. let items: Vec> = map.iter_mut().collect(); let e1 = &items[0]; assert_eq!(**e1, v3); // Test that the RefMut Debug impl looks good. assert!( format!("{e1:?}").starts_with( r#"TestItem { key1: 5, key2: 'a', key3: "y", value: "v""#, ), "RefMut Debug impl should forward to TestItem", ); let e2 = &*items[1]; assert_eq!(*e2, v1); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum CompactnessChange { /// The operation makes the map non-compact. NoLongerCompact, /// The operation makes the map compact. BecomesCompact, /// The operation doesn't change compactness. NoChange, } impl CompactnessChange { /// Applies this compactness change to the given compactness state. fn apply(self, compactness: ValidateCompact) -> ValidateCompact { match (compactness, self) { (ValidateCompact::Compact, CompactnessChange::NoLongerCompact) => { ValidateCompact::NonCompact } ( ValidateCompact::NonCompact, CompactnessChange::BecomesCompact, ) => ValidateCompact::Compact, _ => compactness, } } } #[derive(Debug, Arbitrary)] enum Operation { // Make inserts a bit more common to try and fill up the map. #[weight(6)] InsertUnique(TestItem), #[weight(4)] InsertOverwrite(TestItem), #[weight(2)] Get(u8), #[weight(2)] Remove(u8), #[weight(2)] First, #[weight(2)] Last, #[weight(2)] PopFirst, #[weight(2)] PopLast, #[weight(2)] FirstEntryModify(String), #[weight(2)] LastEntryModify(String), #[weight(2)] RetainValueContains(char, bool), #[weight(2)] RetainModulo(#[strategy(0..3_u8)] u8, #[strategy(1..4_u8)] u8, bool), // clear is set to a lower weight since it makes the map empty. Clear, } impl Operation { fn compactness_change(&self) -> CompactnessChange { match self { Operation::InsertUnique(_) | Operation::Get(_) | Operation::First | Operation::Last | Operation::FirstEntryModify(_) | Operation::LastEntryModify(_) => CompactnessChange::NoChange, // The act of removing items, including calls to insert_overwrite, // can make the map non-compact. Operation::InsertOverwrite(_) | Operation::Remove(_) | Operation::PopFirst | Operation::PopLast | Operation::RetainValueContains(_, _) | Operation::RetainModulo(_, _, _) => { CompactnessChange::NoLongerCompact } // Clear always makes the map compact (empty). Operation::Clear => CompactnessChange::BecomesCompact, } } } #[proptest(cases = 16)] fn proptest_ops( #[strategy(prop::collection::vec(any::(), 0..1024))] ops: Vec< Operation, >, ) { let mut map = IdOrdMap::::make_new(); let mut naive_map = NaiveMap::new_key1(); let mut compactness = ValidateCompact::Compact; // Now perform the operations on both maps. for op in ops { compactness = op.compactness_change().apply(compactness); match op { Operation::InsertUnique(item) => { let map_res = map.insert_unique(item.clone()); let naive_res = naive_map.insert_unique(item.clone()); assert_eq!(map_res.is_ok(), naive_res.is_ok()); if let Err(map_err) = map_res { let naive_err = naive_res.unwrap_err(); assert_eq!(map_err.new_item(), naive_err.new_item()); assert_eq!(map_err.duplicates(), naive_err.duplicates()); } map.validate(compactness, ValidateChaos::No) .expect("map should be valid"); } Operation::InsertOverwrite(item) => { let map_dups = map.insert_overwrite(item.clone()); let mut naive_dups = naive_map.insert_overwrite(item.clone()); assert!(naive_dups.len() <= 1, "max one conflict"); let naive_dup = naive_dups.pop(); assert_eq!( map_dups, naive_dup, "map and naive map should agree on insert_overwrite dup" ); map.validate(compactness, ValidateChaos::No) .expect("map should be valid"); } Operation::Get(key) => { let map_res = map.get(&TestKey1::new(&key)); let naive_res = naive_map.get1(key); assert_eq!(map_res, naive_res); } Operation::Remove(key) => { let map_res = map.remove(&TestKey1::new(&key)); let naive_res = naive_map.remove1(key); assert_eq!(map_res, naive_res); map.validate(compactness, ValidateChaos::No) .expect("map should be valid"); } Operation::First => { let map_res = map.first(); let naive_res = naive_map.first(); assert_eq!(map_res, naive_res); } Operation::Last => { let map_res = map.last(); let naive_res = naive_map.last(); assert_eq!(map_res, naive_res); } Operation::PopFirst => { let map_res = map.pop_first(); let naive_res = naive_map.pop_first(); assert_eq!(map_res, naive_res); map.validate(compactness, ValidateChaos::No) .expect("map should be valid"); } Operation::PopLast => { let map_res = map.pop_last(); let naive_res = naive_map.pop_last(); assert_eq!(map_res, naive_res); map.validate(compactness, ValidateChaos::No) .expect("map should be valid"); } Operation::FirstEntryModify(new_value) => { match (map.first_entry(), naive_map.first_mut()) { (Some(mut entry), Some(item)) => { let key1 = entry.get().key1; entry.get_mut().value = new_value.clone(); item.value = new_value.clone(); assert_eq!( map.get(&TestKey1::new(&key1)).unwrap().value, new_value ); } (None, None) => { // Both empty, this is fine. } _ => { panic!( "map and naive_map should agree on first_entry/first_mut" ); } } map.validate(compactness, ValidateChaos::No) .expect("map should be valid"); } Operation::LastEntryModify(new_value) => { match (map.last_entry(), naive_map.last_mut()) { (Some(mut entry), Some(item)) => { let key1 = entry.get().key1; entry.get_mut().value = new_value.clone(); item.value = new_value.clone(); assert_eq!( map.get(&TestKey1::new(&key1)).unwrap().value, new_value ); } (None, None) => { // Both empty, this is fine. } _ => { panic!( "map and naive_map should agree on last_entry/last_mut" ); } } map.validate(compactness, ValidateChaos::No) .expect("map should be valid"); } Operation::RetainValueContains(ch, equals) => { map.retain(|item| { let contains = item.value.contains(ch); if equals { contains } else { !contains } }); naive_map.retain(|item| { let contains = item.value.contains(ch); if equals { contains } else { !contains } }); map.validate(compactness, ValidateChaos::No) .expect("map should be valid"); } Operation::RetainModulo(a, b, equals) => { let modulo = a + b; let remainder = a; map.retain(|item| { let matches = item.key1 % modulo == remainder; if equals { matches } else { !matches } }); naive_map.retain(|item| { let matches = item.key1 % modulo == remainder; if equals { matches } else { !matches } }); map.validate(compactness, ValidateChaos::No) .expect("map should be valid"); } Operation::Clear => { map.clear(); naive_map.clear(); map.validate(compactness, ValidateChaos::No) .expect("map should be valid"); } } // Check that the iterators work correctly. let mut naive_items = naive_map.iter().collect::>(); naive_items.sort_by(|a, b| a.key().cmp(&b.key())); assert_iter_eq(map.clone(), naive_items); } } #[proptest(cases = 64)] fn proptest_permutation_eq( #[strategy(test_item_permutation_strategy::>(0..256))] items: (Vec, Vec), ) { let (items1, items2) = items; let mut map1 = IdOrdMap::::make_new(); let mut map2 = IdOrdMap::::make_new(); for item in items1.clone() { map1.insert_unique(item.clone()).unwrap(); } for item in items2.clone() { map2.insert_unique(item.clone()).unwrap(); } assert_eq_props(&map1, &map2); // Also test from_iter_unique. let map3 = IdOrdMap::from_iter_unique(items1).unwrap(); let map4 = IdOrdMap::from_iter_unique(items2).unwrap(); assert_eq_props(&map1, &map3); assert_eq_props(&map3, &map4); } // Test various conditions for non-equality. #[test] fn test_permutation_eq_examples() { let mut map1 = IdOrdMap::::make_new(); let mut map2 = IdOrdMap::::make_new(); // Two empty maps are equal. assert_eq!(map1, map2); // Insert a single item into one map. let item = TestItem::new(0, 'a', "x", "v"); map1.insert_unique(item.clone()).unwrap(); // The maps are not equal. assert_ne_props(&map1, &map2); // Insert the same item into the other map. map2.insert_unique(item.clone()).unwrap(); // The maps are now equal. assert_eq_props(&map1, &map2); { // Insert an item with the same key2 and key3 but a different // key1. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(2, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item with the same key1 and key3 but a different key2. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'c', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item with the same key1 and key2 but a different key3. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'b', "z", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item where all the keys are the same, but the value is // different. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "w")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'b', "y", "x")).unwrap(); assert_ne_props(&map1, &map2); } } #[test] #[should_panic(expected = "key changed during RefMut borrow")] fn get_mut_panics_if_key_changes() { let mut map = IdOrdMap::::make_new(); map.insert_unique(TestItem::new(128, 'b', "y", "x")).unwrap(); map.get_mut(&TestKey1::new(&128)).unwrap().key1 = 2; } #[test] fn entry_examples() { let mut map = IdOrdMap::::make_new(); let item1 = TestItem::new(0, 'a', "x", "v"); let id_ord_map::Entry::Vacant(entry) = map.entry(item1.key()) else { panic!("expected VacantEntry") }; let mut entry = entry.insert_entry(item1.clone()); assert_eq!(entry.get(), &item1); assert_eq!(entry.get_mut().into_ref(), &item1); assert_eq!(entry.into_ref(), &item1); // Try looking up another item with the same key1. let item2 = TestItem::new(0, 'b', "y", "x"); let id_ord_map::Entry::Occupied(mut entry) = map.entry(item2.key()) else { panic!("expected OccupiedEntry"); }; assert_eq!(entry.insert(item2.clone()), item1); assert_eq!(entry.remove(), item2); // Put item2 back in via the Entry API. let item2_mut = map.entry(item2.key()).or_insert(item2.clone()); assert_eq!(item2_mut.into_ref(), &item2); // Add another item using or_insert_with. let item3 = TestItem::new(1, 'b', "y", "x"); let item3_mut = map.entry(item3.key()).or_insert_with(|| item3.clone()); assert_eq!(item3_mut.into_ref(), &item3); // item4 is similar to item3 except with a different value. let item4 = TestItem::new(1, 'b', "y", "some-other-value"); // item4 should *not* be inserted via this path. let item3_mut = map.entry(item4.key()).or_insert(item4.clone()); assert_eq!(item3_mut.into_ref(), &item3); // Similarly, item4 should *not* be inserted via the or_insert_with path. let item3_mut = map .entry(item4.key()) .or_insert_with(|| panic!("or_insert_with called for existing key")); assert_eq!(item3_mut.into_ref(), &item3); // Add another item using or_insert_ref. let item5 = TestItem::new(2, 'c', "z", "w"); let item5_ref = map.entry(item5.key()).or_insert_ref(item5.clone()); assert_eq!(item5_ref, &item5); // Add another item using or_insert_with_ref. let item6 = TestItem::new(3, 'd', "a", "b"); let item6_ref = map.entry(item6.key()).or_insert_with_ref(|| item6.clone()); assert_eq!(item6_ref, &item6); // item7 is similar to item5 except with a different value. let item7 = TestItem::new(2, 'c', "z", "yet-another-value"); // item7 should *not* be inserted via this path. let item5_ref = map.entry(item7.key()).or_insert_ref(item7.clone()); assert_eq!(item5_ref, &item5); // Similarly, item7 should *not* be inserted via the or_insert_with_ref // path. let entry = map.entry(item7.key()).or_insert_with_ref(|| { panic!("or_insert_with_ref called for existing key") }); assert_eq!(entry, &item5); // The and_modify path should be called, however. let mut and_modify_called = false; map.entry(item4.key()).and_modify(|_| and_modify_called = true); assert!(and_modify_called); } #[test] #[should_panic = "key already present in map"] fn or_insert_ref_panics_for_present_key() { let v1 = TestItem::new(0, 'a', "foo", "value"); let mut map = IdOrdMap::make_new(); map.insert_unique(v1.clone()).expect("insert_unique succeeded"); let v2 = TestItem::new(1, 'a', "bar", "value"); let entry = map.entry(v2.key()); assert!(matches!(entry, id_ord_map::Entry::Vacant(_))); // Try inserting v1, which is present in the map. entry.or_insert_ref(v1); } #[test] #[should_panic = "key already present in map"] fn or_insert_panics_for_present_key() { let v1 = TestItem::new(0, 'a', "foo", "value"); let mut map = IdOrdMap::make_new(); map.insert_unique(v1.clone()).expect("insert_unique succeeded"); let v2 = TestItem::new(1, 'a', "bar", "value"); let entry = map.entry(v2.key()); assert!(matches!(entry, id_ord_map::Entry::Vacant(_))); // Try inserting v1, which is present in the map. entry.or_insert(v1); } #[test] #[should_panic = "key already present in map"] fn insert_entry_panics_for_present_key() { let v1 = TestItem::new(0, 'a', "foo", "value"); let mut map = IdOrdMap::make_new(); map.insert_unique(v1.clone()).expect("insert_unique succeeded"); let v2 = TestItem::new(1, 'a', "bar", "value"); let entry = map.entry(v2.key()); assert!(matches!(entry, id_ord_map::Entry::Vacant(_))); // Try inserting v1, which is present in the map. if let id_ord_map::Entry::Vacant(vacant_entry) = entry { vacant_entry.insert_entry(v1); } else { panic!("Expected Vacant entry"); } } #[test] fn test_retain_all() { let mut map = IdOrdMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); let original_len = map.len(); map.retain(|_| true); assert_eq!(map.len(), original_len); assert_eq!(map.len(), 3); map.get(&TestKey1::new(&1)).expect("key 1 should be present"); map.get(&TestKey1::new(&2)).expect("key 2 should be present"); map.get(&TestKey1::new(&3)).expect("key 3 should be present"); } #[test] fn test_retain_none() { let mut map = IdOrdMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); map.retain(|_| false); assert_eq!(map.len(), 0); assert!(map.is_empty()); } #[test] fn test_retain_value_contains() { let mut map = IdOrdMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); map.insert_unique(TestItem::new(4, 'd', "w", "qux")).unwrap(); map.retain(|item| item.value.contains('a')); assert_eq!(map.len(), 2); map.get(&TestKey1::new(&2)).expect("key 2 (bar) should be present"); map.get(&TestKey1::new(&3)).expect("key 3 (baz) should be present"); assert!( map.get(&TestKey1::new(&1)).is_none(), "key 1 (foo) should be removed" ); assert!( map.get(&TestKey1::new(&4)).is_none(), "key 4 (qux) should be removed" ); } #[test] fn test_retain_modulo() { let mut map = IdOrdMap::::make_new(); map.insert_unique(TestItem::new(0, 'a', "x", "v0")).unwrap(); map.insert_unique(TestItem::new(1, 'b', "y", "v1")).unwrap(); map.insert_unique(TestItem::new(2, 'c', "z", "v2")).unwrap(); map.insert_unique(TestItem::new(3, 'd', "w", "v3")).unwrap(); map.insert_unique(TestItem::new(4, 'e', "u", "v4")).unwrap(); map.insert_unique(TestItem::new(5, 'f', "t", "v5")).unwrap(); map.retain(|item| item.key1 % 3 == 1); assert_eq!(map.len(), 2); map.get(&TestKey1::new(&1)).expect("key 1 should be present"); map.get(&TestKey1::new(&4)).expect("key 4 should be present"); assert!(map.get(&TestKey1::new(&0)).is_none(), "key 0 should be removed"); assert!(map.get(&TestKey1::new(&2)).is_none(), "key 2 should be removed"); assert!(map.get(&TestKey1::new(&3)).is_none(), "key 3 should be removed"); assert!(map.get(&TestKey1::new(&5)).is_none(), "key 5 should be removed"); // Test with a larger map for miri coverage. let mut large_map = IdOrdMap::::make_new(); for i in 0..32_u8 { large_map.insert_unique(TestItem::new(i, 'x', "y", "z")).unwrap(); } large_map.retain(|item| item.key1 % 7 == 3); // Verify the retained items. for i in 0..32_u8 { if i % 7 == 3 { large_map .get(&TestKey1::new(&i)) .unwrap_or_else(|| panic!("key {} should be present", i)); } else { assert!( large_map.get(&TestKey1::new(&i)).is_none(), "key {} should be removed", i ); } } } #[test] fn test_retain_preserves_ordering() { let mut map = IdOrdMap::::make_new(); map.insert_unique(TestItem::new(5, 'a', "x", "v5")).unwrap(); map.insert_unique(TestItem::new(1, 'b', "y", "v1")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "v3")).unwrap(); map.insert_unique(TestItem::new(7, 'd', "w", "v7")).unwrap(); map.insert_unique(TestItem::new(2, 'e', "u", "v2")).unwrap(); // Retain odd keys map.retain(|item| item.key1 % 2 == 1); // Iteration should be in key order: 1, 3, 5, 7 let keys: Vec = map.iter().map(|item| item.key1).collect(); assert_eq!(keys, vec![1, 3, 5, 7]); } #[test] fn test_retain_empty_map() { let mut map = IdOrdMap::::make_new(); map.retain(|_| true); assert!(map.is_empty()); } #[test] fn test_clear_empty_map() { let mut map = IdOrdMap::::make_new(); map.clear(); assert!(map.is_empty()); map.validate(ValidateCompact::Compact, ValidateChaos::No) .expect("empty cleared map should be compact"); } #[test] fn test_clear_makes_compact() { let mut map = IdOrdMap::::make_new(); // Add items. map.insert_unique(TestItem::new(1, 'a', "x", "v1")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "v2")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "v3")).unwrap(); // Remove an item to make it non-compact. map.remove(&TestKey1::new(&2)); map.validate(ValidateCompact::NonCompact, ValidateChaos::No) .expect("map should be valid but non-compact"); // Clear should make it compact again. map.clear(); assert!(map.is_empty()); map.validate(ValidateCompact::Compact, ValidateChaos::No) .expect("cleared map should be compact"); } #[test] fn borrowed_item() { let mut map = IdOrdMap::::default(); let item1 = BorrowedItem { key1: "foo", key2: Cow::Borrowed(b"foo"), key3: Path::new("foo"), }; let item2 = BorrowedItem { key1: "bar", key2: Cow::Borrowed(b"bar"), key3: Path::new("bar"), }; // Insert items. map.insert_unique(item1.clone()).unwrap(); map.insert_unique(item2.clone()).unwrap(); // Check that we can retrieve them. assert_eq!(map.get("foo").unwrap().key1, "foo"); assert_eq!(map.get("bar").unwrap().key1, "bar"); // Check that we can mutably retrieve them. { let mut item1 = map.get_mut("foo").unwrap(); item1.key2 = Cow::Borrowed(b"foo2"); // Including reborrows. { let mut item1_reborrowed = item1.reborrow(); item1_reborrowed.key3 = Path::new("foo2"); } item1.key2 = Cow::Borrowed(b"foo3"); } // Check that we can iterate over them. let keys: Vec<_> = map.iter().map(|item| item.key()).collect(); assert_eq!(keys, vec!["bar", "foo"]); // Check that we can print a Debug representation, even within a function // (supporting this requires a little bit of unsafe code to get the // lifetimes to line up). fn fmt_debug(map: &IdOrdMap>) -> String { format!("{map:?}") } #[cfg(feature = "serde")] fn serialize_as_map( map: &IdOrdMap>, ) -> Result { let mut out: Vec = Vec::new(); let mut ser = iddqd_test_utils::serde_json::Serializer::new(&mut out); id_ord_map::IdOrdMapAsMap::serialize(map, &mut ser)?; Ok(String::from_utf8(out) .expect("serde_json should always emit valid UTF-8")) } static DEBUG_OUTPUT: &str = "{\"bar\": BorrowedItem { \ key1: \"bar\", key2: [98, 97, 114], key3: \"bar\" }, \ \"foo\": BorrowedItem { \ key1: \"foo\", key2: [102, 111, 111, 51], key3: \"foo2\" }}"; assert_eq!(format!("{map:?}"), DEBUG_OUTPUT); assert_eq!(fmt_debug(&map), DEBUG_OUTPUT); #[cfg(feature = "serde")] { let map_string = serialize_as_map(&map).unwrap(); let deserialized: IdOrdMap> = iddqd_test_utils::serde_json::from_str(&map_string).unwrap(); assert_eq!(map, deserialized); } // Try using the entry API against the borrowed item. fn entry_api_tests(map: &mut IdOrdMap>) { let entry = map.entry("foo"); entry.or_insert(BorrowedItem { key1: "foo", key2: Cow::Borrowed(b"foo"), key3: Path::new("foo"), }); let entry = map.entry("foo"); entry.or_insert_with(|| BorrowedItem { key1: "foo", key2: Cow::Borrowed(b"foo"), key3: Path::new("foo"), }); let entry = map.entry("bar"); let entry = entry.and_modify(|mut v| { // IdOrdMap> is not indexed by key2, so changing // key2 will not cause a panic. (Changing key1 would cause a panic.) v.key2 = Cow::Borrowed(b"baz"); }); let id_ord_map::Entry::Occupied(mut entry) = entry else { panic!("Entry should be occupied") }; let mut v = entry.get_mut(); v.key2 = Cow::Borrowed(b"quux"); } entry_api_tests(&mut map); } mod macro_tests { use super::*; #[derive(Debug, PartialEq)] struct User { id: u32, name: String, } impl IdOrdItem for User { type Key<'a> = u32; fn key(&self) -> Self::Key<'_> { self.id } id_upcast!(); } #[test] fn macro_basic() { let map = id_ord_map! { User { id: 1, name: "Alice".to_string() }, User { id: 2, name: "Bob".to_string() }, }; assert_eq!(map.len(), 2); assert_eq!(map.get(&1).unwrap().name, "Alice"); assert_eq!(map.get(&2).unwrap().name, "Bob"); } #[test] fn macro_empty() { let empty_map: IdOrdMap = id_ord_map! {}; assert!(empty_map.is_empty()); } #[test] fn macro_without_trailing_comma() { let map = id_ord_map! { User { id: 1, name: "Alice".to_string() } }; assert_eq!(map.len(), 1); } #[test] #[should_panic(expected = "DuplicateItem")] fn macro_duplicate_key() { let _map = id_ord_map! { User { id: 1, name: "Alice".to_string() }, User { id: 1, name: "Bob".to_string() }, }; } } #[cfg(feature = "serde")] mod serde_tests { use iddqd::IdOrdMap; use iddqd_test_utils::{ serde_utils::assert_serialize_roundtrip, test_item::TestItem, }; use test_strategy::proptest; #[proptest] fn proptest_serialize_roundtrip(values: Vec) { assert_serialize_roundtrip::>(values); } } #[cfg(feature = "proptest")] #[proptest(cases = 16)] fn proptest_arbitrary_map(map: IdOrdMap) { // Test that the arbitrarily generated map is valid. map.validate(ValidateCompact::NonCompact, ValidateChaos::No) .expect("map should be valid"); // Test that we can perform basic operations on the generated map. let len = map.len(); assert_eq!(map.is_empty(), len == 0); // Test that we can iterate over the map. let mut count = 0; for item in &map { count += 1; // Each item should be findable by its key. assert_eq!(map.get(&item.key()), Some(item)); } assert_eq!(count, len); } iddqd-0.3.17/tests/integration/main.rs000064400000000000000000000004221046102023000157560ustar 00000000000000mod bi_hash_map; mod id_hash_map; #[cfg(feature = "std")] mod id_ord_map; #[cfg(feature = "schemars08")] mod schemars_tests; #[cfg(all( feature = "std", feature = "default-hasher", target_pointer_width = "64", not(miri) ))] mod size_tests; mod tri_hash_map; iddqd-0.3.17/tests/integration/schemars_tests.rs000064400000000000000000000120121046102023000200570ustar 00000000000000use expectorate::assert_contents; use iddqd::{ BiHashItem, BiHashMap, IdHashItem, IdHashMap, TriHashItem, TriHashMap, bi_hash_map::BiHashMapAsMap, bi_upcast, id_hash_map::IdHashMapAsMap, id_upcast, tri_hash_map::TriHashMapAsMap, tri_upcast, }; #[cfg(feature = "std")] use iddqd::{IdOrdItem, IdOrdMap, id_ord_map::IdOrdMapAsMap}; use schemars::{JsonSchema, schema_for}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, JsonSchema)] struct TestUser { // This is in alphabetical order to ensure that the same fixture gets // generated with or without the `schemars/preserve_order` feature. age: u32, email: String, id: u32, name: String, } impl IdHashItem for TestUser { type Key<'a> = &'a str; fn key(&self) -> Self::Key<'_> { &self.name } id_upcast!(); } impl BiHashItem for TestUser { type K1<'a> = &'a str; type K2<'a> = u32; fn key1(&self) -> Self::K1<'_> { &self.name } fn key2(&self) -> Self::K2<'_> { self.id } bi_upcast!(); } impl TriHashItem for TestUser { type K1<'a> = &'a str; type K2<'a> = u32; type K3<'a> = &'a str; fn key1(&self) -> Self::K1<'_> { &self.name } fn key2(&self) -> Self::K2<'_> { self.id } fn key3(&self) -> Self::K3<'_> { &self.email } tri_upcast!(); } #[cfg(feature = "std")] impl IdOrdItem for TestUser { type Key<'a> = &'a str; fn key(&self) -> Self::Key<'_> { &self.name } id_upcast!(); } #[test] fn schema_fixtures() { let schema = schema_for!(IdHashMap); assert_contents( "tests/output/id_hash_map_schema.json", &to_string_pretty_ln(&schema), ); let schema = schema_for!(IdHashMapAsMap); assert_contents( "tests/output/id_hash_map_as_map_schema.json", &to_string_pretty_ln(&schema), ); #[cfg(feature = "std")] { let schema = schema_for!(IdOrdMap); assert_contents( "tests/output/id_ord_map_schema.json", &to_string_pretty_ln(&schema), ); let schema = schema_for!(IdOrdMapAsMap); assert_contents( "tests/output/id_ord_map_as_map_schema.json", &to_string_pretty_ln(&schema), ); } let schema = schema_for!(BiHashMap); assert_contents( "tests/output/bi_hash_map_schema.json", &to_string_pretty_ln(&schema), ); let schema = schema_for!(BiHashMapAsMap); assert_contents( "tests/output/bi_hash_map_as_map_schema.json", &to_string_pretty_ln(&schema), ); let schema = schema_for!(TriHashMap); assert_contents( "tests/output/tri_hash_map_schema.json", &to_string_pretty_ln(&schema), ); let schema = schema_for!(TriHashMapAsMap); assert_contents( "tests/output/tri_hash_map_as_map_schema.json", &to_string_pretty_ln(&schema), ); } #[cfg(feature = "std")] #[test] fn container_fixtures() { #[derive(JsonSchema)] #[expect(unused)] struct Container { // This is in alphabetical order to ensure that the same fixture gets // generated with or without the `schemars/preserve_order` feature. users_bi: BiHashMap, users_hash: IdHashMap, users_ord: IdOrdMap, users_tri: TriHashMap, } // Verify the container can generate a schema. let schema = schema_for!(Container); assert_contents( "tests/output/container_schema.json", &to_string_pretty_ln(&schema), ); // A simple container with just IdHashMap. This fixture is // used by `typify-types.rs` to show end-to-end usage. #[derive(JsonSchema)] #[expect(unused)] struct SimpleContainer { users: IdHashMap, } let schema = schema_for!(SimpleContainer); assert_contents( "tests/output/simple_container_schema.json", &to_string_pretty_ln(&schema), ); // Container using the AsMap types with serde's `with` attribute. #[derive(JsonSchema)] #[expect(unused)] struct ContainerAsMap { // This is in alphabetical order to ensure that the same fixture gets // generated with or without the `schemars/preserve_order` feature. #[serde(with = "BiHashMapAsMap::")] users_bi: BiHashMap, #[serde(with = "IdHashMapAsMap::")] users_hash: IdHashMap, #[serde(with = "IdOrdMapAsMap::")] users_ord: IdOrdMap, #[serde(with = "TriHashMapAsMap::")] users_tri: TriHashMap, } let schema = schema_for!(ContainerAsMap); assert_contents( "tests/output/container_as_map_schema.json", &to_string_pretty_ln(&schema), ); } fn to_string_pretty_ln(data: &T) -> String { let mut s = serde_json::to_string_pretty(data).unwrap(); s.push('\n'); s } iddqd-0.3.17/tests/integration/size_tests.rs000064400000000000000000000034061046102023000172330ustar 00000000000000use iddqd::{BiHashMap, IdHashMap, IdOrdMap, TriHashMap}; use iddqd_test_utils::test_item::TestItem; use std::collections::hash_map::RandomState; #[test] fn test_map_sizes() { let mut output = String::new(); let id_hash_map_default_size = std::mem::size_of::>(); output.push_str(&format!( "IdHashMap: {}\n", id_hash_map_default_size )); let id_hash_map_random_size = std::mem::size_of::>(); output.push_str(&format!( "IdHashMap: {}\n", id_hash_map_random_size )); output.push('\n'); let bi_hash_map_default_size = std::mem::size_of::>(); output.push_str(&format!( "BiHashMap: {}\n", bi_hash_map_default_size )); let bi_hash_map_random_size = std::mem::size_of::>(); output.push_str(&format!( "BiHashMap: {}\n", bi_hash_map_random_size )); output.push('\n'); let tri_hash_map_default_size = std::mem::size_of::>(); output.push_str(&format!( "TriHashMap: {}\n", tri_hash_map_default_size )); let tri_hash_map_random_size = std::mem::size_of::>(); output.push_str(&format!( "TriHashMap: {}\n", tri_hash_map_random_size )); output.push('\n'); let id_ord_map_size = std::mem::size_of::>(); output.push_str(&format!("IdOrdMap: {}\n", id_ord_map_size)); expectorate::assert_contents("tests/snapshots/map_sizes.txt", &output); } iddqd-0.3.17/tests/integration/tri_hash_map.rs000064400000000000000000000760621046102023000175050ustar 00000000000000use iddqd::{ TriHashItem, TriHashMap, internal::ValidateCompact, tri_hash_map, tri_upcast, }; use iddqd_test_utils::{ borrowed_item::BorrowedItem, eq_props::{assert_eq_props, assert_ne_props}, naive_map::NaiveMap, test_item::{ Alloc, HashBuilder, ItemMap, TestItem, TestKey1, TestKey2, TestKey3, assert_iter_eq, test_item_permutation_strategy, }, }; use proptest::prelude::*; use std::{borrow::Cow, path::Path}; use test_strategy::{Arbitrary, proptest}; #[derive(Clone, Debug)] struct SimpleItem { key1: u32, key2: char, key3: u8, } impl TriHashItem for SimpleItem { type K1<'a> = u32; type K2<'a> = char; type K3<'a> = u8; fn key1(&self) -> Self::K1<'_> { self.key1 } fn key2(&self) -> Self::K2<'_> { self.key2 } fn key3(&self) -> Self::K3<'_> { self.key3 } tri_upcast!(); } #[test] fn debug_impls() { let mut map = TriHashMap::::make_new(); map.insert_unique(SimpleItem { key1: 1, key2: 'a', key3: 0 }).unwrap(); map.insert_unique(SimpleItem { key1: 20, key2: 'b', key3: 1 }).unwrap(); map.insert_unique(SimpleItem { key1: 10, key2: 'c', key3: 2 }).unwrap(); assert_eq!( format!("{map:?}"), // This is a small-enough map that the order of iteration is // deterministic. "{{k1: 1, k2: 'a', k3: 0}: SimpleItem { key1: 1, key2: 'a', key3: 0 }, \ {k1: 10, k2: 'c', k3: 2}: SimpleItem { key1: 10, key2: 'c', key3: 2 }, \ {k1: 20, k2: 'b', k3: 1}: SimpleItem { key1: 20, key2: 'b', key3: 1 }}", ); assert_eq!( format!("{:?}", map.get1_mut(&1).unwrap()), "SimpleItem { key1: 1, key2: 'a', key3: 0 }" ); } #[test] fn debug_impls_borrowed() { let before = tri_hash_map! { HashBuilder; BorrowedItem { key1: "a", key2: Cow::Borrowed(b"b0"), key3: Path::new("path0") }, BorrowedItem { key1: "b", key2: Cow::Borrowed(b"b1"), key3: Path::new("path1") }, BorrowedItem { key1: "c", key2: Cow::Borrowed(b"b2"), key3: Path::new("path2") }, }; assert_eq!( format!("{before:?}"), r#"{{k1: "a", k2: [98, 48], k3: "path0"}: BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" }, {k1: "c", k2: [98, 50], k3: "path2"}: BorrowedItem { key1: "c", key2: [98, 50], key3: "path2" }, {k1: "b", k2: [98, 49], k3: "path1"}: BorrowedItem { key1: "b", key2: [98, 49], key3: "path1" }}"# ); #[cfg(feature = "daft")] { use daft::Diffable; let after = tri_hash_map! { HashBuilder; BorrowedItem { key1: "a", key2: Cow::Borrowed(b"b0"), key3: Path::new("path0") }, BorrowedItem { key1: "c", key2: Cow::Borrowed(b"b3"), key3: Path::new("path3") }, BorrowedItem { key1: "d", key2: Cow::Borrowed(b"b4"), key3: Path::new("path4") }, }; let diff = before.diff(&after).by_unique(); assert_eq!( format!("{diff:?}"), r#"Diff { common: {{k1: "a", k2: [98, 48], k3: "path0"}: IdLeaf { before: BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" }, after: BorrowedItem { key1: "a", key2: [98, 48], key3: "path0" } }}, added: {{k1: "d", k2: [98, 52], k3: "path4"}: BorrowedItem { key1: "d", key2: [98, 52], key3: "path4" }, {k1: "c", k2: [98, 51], k3: "path3"}: BorrowedItem { key1: "c", key2: [98, 51], key3: "path3" }}, removed: {{k1: "c", k2: [98, 50], k3: "path2"}: BorrowedItem { key1: "c", key2: [98, 50], key3: "path2" }, {k1: "b", k2: [98, 49], k3: "path1"}: BorrowedItem { key1: "b", key2: [98, 49], key3: "path1" }} }"# ); } } #[test] fn test_extend() { let mut map = TriHashMap::::make_new(); let items = vec![ TestItem::new(1, 'a', "x", "v"), TestItem::new(2, 'b', "y", "w"), TestItem::new(1, 'c', "z", "overwrote key1"), TestItem::new(3, 'b', "q", "overwrote key2"), TestItem::new(4, 'd', "x", "overwrote key3"), TestItem::new(10, 'A', "X", ""), TestItem::new(20, 'B', "Y", ""), TestItem::new(30, 'A', "Y", "overwrote key2 and key3"), TestItem::new(40, 'C', "Z", ""), TestItem::new(50, 'D', "foo", "stays as is"), TestItem::new(40, 'E', "Z", "overwrote key1 and key3"), ]; map.extend(items.clone()); assert_eq!(map.len(), 6); assert_eq!(map.get1(&TestKey1::new(&1)).unwrap().value, "overwrote key1"); assert_eq!(map.get1(&TestKey1::new(&2)), None); assert_eq!(map.get1(&TestKey1::new(&3)).unwrap().value, "overwrote key2"); assert_eq!(map.get1(&TestKey1::new(&4)).unwrap().value, "overwrote key3"); assert_eq!( map.get1(&TestKey1::new(&30)).unwrap().value, "overwrote key2 and key3" ); assert_eq!( map.get1(&TestKey1::new(&40)).unwrap().value, "overwrote key1 and key3" ); assert_eq!(map.get1(&TestKey1::new(&50)).unwrap().value, "stays as is"); } #[test] fn with_capacity() { let map = TriHashMap::::with_capacity_and_hasher( 1024, HashBuilder::default(), ); assert!(map.capacity() >= 1024); } #[test] fn test_insert_unique() { let mut map = TriHashMap::::make_new(); // Add an element. let v1 = TestItem::new(0, 'a', "x", "v"); map.insert_unique(v1.clone()).unwrap(); // Add an exact duplicate, which should error out. let error = map.insert_unique(v1.clone()).unwrap_err(); assert_eq!(error.new_item(), &v1); assert_eq!(error.duplicates(), vec![&v1]); // Add a duplicate against just key1, which should error out. let v2 = TestItem::new(0, 'b', "y", "v"); let error = map.insert_unique(v2.clone()).unwrap_err(); assert_eq!(error.new_item(), &v2); assert_eq!(error.duplicates(), vec![&v1]); // Add a duplicate against just key2, which should error out. let v3 = TestItem::new(1, 'a', "y", "v"); let error = map.insert_unique(v3.clone()).unwrap_err(); assert_eq!(error.new_item(), &v3); // Add a duplicate against just key3, which should error out. let v4 = TestItem::new(1, 'b', "x", "v"); let error = map.insert_unique(v4.clone()).unwrap_err(); assert_eq!(error.new_item(), &v4); // Add an item that doesn't have any conflicts. let v5 = TestItem::new(1, 'b', "y", "v"); map.insert_unique(v5.clone()).unwrap(); // Iterate over the items mutably. This ensures that miri detects UB if it // exists. { let mut items: Vec> = map.iter_mut().collect(); items.sort_by(|a, b| a.key1().cmp(&b.key1())); let e1 = &items[0]; assert_eq!(**e1, v1); // Test that the RefMut Debug impl looks good. assert!( format!("{e1:?}").starts_with( r#"TestItem { key1: 0, key2: 'a', key3: "x", value: "v""# ), "RefMut Debug impl should forward to TestItem", ); let e2 = &*items[1]; assert_eq!(*e2, v5); } // Check that the *unique methods work. assert!(map.contains_key_unique(&v5.key1(), &v5.key2(), &v5.key3())); assert_eq!(map.get_unique(&v5.key1(), &v5.key2(), &v5.key3()), Some(&v5)); assert_eq!( *map.get_mut_unique(&v5.key1(), &v5.key2(), &v5.key3()).unwrap(), &v5 ); assert_eq!(map.remove_unique(&v5.key1(), &v5.key2(), &v5.key3()), Some(v5)); } // Example-based test for insert_overwrite. // // Can be used to write down examples seen from the property-based operation // test, for easier debugging. #[test] fn test_insert_overwrite() { let mut map = TriHashMap::::make_new(); // Add an element. let v1 = TestItem::new(20, 'a', "x", "v"); assert_eq!(map.insert_overwrite(v1.clone()), Vec::::new()); // Add an element with the same keys but a different value. let v2 = TestItem::new(20, 'a', "x", "w"); assert_eq!(map.insert_overwrite(v2.clone()), vec![v1]); map.validate(ValidateCompact::NonCompact).expect("validation failed"); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum CompactnessChange { /// The operation makes the map non-compact. NoLongerCompact, /// The operation makes the map compact. BecomesCompact, /// The operation doesn't change compactness. NoChange, } impl CompactnessChange { /// Applies this compactness change to the given compactness state. fn apply(self, compactness: ValidateCompact) -> ValidateCompact { match (compactness, self) { (ValidateCompact::Compact, CompactnessChange::NoLongerCompact) => { ValidateCompact::NonCompact } ( ValidateCompact::NonCompact, CompactnessChange::BecomesCompact, ) => ValidateCompact::Compact, _ => compactness, } } } #[derive(Debug, Arbitrary)] enum Operation { // Make inserts a bit more common to try and fill up the map. #[weight(4)] InsertUnique(TestItem), #[weight(3)] InsertOverwrite(TestItem), #[weight(2)] Get1(u8), #[weight(2)] Get2(char), #[weight(2)] Get3(String), #[weight(2)] Remove1(u8), #[weight(2)] Remove2(char), #[weight(2)] Remove3(String), #[weight(2)] RetainValueContains(char, bool), #[weight(2)] RetainModulo(#[strategy(0..3_u8)] u8, #[strategy(1..4_u8)] u8, bool), Clear, } impl Operation { fn compactness_change(&self) -> CompactnessChange { match self { Operation::InsertUnique(_) | Operation::Get1(_) | Operation::Get2(_) | Operation::Get3(_) => CompactnessChange::NoChange, // The act of removing items, including calls to insert_overwrite, // can make the map non-compact. Operation::InsertOverwrite(_) | Operation::Remove1(_) | Operation::Remove2(_) | Operation::Remove3(_) | Operation::RetainValueContains(_, _) | Operation::RetainModulo(_, _, _) => { CompactnessChange::NoLongerCompact } // Clear always makes the map compact (empty). Operation::Clear => CompactnessChange::BecomesCompact, } } } #[proptest(cases = 16)] fn proptest_ops( #[strategy(prop::collection::vec(any::(), 0..1024))] ops: Vec< Operation, >, ) { let mut map = TriHashMap::::make_new(); let mut naive_map = NaiveMap::new_key123(); let mut compactness = ValidateCompact::Compact; // Now perform the operations on both maps. for op in ops.into_iter() { compactness = op.compactness_change().apply(compactness); match op { Operation::InsertUnique(item) => { let map_res = map.insert_unique(item.clone()); let naive_res = naive_map.insert_unique(item.clone()); assert_eq!( map_res.is_ok(), naive_res.is_ok(), "map and naive map should agree on insert result" ); if let Err(map_err) = map_res { let naive_err = naive_res.unwrap_err(); assert_eq!(map_err.new_item(), naive_err.new_item()); // The duplicates may be in any order, so sort them before // comparing. let mut map_err_dups = map_err.duplicates().to_vec(); let mut naive_err_dups = naive_err.duplicates().to_vec(); map_err_dups.sort(); naive_err_dups.sort(); assert_eq!(map_err_dups, naive_err_dups); } map.validate(compactness).expect("map should be valid"); } Operation::InsertOverwrite(item) => { let mut map_dups = map.insert_overwrite(item.clone()); map_dups.sort(); let mut naive_dups = naive_map.insert_overwrite(item.clone()); naive_dups.sort(); assert_eq!( map_dups, naive_dups, "map and naive map should agree on insert_overwrite dups" ); map.validate(compactness).expect("map should be valid"); } Operation::Get1(key1) => { let map_res = map.get1(&TestKey1::new(&key1)); let naive_res = naive_map.get1(key1); assert_eq!(map_res, naive_res); } Operation::Get2(key2) => { let map_res = map.get2(&TestKey2::new(key2)); let naive_res = naive_map.get2(key2); assert_eq!(map_res, naive_res); } Operation::Get3(key3) => { let map_res = map.get3(&TestKey3::new(&key3)); let naive_res = naive_map.get3(&key3); assert_eq!(map_res, naive_res); } Operation::Remove1(key1) => { let map_res = map.remove1(&TestKey1::new(&key1)); let naive_res = naive_map.remove1(key1); assert_eq!(map_res, naive_res); map.validate(compactness).expect("map should be valid"); } Operation::Remove2(key2) => { let map_res = map.remove2(&TestKey2::new(key2)); let naive_res = naive_map.remove2(key2); assert_eq!(map_res, naive_res); map.validate(compactness).expect("map should be valid"); } Operation::Remove3(key3) => { let map_res = map.remove3(&TestKey3::new(&key3)); let naive_res = naive_map.remove3(&key3); assert_eq!(map_res, naive_res); map.validate(compactness).expect("map should be valid"); } Operation::RetainValueContains(ch, equals) => { map.retain(|item| { let contains = item.value.contains(ch); if equals { contains } else { !contains } }); naive_map.retain(|item| { let contains = item.value.contains(ch); if equals { contains } else { !contains } }); map.validate(compactness).expect("map should be valid"); } Operation::RetainModulo(a, b, equals) => { let modulo = a + b; let remainder = a; map.retain(|item| { let matches = item.key1 % modulo == remainder; if equals { matches } else { !matches } }); naive_map.retain(|item| { let matches = item.key1 % modulo == remainder; if equals { matches } else { !matches } }); map.validate(compactness).expect("map should be valid"); } Operation::Clear => { map.clear(); naive_map.clear(); map.validate(compactness).expect("map should be valid"); } } // Check that the iterators work correctly. let mut naive_items = naive_map.iter().collect::>(); naive_items.sort_by(|a, b| a.key1().cmp(&b.key1())); assert_iter_eq(map.clone(), naive_items); } } #[proptest(cases = 64)] fn proptest_permutation_eq( #[strategy(test_item_permutation_strategy::>(0..256))] items: (Vec, Vec), ) { let (items1, items2) = items; let mut map1 = TriHashMap::::make_new(); let mut map2 = TriHashMap::::make_new(); for item in items1 { map1.insert_unique(item.clone()).unwrap(); } for item in items2 { map2.insert_unique(item.clone()).unwrap(); } assert_eq_props(map1, map2); } // Test various conditions for non-equality. // // It's a bit difficult to capture mutations in a proptest, so this is a small // example-based test. #[test] fn test_permutation_eq_examples() { let mut map1 = TriHashMap::::make_new(); let mut map2 = TriHashMap::::make_new(); // Two empty maps are equal. assert_eq!(map1, map2); // Insert a single item into one map. let item = TestItem::new(0, 'a', "x", "v"); map1.insert_unique(item.clone()).unwrap(); // The maps are not equal. assert_ne_props(&map1, &map2); // Insert the same item into the other map. map2.insert_unique(item.clone()).unwrap(); // The maps are now equal. assert_eq_props(&map1, &map2); { // Insert an item with the same key2 and key3 but a different // key1. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(2, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item with the same key1 and key3 but a different // key2. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'c', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item with the same key1 and key2 but a different // key3. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "v")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'b', "z", "v")).unwrap(); assert_ne_props(&map1, &map2); } { // Insert an item where all the keys are the same, but the value is // different. let mut map1 = map1.clone(); map1.insert_unique(TestItem::new(1, 'b', "y", "w")).unwrap(); assert_ne_props(&map1, &map2); let mut map2 = map2.clone(); map2.insert_unique(TestItem::new(1, 'b', "y", "x")).unwrap(); assert_ne_props(&map1, &map2); } } #[test] #[should_panic(expected = "key1 changed during RefMut borrow")] fn get_mut_panics_if_key1_changes() { let mut map = TriHashMap::::make_new(); map.insert_unique(TestItem::new(128, 'b', "y", "x")).unwrap(); map.get1_mut(&TestKey1::new(&128)).unwrap().key1 = 2; } #[test] #[should_panic(expected = "key2 changed during RefMut borrow")] fn get_mut_panics_if_key2_changes() { let mut map = TriHashMap::::make_new(); map.insert_unique(TestItem::new(128, 'b', "y", "x")).unwrap(); map.get1_mut(&TestKey1::new(&128)).unwrap().key2 = 'c'; } #[test] #[should_panic(expected = "key3 changed during RefMut borrow")] fn get_mut_panics_if_key3_changes() { let mut map = TriHashMap::::make_new(); map.insert_unique(TestItem::new(128, 'b', "y", "x")).unwrap(); map.get1_mut(&TestKey1::new(&128)).unwrap().key3 = "z".to_owned(); } #[test] fn borrowed_item() { let mut map = TriHashMap::::default(); let item1 = BorrowedItem { key1: "foo", key2: Cow::Borrowed(b"foo"), key3: Path::new("foo"), }; let item2 = BorrowedItem { key1: "bar", key2: Cow::Borrowed(b"bar"), key3: Path::new("bar"), }; // Insert items. map.insert_unique(item1.clone()).unwrap(); map.insert_unique(item2.clone()).unwrap(); // Check that we can retrieve them. assert_eq!(map.get1("foo").unwrap().key1, "foo"); assert_eq!(map.get1("bar").unwrap().key1, "bar"); // Check that we can iterate over them. let keys: Vec<_> = map.iter().map(|item| item.key1()).collect(); assert_eq!(keys, vec!["foo", "bar"]); // Check that we can print a Debug representation, even within a function // (supporting this requires a little bit of unsafe code to get the // lifetimes to line up). fn fmt_debug( map: &TriHashMap, HashBuilder, Alloc>, ) -> String { format!("{map:?}") } #[cfg(feature = "serde")] fn serialize_as_map( map: &TriHashMap, HashBuilder, Alloc>, ) -> Result { let mut out: Vec = Vec::new(); let mut ser = iddqd_test_utils::serde_json::Serializer::new(&mut out); tri_hash_map::TriHashMapAsMap::serialize(map, &mut ser)?; Ok(String::from_utf8(out) .expect("serde_json should always emit valid UTF-8")) } static DEBUG_OUTPUT: &str = "{{k1: \"foo\", k2: [102, 111, 111], k3: \"foo\"}: BorrowedItem { \ key1: \"foo\", key2: [102, 111, 111], key3: \"foo\" }, \ {k1: \"bar\", k2: [98, 97, 114], k3: \"bar\"}: BorrowedItem { \ key1: \"bar\", key2: [98, 97, 114], key3: \"bar\" }}"; assert_eq!(format!("{map:?}"), DEBUG_OUTPUT); assert_eq!(fmt_debug(&map), DEBUG_OUTPUT); #[cfg(feature = "serde")] { let map_string = serialize_as_map(&map).unwrap(); let deserialized: TriHashMap, HashBuilder, Alloc> = iddqd_test_utils::serde_json::from_str(&map_string).unwrap(); assert_eq!(map, deserialized); } } #[test] fn test_retain_all() { let mut map = TriHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); let original_len = map.len(); map.retain(|_| true); assert_eq!(map.len(), original_len); assert_eq!(map.len(), 3); map.get1(&TestKey1::new(&1)).expect("key1=1 should be present"); map.get1(&TestKey1::new(&2)).expect("key1=2 should be present"); map.get1(&TestKey1::new(&3)).expect("key1=3 should be present"); } #[test] fn test_retain_none() { let mut map = TriHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); map.retain(|_| false); assert_eq!(map.len(), 0); assert!(map.is_empty()); } #[test] fn test_retain_value_contains() { let mut map = TriHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); map.insert_unique(TestItem::new(4, 'd', "w", "qux")).unwrap(); map.retain(|item| item.value.contains('a')); assert_eq!(map.len(), 2); map.get1(&TestKey1::new(&2)).expect("key1=2 (bar) should be present"); map.get1(&TestKey1::new(&3)).expect("key1=3 (baz) should be present"); assert!( map.get1(&TestKey1::new(&1)).is_none(), "key1=1 (foo) should be removed" ); assert!( map.get1(&TestKey1::new(&4)).is_none(), "key1=4 (qux) should be removed" ); } #[test] fn test_retain_modulo() { let mut map = TriHashMap::::make_new(); map.insert_unique(TestItem::new(0, 'a', "x", "v0")).unwrap(); map.insert_unique(TestItem::new(1, 'b', "y", "v1")).unwrap(); map.insert_unique(TestItem::new(2, 'c', "z", "v2")).unwrap(); map.insert_unique(TestItem::new(3, 'd', "w", "v3")).unwrap(); map.insert_unique(TestItem::new(4, 'e', "u", "v4")).unwrap(); map.insert_unique(TestItem::new(5, 'f', "t", "v5")).unwrap(); map.retain(|item| item.key1 % 3 == 1); assert_eq!(map.len(), 2); map.get1(&TestKey1::new(&1)).expect("key1=1 should be present"); map.get1(&TestKey1::new(&4)).expect("key1=4 should be present"); assert!(map.get1(&TestKey1::new(&0)).is_none(), "key1=0 should be removed"); assert!(map.get1(&TestKey1::new(&2)).is_none(), "key1=2 should be removed"); assert!(map.get1(&TestKey1::new(&3)).is_none(), "key1=3 should be removed"); assert!(map.get1(&TestKey1::new(&5)).is_none(), "key1=5 should be removed"); // Test with a larger map for miri coverage. let mut large_map = TriHashMap::::make_new(); for i in 0..32_u8 { large_map .insert_unique(TestItem::new( i, char::from(b'a' + i), format!("k{}", i), "z", )) .unwrap(); } large_map.retain(|item| item.key1 % 7 == 3); for i in 0..32_u8 { if i % 7 == 3 { large_map .get1(&TestKey1::new(&i)) .unwrap_or_else(|| panic!("key1={} should be present", i)); } else { assert!( large_map.get1(&TestKey1::new(&i)).is_none(), "key1={} should be removed", i ); } } } #[test] fn test_retain_empty_map() { let mut map = TriHashMap::::make_new(); map.retain(|_| true); assert!(map.is_empty()); } #[test] fn test_clear_empty_map() { let mut map = TriHashMap::::make_new(); map.clear(); assert!(map.is_empty()); map.validate(ValidateCompact::Compact) .expect("empty cleared map should be compact"); } #[test] fn test_clear_makes_compact() { let mut map = TriHashMap::::make_new(); // Add items map.insert_unique(TestItem::new(1, 'a', "x", "v1")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "v2")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "v3")).unwrap(); // Remove an item to make it non-compact map.remove1(&TestKey1::new(&2)); map.validate(ValidateCompact::NonCompact) .expect("map should be valid but non-compact"); // Clear should make it compact again map.clear(); assert!(map.is_empty()); map.validate(ValidateCompact::Compact) .expect("cleared map should be compact"); } #[test] fn test_retain_verifies_all_keys() { let mut map = TriHashMap::::make_new(); map.insert_unique(TestItem::new(1, 'a', "x", "foo")).unwrap(); map.insert_unique(TestItem::new(2, 'b', "y", "bar")).unwrap(); map.insert_unique(TestItem::new(3, 'c', "z", "baz")).unwrap(); // Retain only key1=2 map.retain(|item| item.key1 == 2); // Verify all three keys work map.get1(&TestKey1::new(&2)).expect("key1=2 should be present"); map.get2(&TestKey2::new('b')).expect("key2='b' should be present"); map.get3(&TestKey3::new("y")).expect("key3=\"y\" should be present"); assert!(map.get1(&TestKey1::new(&1)).is_none()); assert!(map.get2(&TestKey2::new('a')).is_none()); assert!(map.get3(&TestKey3::new("x")).is_none()); } mod macro_tests { use super::*; #[derive(Debug, PartialEq)] struct Person { id: u32, name: String, email: String, } impl TriHashItem for Person { type K1<'a> = u32; type K2<'a> = &'a str; type K3<'a> = &'a str; fn key1(&self) -> Self::K1<'_> { self.id } fn key2(&self) -> Self::K2<'_> { &self.name } fn key3(&self) -> Self::K3<'_> { &self.email } tri_upcast!(); } #[cfg(feature = "default-hasher")] #[test] fn macro_basic() { let map = tri_hash_map! { Person { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() }, Person { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() }, }; assert_eq!(map.len(), 2); assert_eq!(map.get1(&1).unwrap().name, "Alice"); assert_eq!(map.get2("Bob").unwrap().id, 2); assert_eq!(map.get3("alice@example.com").unwrap().name, "Alice"); } #[test] fn macro_with_hasher() { let map = tri_hash_map! { HashBuilder; Person { id: 3, name: "Charlie".to_string(), email: "charlie@example.com".to_string() }, Person { id: 4, name: "David".to_string(), email: "david@example.com".to_string() }, }; assert_eq!(map.len(), 2); assert_eq!(map.get1(&3).unwrap().name, "Charlie"); assert_eq!(map.get2("David").unwrap().id, 4); assert_eq!(map.get3("charlie@example.com").unwrap().name, "Charlie"); } #[cfg(feature = "default-hasher")] #[test] fn macro_empty() { let empty_map: TriHashMap = tri_hash_map! {}; assert!(empty_map.is_empty()); } #[cfg(feature = "default-hasher")] #[test] fn macro_without_trailing_comma() { let map = tri_hash_map! { Person { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() } }; assert_eq!(map.len(), 1); } #[cfg(feature = "default-hasher")] #[test] #[should_panic(expected = "DuplicateItem")] fn macro_duplicate_key1() { let _map = tri_hash_map! { Person { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() }, Person { id: 1, name: "Bob".to_string(), email: "bob@example.com".to_string() }, }; } #[cfg(feature = "default-hasher")] #[test] #[should_panic(expected = "DuplicateItem")] fn macro_duplicate_key2() { let _map = tri_hash_map! { Person { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() }, Person { id: 2, name: "Alice".to_string(), email: "alice2@example.com".to_string() }, }; } #[cfg(feature = "default-hasher")] #[test] #[should_panic(expected = "DuplicateItem")] fn macro_duplicate_key3() { let _map = tri_hash_map! { Person { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() }, Person { id: 2, name: "Bob".to_string(), email: "alice@example.com".to_string() }, }; } } #[cfg(feature = "serde")] mod serde_tests { use iddqd::TriHashMap; use iddqd_test_utils::{ serde_utils::assert_serialize_roundtrip, test_item::{Alloc, HashBuilder, TestItem}, }; use test_strategy::proptest; #[proptest] fn proptest_serialize_roundtrip(values: Vec) { assert_serialize_roundtrip::>( values, ); } } #[cfg(feature = "proptest")] #[proptest(cases = 16)] fn proptest_arbitrary_map(map: TriHashMap) { // Test that the arbitrarily generated map is valid. map.validate(ValidateCompact::NonCompact).expect("map should be valid"); // Test that we can perform basic operations on the generated map. let len = map.len(); assert_eq!(map.is_empty(), len == 0); // Test that we can iterate over the map. let mut count = 0; for item in &map { count += 1; // Each item should be findable by all three keys. assert_eq!(map.get1(&item.key1()), Some(item)); assert_eq!(map.get2(&item.key2()), Some(item)); assert_eq!(map.get3(&item.key3()), Some(item)); } assert_eq!(count, len); } iddqd-0.3.17/tests/output/bi_hash_map_as_map_schema.json000064400000000000000000000015661046102023000215000ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "BiHashMapAsMap", "type": "object", "additionalProperties": { "$ref": "#/definitions/TestUser" }, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::BiHashMap", "version": "*" }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/output/bi_hash_map_schema.json000064400000000000000000000015701046102023000201530ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "BiHashMap", "type": "array", "items": { "$ref": "#/definitions/TestUser" }, "uniqueItems": true, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::BiHashMap", "version": "*" }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/output/container_as_map_schema.json000064400000000000000000000043551046102023000212270ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ContainerAsMap", "type": "object", "required": [ "users_bi", "users_hash", "users_ord", "users_tri" ], "properties": { "users_bi": { "title": "BiHashMapAsMap", "type": "object", "additionalProperties": { "$ref": "#/definitions/TestUser" }, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::BiHashMap", "version": "*" } }, "users_hash": { "title": "IdHashMapAsMap", "type": "object", "additionalProperties": { "$ref": "#/definitions/TestUser" }, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::IdHashMap", "version": "*" } }, "users_ord": { "title": "IdOrdMapAsMap", "type": "object", "additionalProperties": { "$ref": "#/definitions/TestUser" }, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::IdOrdMap", "version": "*" } }, "users_tri": { "title": "TriHashMapAsMap", "type": "object", "additionalProperties": { "$ref": "#/definitions/TestUser" }, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::TriHashMap", "version": "*" } } }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/output/container_schema.json000064400000000000000000000044001046102023000176760ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Container", "type": "object", "required": [ "users_bi", "users_hash", "users_ord", "users_tri" ], "properties": { "users_bi": { "title": "BiHashMap", "type": "array", "items": { "$ref": "#/definitions/TestUser" }, "uniqueItems": true, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::BiHashMap", "version": "*" } }, "users_hash": { "title": "IdHashMap", "type": "array", "items": { "$ref": "#/definitions/TestUser" }, "uniqueItems": true, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::IdHashMap", "version": "*" } }, "users_ord": { "title": "IdOrdMap", "type": "array", "items": { "$ref": "#/definitions/TestUser" }, "uniqueItems": true, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::IdOrdMap", "version": "*" } }, "users_tri": { "title": "TriHashMap", "type": "array", "items": { "$ref": "#/definitions/TestUser" }, "uniqueItems": true, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::TriHashMap", "version": "*" } } }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/output/id_hash_map_as_map_schema.json000064400000000000000000000015661046102023000215020ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "IdHashMapAsMap", "type": "object", "additionalProperties": { "$ref": "#/definitions/TestUser" }, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::IdHashMap", "version": "*" }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/output/id_hash_map_schema.json000064400000000000000000000015701046102023000201550ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "IdHashMap", "type": "array", "items": { "$ref": "#/definitions/TestUser" }, "uniqueItems": true, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::IdHashMap", "version": "*" }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/output/id_ord_map_as_map_schema.json000064400000000000000000000015641046102023000213410ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "IdOrdMapAsMap", "type": "object", "additionalProperties": { "$ref": "#/definitions/TestUser" }, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::IdOrdMap", "version": "*" }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/output/id_ord_map_schema.json000064400000000000000000000015661046102023000200230ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "IdOrdMap", "type": "array", "items": { "$ref": "#/definitions/TestUser" }, "uniqueItems": true, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::IdOrdMap", "version": "*" }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/output/simple_container_schema.json000064400000000000000000000020661046102023000212550ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "SimpleContainer", "type": "object", "required": [ "users" ], "properties": { "users": { "title": "IdHashMap", "type": "array", "items": { "$ref": "#/definitions/TestUser" }, "uniqueItems": true, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::IdHashMap", "version": "*" } } }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/output/tri_hash_map_as_map_schema.json000064400000000000000000000015701046102023000216770ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "TriHashMapAsMap", "type": "object", "additionalProperties": { "$ref": "#/definitions/TestUser" }, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::TriHashMap", "version": "*" }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/output/tri_hash_map_schema.json000064400000000000000000000015721046102023000203610ustar 00000000000000{ "$schema": "http://json-schema.org/draft-07/schema#", "title": "TriHashMap", "type": "array", "items": { "$ref": "#/definitions/TestUser" }, "uniqueItems": true, "x-rust-type": { "crate": "iddqd", "parameters": [ { "$ref": "#/definitions/TestUser" } ], "path": "iddqd::TriHashMap", "version": "*" }, "definitions": { "TestUser": { "type": "object", "required": [ "age", "email", "id", "name" ], "properties": { "age": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "email": { "type": "string" }, "id": { "type": "integer", "format": "uint32", "minimum": 0.0 }, "name": { "type": "string" } } } } } iddqd-0.3.17/tests/snapshots/map_sizes.txt000064400000000000000000000004231046102023000167170ustar 00000000000000IdHashMap: 80 IdHashMap: 88 BiHashMap: 112 BiHashMap: 120 TriHashMap: 144 TriHashMap: 152 IdOrdMap: 72