├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── README.tpl ├── benchmark ├── Cargo.toml ├── benches │ └── primary.rs └── src │ └── lib.rs ├── comparison ├── Cargo.toml └── src │ └── main.rs └── src ├── arena.rs ├── free_pointer.rs ├── generation.rs ├── iter ├── drain.rs ├── into_iter.rs ├── iter.rs ├── iter_mut.rs └── mod.rs └── lib.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | rust_version: [stable, "1.47.0"] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Setup Rust toolchain 25 | run: rustup default ${{ matrix.rust_version }} 26 | 27 | - name: Build 28 | run: cargo build --verbose 29 | 30 | - name: Build (no_std) 31 | run: cargo build --no-default-features --verbose 32 | 33 | - name: Run tests 34 | run: cargo test --verbose 35 | 36 | - name: Rustfmt and Clippy 37 | run: | 38 | cargo fmt -- --check 39 | cargo clippy --all-features 40 | if: matrix.rust_version == 'stable' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Thunderdome Changelog 2 | 3 | ## Unreleased Changes 4 | 5 | ## [0.6.1] - 2023-06-24 6 | * Added `Index::DANGLING`. 7 | * Replaced unsafe code in `get2_mut` with safe equivalent. ([#42]) 8 | 9 | [#42]: https://github.com/LPGhatguy/thunderdome/pull/42 10 | [0.6.1]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.6.1 11 | 12 | ## [0.6.0] - 2022-10-18 13 | * Raised MSRV to 1.47.0. 14 | * Many functions are now `const` ([#38]) 15 | 16 | [#38]: https://github.com/LPGhatguy/thunderdome/pull/38 17 | [0.6.0]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.6.0 18 | 19 | ## [0.5.1] - 2022-07-04 20 | * Fixed bug when calling `Arena::insert_at` on a slot in the middle of the free list. ([#36]) 21 | * Added `Index::generation` for extracting the generation portion of an index. ([#34]) 22 | 23 | [#34]: https://github.com/LPGhatguy/thunderdome/issues/34 24 | [#36]: https://github.com/LPGhatguy/thunderdome/issues/36 25 | [0.5.1]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.5.1 26 | 27 | ## [0.5.0] - 2021-10-07 28 | * Moved iterator types into `thunderdome::iter`. ([#24]) 29 | * Changed `Index::from_bits` to return `Option` instead of `Index`, and no longer panic. ([#31]) 30 | 31 | [#24]: https://github.com/LPGhatguy/thunderdome/issues/24 32 | [#31]: https://github.com/LPGhatguy/thunderdome/issues/31 33 | [0.5.0]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.5.0 34 | 35 | ## [0.4.2] - 2021-10-07 36 | * Fixed miri warning for `Arena::get2_mut`. ([#29]) 37 | * Added `Arena::insert_at` and `Arena::insert_at_slot` for inserting into specific indexes or slots. ([#30]) 38 | 39 | [#29]: https://github.com/LPGhatguy/thunderdome/pull/29 40 | [#30]: https://github.com/LPGhatguy/thunderdome/pull/30 41 | [0.4.2]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.4.2 42 | 43 | ## [0.4.1] - 2021-02-24 44 | * Implemented `IntoIterator` for `&Arena` and `&mut Arena`. ([#18]) 45 | * Added `Arena::get2_mut` for getting two mutable references of different slots at once. ([#22]) 46 | 47 | [#18]: https://github.com/LPGhatguy/thunderdome/pull/18 48 | [#22]: https://github.com/LPGhatguy/thunderdome/pull/22 49 | [0.4.1]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.4.1 50 | 51 | ## [0.4.0] - 2020-11-17 52 | * Fixed `Arena::iter_mut` to return mutable references. ([#10]) 53 | * Added `Arena::retain` for conveniently removing entries which do not satisfy a given predicate. ([#11]) 54 | * Added `Arena::contains` for checking whether an `Index` is valid for a given `Arena`. ([#12]) 55 | * Added `Index::slot` for extracting the slot portion of an index as well as slot-related APIs. ([#13]) 56 | * Added `Arena::contains_slot` for checking whether a slot is occupied in a given `Arena` and resolving its `Index` if so. 57 | * Added `Arena::get_by_slot` and `Arena::get_by_slot_mut` for retrieving an entry by its slot, ignoring generation. 58 | * Added `Arena::remove_by_slot` for removing an entry by its slot, ignoring generation. 59 | 60 | [#10]: https://github.com/LPGhatguy/thunderdome/pull/10 61 | [#11]: https://github.com/LPGhatguy/thunderdome/pull/11 62 | [#12]: https://github.com/LPGhatguy/thunderdome/pull/12 63 | [#13]: https://github.com/LPGhatguy/thunderdome/pull/13 64 | [0.4.0]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.4.0 65 | 66 | ## [0.3.0] - 2020-10-16 67 | * Added `Arena::invalidate` for invalidating indices on-demand, as a faster remove-followed-by-reinsert. ([#6]) 68 | * Added `Index::to_bits` and `Index::from_bits` for converting indices to a form convenient for passing outside of Rust. ([#6]) 69 | * Added `Arena::clear` for conveniently clearing the whole arena. ([#7]) 70 | * Change the semantics of `Arena::drain` to drop any remaining uniterated items when the `Drain` iterator is dropped, clearing the `Arena`. ([#8]) 71 | 72 | [#6]: https://github.com/LPGhatguy/thunderdome/pull/6 73 | [#7]: https://github.com/LPGhatguy/thunderdome/pull/7 74 | [#8]: https://github.com/LPGhatguy/thunderdome/pull/8 75 | [0.3.0]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.3.0 76 | 77 | ## [0.2.1] - 2020-10-01 78 | * Added `Default` implementation for `Arena`. 79 | * Added `IntoIterator` implementation for `Arena` ([#1](https://github.com/LPGhatguy/thunderdome/issues/1)) 80 | * Added `Arena::iter` and `Arena::iter_mut` ([#2](https://github.com/LPGhatguy/thunderdome/issues/2)) 81 | 82 | [0.2.1]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.2.1 83 | 84 | ## [0.2.0] - 2020-09-03 85 | * Bumped MSRV to 1.34.1. 86 | * Reduced size of `Index` by limiting `Arena` to 2^32 elements and 2^32 generations per slot. 87 | * These limits should not be hit in practice, but will consistently trigger panics. 88 | * Changed generation counter to wrap instead of panic on overflow. 89 | * Collisions where an index using the same slot and a colliding generation on [1, 2^32] should be incredibly unlikely. 90 | 91 | [0.2.0]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.2.0 92 | 93 | ## [0.1.1] - 2020-09-02 94 | * Added `Arena::with_capacity` for preallocating space. 95 | * Added `Arena::len`, `Arena::capacity`, and `Arena::is_empty`. 96 | * Improved panic-on-wrap guarantees, especially around unsafe code. 97 | * Simplified and documented implementation. 98 | 99 | [0.1.1]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.1.1 100 | 101 | ## [0.1.0] - 2020-09-02 102 | * Initial release 103 | 104 | [0.1.0]: https://github.com/LPGhatguy/thunderdome/releases/tag/v0.1.0 105 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thunderdome" 3 | description = "Fast arena allocator with compact generational indices" 4 | version = "0.6.1" 5 | authors = ["Lucien Greathouse "] 6 | edition = "2018" 7 | documentation = "https://docs.rs/thunderdome" 8 | homepage = "https://github.com/LPGhatguy/thunderdome" 9 | repository = "https://github.com/LPGhatguy/thunderdome" 10 | readme = "README.md" 11 | keywords = ["arena", "slab", "generational"] 12 | license = "MIT OR Apache-2.0" 13 | 14 | [features] 15 | default = ["std"] 16 | std = [] 17 | 18 | [workspace] 19 | members = ["benchmark", "comparison"] -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Lucien Greathouse 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thunderdome 2 | 3 | [![GitHub CI Status](https://github.com/LPGhatguy/thunderdome/workflows/CI/badge.svg)](https://github.com/LPGhatguy/thunderdome/actions) 4 | [![thunderdome on crates.io](https://img.shields.io/crates/v/thunderdome.svg)](https://crates.io/crates/thunderdome) 5 | [![thunderdome docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/thunderdome) 6 | 7 | Thunderdome is a ~~gladitorial~~ generational arena inspired by 8 | [generational-arena](https://crates.io/crates/generational-arena), 9 | [slotmap](https://crates.io/crates/slotmap), and 10 | [slab](https://crates.io/crates/slab). It provides constant time insertion, 11 | lookup, and removal via small (8 byte) keys returned from [`Arena`]. 12 | 13 | Thunderdome's key type, [`Index`], is still 8 bytes when put inside of an 14 | `Option` thanks to Rust's `NonZero*` types. 15 | 16 | ## Basic Examples 17 | 18 | ```rust 19 | let mut arena = Arena::new(); 20 | 21 | let foo = arena.insert("Foo"); 22 | let bar = arena.insert("Bar"); 23 | 24 | assert_eq!(arena[foo], "Foo"); 25 | assert_eq!(arena[bar], "Bar"); 26 | 27 | arena[bar] = "Replaced"; 28 | assert_eq!(arena[bar], "Replaced"); 29 | 30 | let foo_value = arena.remove(foo); 31 | assert_eq!(foo_value, Some("Foo")); 32 | 33 | // The slot previously used by foo will be reused for baz 34 | let baz = arena.insert("Baz"); 35 | assert_eq!(arena[baz], "Baz"); 36 | 37 | // foo is no longer a valid key 38 | assert_eq!(arena.get(foo), None); 39 | ``` 40 | 41 | ## Comparison With Similar Crates 42 | 43 | | Feature | Thunderdome | generational-arena | slotmap | slab | 44 | |------------------------------|-------------|--------------------|---------|------| 45 | | Generational Indices¹ | Yes | Yes | Yes | No | 46 | | `size_of::()` | 8 | 16 | 8 | 8 | 47 | | `size_of::>()` | 8 | 24 | 8 | 16 | 48 | | Max Elements | 2³² | 2⁶⁴ | 2³² | 2⁶⁴ | 49 | | Non-`Copy` Values | Yes | Yes | Yes | Yes | 50 | | `no_std` Support | Yes | Yes | Yes | No | 51 | | Serde Support | No | Yes | Yes | No | 52 | 53 | * Sizes calculated on rustc `1.44.0-x86_64-pc-windows-msvc` 54 | * See [the Thunderdome comparison 55 | Cargo.toml](https://github.com/LPGhatguy/thunderdome/blob/main/comparison/Cargo.toml) 56 | for versions of each library tested. 57 | 58 | 1. Generational indices help solve the [ABA 59 | Problem](https://en.wikipedia.org/wiki/ABA_problem), which can cause dangling 60 | keys to mistakenly access newly-inserted data. 61 | 62 | ## Minimum Supported Rust Version (MSRV) 63 | 64 | Thunderdome supports Rust 1.47.0 and newer. Until Thunderdome reaches 1.0, 65 | changes to the MSRV will require major version bumps. After 1.0, MSRV changes 66 | will only require minor version bumps, but will need significant justification. 67 | 68 | ## Crate Features 69 | * `std` (default): Use the standard library. Disable to make this crate `no-std` compatible. 70 | 71 | [`Arena`]: https://docs.rs/thunderdome/latest/thunderdome/struct.Arena.html 72 | [`Index`]: https://docs.rs/thunderdome/latest/thunderdome/struct.Index.html 73 | 74 | ## License 75 | 76 | Licensed under either of 77 | 78 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 79 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 80 | 81 | at your option. 82 | 83 | ### Contribution 84 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 85 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | # Thunderdome 2 | 3 | {{readme}} 4 | 5 | [`Arena`]: https://docs.rs/thunderdome/latest/thunderdome/struct.Arena.html 6 | [`Index`]: https://docs.rs/thunderdome/latest/thunderdome/struct.Index.html 7 | 8 | ## License 9 | 10 | Licensed under either of 11 | 12 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 13 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 14 | 15 | at your option. 16 | 17 | ### Contribution 18 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -------------------------------------------------------------------------------- /benchmark/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benchmark" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | thunderdome = { path = ".." } 10 | 11 | criterion = "0.3.6" 12 | rand = "0.8.5" 13 | 14 | [[bench]] 15 | name = "primary" 16 | harness = false -------------------------------------------------------------------------------- /benchmark/benches/primary.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; 2 | use rand::{seq::SliceRandom, thread_rng}; 3 | 4 | use thunderdome::Arena; 5 | 6 | pub fn iter(c: &mut Criterion) { 7 | let mut arena = Arena::new(); 8 | for i in 0..10_000 { 9 | arena.insert(i); 10 | } 11 | 12 | c.bench_function("iter 10k", |b| { 13 | b.iter(|| { 14 | for kv in arena.iter() { 15 | black_box(kv); 16 | } 17 | }) 18 | }); 19 | } 20 | 21 | pub fn insert(c: &mut Criterion) { 22 | let arena: Arena = Arena::new(); 23 | 24 | c.bench_function("insert 10k", |b| { 25 | b.iter_batched_ref( 26 | || arena.clone(), 27 | |arena| { 28 | for i in 0..10_000 { 29 | arena.insert(i); 30 | } 31 | }, 32 | BatchSize::SmallInput, 33 | ) 34 | }); 35 | } 36 | 37 | pub fn get_random(c: &mut Criterion) { 38 | let mut arena: Arena = Arena::new(); 39 | 40 | let mut keys = Vec::new(); 41 | for i in 0..10_000 { 42 | keys.push(arena.insert(i)); 43 | } 44 | keys.shuffle(&mut thread_rng()); 45 | 46 | c.bench_function("get_random 10k", |b| { 47 | b.iter(|| { 48 | for &k in &keys { 49 | black_box(arena.get(k)); 50 | } 51 | }) 52 | }); 53 | } 54 | 55 | pub fn remove_random(c: &mut Criterion) { 56 | let mut arena: Arena = Arena::new(); 57 | 58 | let mut keys = Vec::new(); 59 | for i in 0..10_000 { 60 | keys.push(arena.insert(i)); 61 | } 62 | keys.shuffle(&mut thread_rng()); 63 | 64 | c.bench_function("remove_random 10k", |b| { 65 | b.iter_batched_ref( 66 | || arena.clone(), 67 | |arena| { 68 | for &k in &keys { 69 | black_box(arena.remove(k)); 70 | } 71 | }, 72 | BatchSize::SmallInput, 73 | ) 74 | }); 75 | } 76 | 77 | pub fn reinsert_random(c: &mut Criterion) { 78 | let mut arena: Arena = Arena::new(); 79 | 80 | let mut keys = Vec::new(); 81 | for i in 0..10_000 { 82 | keys.push(arena.insert(i)); 83 | } 84 | 85 | keys.shuffle(&mut thread_rng()); 86 | 87 | for key in keys { 88 | arena.remove(key); 89 | } 90 | 91 | c.bench_function("reinsert_random 10k", |b| { 92 | b.iter_batched_ref( 93 | || arena.clone(), 94 | |arena| { 95 | for i in 0..10_000 { 96 | black_box(arena.insert(i)); 97 | } 98 | }, 99 | BatchSize::SmallInput, 100 | ) 101 | }); 102 | } 103 | 104 | criterion_group!( 105 | benches, 106 | iter, 107 | insert, 108 | get_random, 109 | remove_random, 110 | reinsert_random 111 | ); 112 | criterion_main!(benches); 113 | -------------------------------------------------------------------------------- /benchmark/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /comparison/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thunderdome-comparison" 3 | description = "Compare properties of Thunderdome to related crates" 4 | publish = false 5 | version = "0.1.0" 6 | authors = ["Lucien Greathouse "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | thunderdome = { path = ".." } 11 | generational-arena = "0.2.8" 12 | slotmap = "0.4.0" 13 | slab = "0.4.2" 14 | -------------------------------------------------------------------------------- /comparison/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | 3 | fn show_thunderdome() { 4 | use thunderdome::Index; 5 | 6 | println!("thunderdome"); 7 | println!("Size of Index: {}", size_of::()); 8 | println!("Size of Option: {}", size_of::>()); 9 | } 10 | 11 | fn show_generational_arena() { 12 | use generational_arena::Index; 13 | 14 | println!("generational-arena"); 15 | println!("Size of Index: {}", size_of::()); 16 | println!("Size of Option: {}", size_of::>()); 17 | } 18 | 19 | fn show_slotmap() { 20 | use slotmap::DefaultKey; 21 | 22 | println!("slotmap"); 23 | println!("Size of DefaultKey: {}", size_of::()); 24 | println!( 25 | "Size of Option: {}", 26 | size_of::>() 27 | ); 28 | } 29 | 30 | fn show_slab() { 31 | println!("slab"); 32 | println!("Size of usize: {}", size_of::()); 33 | println!("Size of Option: {}", size_of::>()); 34 | } 35 | 36 | fn main() { 37 | show_thunderdome(); 38 | println!(); 39 | 40 | show_generational_arena(); 41 | println!(); 42 | 43 | show_slotmap(); 44 | println!(); 45 | 46 | show_slab(); 47 | } 48 | -------------------------------------------------------------------------------- /src/arena.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryInto; 2 | use core::mem::replace; 3 | use core::ops; 4 | 5 | // Vec is part of the prelude when std is enabled. 6 | #[cfg(not(feature = "std"))] 7 | use alloc::vec::Vec; 8 | 9 | use crate::free_pointer::FreePointer; 10 | use crate::generation::Generation; 11 | use crate::iter::{Drain, IntoIter, Iter, IterMut}; 12 | 13 | /// Container that can have elements inserted into it and removed from it. 14 | /// 15 | /// Indices use the [`Index`] type, created by inserting values with [`Arena::insert`]. 16 | #[derive(Debug, Clone)] 17 | pub struct Arena { 18 | storage: Vec>, 19 | len: u32, 20 | first_free: Option, 21 | } 22 | 23 | /// Index type for [`Arena`] that has a generation attached to it. 24 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] 25 | pub struct Index { 26 | pub(crate) slot: u32, 27 | pub(crate) generation: Generation, 28 | } 29 | 30 | impl Index { 31 | /// Represents an `Index` that is unlikely to be in use. This is useful for 32 | /// programs that want to do two-phase initialization in safe Rust. Avoid 33 | /// using this value to represent the absence of an `Index`: prefer 34 | /// `Option`. 35 | pub const DANGLING: Self = Self { 36 | slot: u32::MAX, 37 | generation: Generation::DANGLING, 38 | }; 39 | 40 | /// Convert this `Index` to an equivalent `u64` representation. Mostly 41 | /// useful for passing to code outside of Rust. 42 | #[allow(clippy::integer_arithmetic)] 43 | pub const fn to_bits(self) -> u64 { 44 | // This is safe because a `u32` bit-shifted by 32 will still fit in a `u64`. 45 | ((self.generation.to_u32() as u64) << 32) | (self.slot as u64) 46 | } 47 | 48 | /// Create an `Index` from bits created with `Index::to_bits`. 49 | /// 50 | /// If this function is called with bits that are not valid for an `Index`, 51 | /// returns `None`. This can happen if the encoded generation value is 0, 52 | /// for example. 53 | /// 54 | /// ## Stability 55 | /// Bits from `Index` values are guaranteed to be compatible within all 56 | /// semver-compatible versions of Thunderdome. That is, using 57 | /// `Index::to_bits` in 0.4.0 and `Index::from_bits` in 0.4.2 is guaranteed 58 | /// to work. 59 | #[allow(clippy::integer_arithmetic)] 60 | pub const fn from_bits(bits: u64) -> Option { 61 | // By bit-shifting right by 32, we're undoing the left-shift in `to_bits` 62 | // thus this is okay by the same rationale. 63 | let generation = match Generation::from_u32((bits >> 32) as u32) { 64 | Some(v) => v, 65 | None => return None, 66 | }; 67 | 68 | let slot = bits as u32; 69 | 70 | Some(Self { generation, slot }) 71 | } 72 | 73 | /// Convert this `Index` into a generation, discarding its slot. 74 | pub const fn generation(self) -> u32 { 75 | self.generation.to_u32() 76 | } 77 | 78 | /// Convert this `Index` into a slot, discarding its generation. Slots describe a 79 | /// location in an [`Arena`] and are reused when entries are removed. 80 | pub const fn slot(self) -> u32 { 81 | self.slot 82 | } 83 | } 84 | 85 | #[derive(Debug, Clone)] 86 | pub(crate) enum Entry { 87 | Occupied(OccupiedEntry), 88 | Empty(EmptyEntry), 89 | } 90 | 91 | impl Entry { 92 | /// Consume the entry, and if it's occupied, return the value. 93 | fn into_value(self) -> Option { 94 | match self { 95 | Entry::Occupied(occupied) => Some(occupied.value), 96 | Entry::Empty(_) => None, 97 | } 98 | } 99 | 100 | fn get_value_mut(&mut self, generation: Generation) -> Option<&mut T> { 101 | match self { 102 | Entry::Occupied(occupied) if occupied.generation == generation => { 103 | Some(&mut occupied.value) 104 | } 105 | _ => None, 106 | } 107 | } 108 | 109 | /// If the entry is empty, a reference to it. 110 | fn as_empty(&self) -> Option<&EmptyEntry> { 111 | match self { 112 | Entry::Empty(empty) => Some(empty), 113 | Entry::Occupied(_) => None, 114 | } 115 | } 116 | 117 | /// If the entry is empty, return a mutable reference to it. 118 | fn as_empty_mut(&mut self) -> Option<&mut EmptyEntry> { 119 | match self { 120 | Entry::Empty(empty) => Some(empty), 121 | Entry::Occupied(_) => None, 122 | } 123 | } 124 | } 125 | 126 | #[derive(Debug, Clone)] 127 | pub(crate) struct OccupiedEntry { 128 | pub(crate) generation: Generation, 129 | pub(crate) value: T, 130 | } 131 | 132 | #[derive(Debug, Clone, Copy)] 133 | pub(crate) struct EmptyEntry { 134 | pub(crate) generation: Generation, 135 | pub(crate) next_free: Option, 136 | } 137 | 138 | impl Arena { 139 | /// Construct an empty arena. 140 | pub const fn new() -> Self { 141 | Self { 142 | storage: Vec::new(), 143 | len: 0, 144 | first_free: None, 145 | } 146 | } 147 | 148 | /// Construct an empty arena with space to hold exactly `capacity` elements 149 | /// without reallocating. 150 | pub fn with_capacity(capacity: usize) -> Self { 151 | Self { 152 | storage: Vec::with_capacity(capacity), 153 | len: 0, 154 | first_free: None, 155 | } 156 | } 157 | 158 | /// Return the number of elements contained in the arena. 159 | pub const fn len(&self) -> usize { 160 | self.len as usize 161 | } 162 | 163 | /// Return the number of elements the arena can hold without allocating, 164 | /// including the elements currently in the arena. 165 | pub fn capacity(&self) -> usize { 166 | self.storage.capacity() 167 | } 168 | 169 | /// Reserve capacity for at least `additional` more elements to be inserted 170 | pub fn reserve(&mut self, additional: usize) { 171 | let currently_free = self.storage.len().saturating_sub(self.len as usize); 172 | let to_reserve = additional.saturating_sub(currently_free); 173 | self.storage.reserve(to_reserve); 174 | } 175 | 176 | /// Returns whether the arena is empty. 177 | pub const fn is_empty(&self) -> bool { 178 | self.len == 0 179 | } 180 | 181 | /// Insert a new value into the arena, returning an index that can be used 182 | /// to later retrieve the value. 183 | pub fn insert(&mut self, value: T) -> Index { 184 | // This value will definitely be inserted, so we can update length now. 185 | self.len = self 186 | .len 187 | .checked_add(1) 188 | .unwrap_or_else(|| panic!("Cannot insert more than u32::MAX elements into Arena")); 189 | 190 | // If there was a previously free entry, we can re-use its slot as long 191 | // as we increment its generation. 192 | if let Some(free_pointer) = self.first_free { 193 | let slot = free_pointer.slot(); 194 | let entry = self.storage.get_mut(slot as usize).unwrap_or_else(|| { 195 | unreachable!("first_free pointed past the end of the arena's storage") 196 | }); 197 | 198 | let empty = entry 199 | .as_empty() 200 | .unwrap_or_else(|| unreachable!("first_free pointed to an occupied entry")); 201 | 202 | // If there is another empty entry after this one, we'll update the 203 | // arena to point to it to use it on the next insertion. 204 | self.first_free = empty.next_free; 205 | 206 | // Overwrite the entry directly using our mutable reference instead 207 | // of indexing into our storage again. This should avoid an 208 | // additional bounds check. 209 | let generation = empty.generation.next(); 210 | *entry = Entry::Occupied(OccupiedEntry { generation, value }); 211 | 212 | Index { slot, generation } 213 | } else { 214 | // There were no more empty entries left in our free list, so we'll 215 | // create a new first-generation entry and push it into storage. 216 | 217 | let generation = Generation::first(); 218 | let slot: u32 = self.storage.len().try_into().unwrap_or_else(|_| { 219 | unreachable!("Arena storage exceeded what can be represented by a u32") 220 | }); 221 | 222 | self.storage 223 | .push(Entry::Occupied(OccupiedEntry { generation, value })); 224 | 225 | Index { slot, generation } 226 | } 227 | } 228 | 229 | /// Traverse the free list and remove this known-empty slot from it, given the slot to remove 230 | /// and the `next_free` pointer of that slot. 231 | fn remove_slot_from_free_list(&mut self, slot: u32, new_next_free: Option) { 232 | // We will need to fix up the free list so that whatever pointer previously pointed 233 | // to this empty entry will point to the next empty entry after it. 234 | let mut next_fp = self 235 | .first_free 236 | .expect("Free entry exists but first_free is None"); 237 | 238 | // As state during this traversal, we keep the "next free" pointer which we are testing 239 | // (which will always be `Some` as long as the free list is correct and contains this empty 240 | // entry) as well as the current slot that contains that "next free" pointer. If the current 241 | // slot is `None`, it means that the container of the relevant "next free" pointer is 242 | // actually the root (`self.first_free`). 243 | let mut current_slot = None; 244 | while next_fp.slot() != slot { 245 | current_slot = Some(next_fp.slot()); 246 | next_fp = self 247 | .storage 248 | .get(next_fp.slot() as usize) 249 | .expect("Empty entry not in storage!") 250 | .as_empty() 251 | .expect("Entry in free list not actually empty!") 252 | .next_free 253 | .expect("Hit the end of the free list without finding the target slot!"); 254 | } 255 | 256 | // If we found the slot to fix, then fix it; otherwise, we know that this slot is 257 | // actually the very first in the free list, so fix it at the root. 258 | match current_slot { 259 | Some(slot_to_fix) => { 260 | self.storage[slot_to_fix as usize] 261 | .as_empty_mut() 262 | .unwrap() 263 | .next_free = new_next_free 264 | } 265 | None => self.first_free = new_next_free, 266 | } 267 | } 268 | 269 | // Shared functionality between `insert_at` and `insert_at_slot`. 270 | #[inline] 271 | fn insert_at_inner( 272 | &mut self, 273 | slot: u32, 274 | generation: Option, 275 | value: T, 276 | ) -> (Index, Option) { 277 | // Three cases to consider: 278 | // 279 | // 1.) The slot is free; we need to traverse the free list, remove it from the list, and 280 | // then insert the value. 281 | // 2.) The slot is occupied; we can just replace the value and return the old one. 282 | // 3.) The slot is beyond the current length of the arena. In this case, we must extend 283 | // the arena with new empty slots filling the free list accordingly, and then insert the 284 | // value. 285 | 286 | let (index, old_value) = match self.storage.get_mut(slot as usize) { 287 | Some(Entry::Empty(empty)) => { 288 | let generation = generation.unwrap_or_else(|| empty.generation.next()); 289 | // We will need to fix up the free list so that whatever pointer previously pointed 290 | // to this empty entry will point to the next empty entry after it. 291 | let new_next_free = empty.next_free; 292 | self.remove_slot_from_free_list(slot, new_next_free); 293 | self.storage[slot as usize] = Entry::Occupied(OccupiedEntry { generation, value }); 294 | 295 | (Index { slot, generation }, None) 296 | } 297 | Some(Entry::Occupied(occupied)) => { 298 | occupied.generation = generation.unwrap_or_else(|| occupied.generation.next()); 299 | let generation = occupied.generation; 300 | let old_value = replace(&mut occupied.value, value); 301 | 302 | (Index { slot, generation }, Some(old_value)) 303 | } 304 | None => { 305 | let mut first_free = self.first_free; 306 | while self.storage.len() < slot as usize { 307 | let new_slot: u32 = self.storage.len().try_into().unwrap_or_else(|_| { 308 | unreachable!("Arena storage exceeded what can be represented by a u32") 309 | }); 310 | 311 | self.storage.push(Entry::Empty(EmptyEntry { 312 | generation: Generation::first(), 313 | next_free: first_free, 314 | })); 315 | 316 | first_free = Some(FreePointer::from_slot(new_slot)); 317 | } 318 | 319 | self.first_free = first_free; 320 | let generation = generation.unwrap_or_else(Generation::first); 321 | self.storage 322 | .push(Entry::Occupied(OccupiedEntry { generation, value })); 323 | 324 | (Index { slot, generation }, None) 325 | } 326 | }; 327 | 328 | // If this insertion didn't replace an old value, then the arena now contains one more 329 | // element; we need to update its length accordingly. 330 | if old_value.is_none() { 331 | self.len = self 332 | .len 333 | .checked_add(1) 334 | .unwrap_or_else(|| panic!("Cannot insert more than u32::MAX elements into Arena")); 335 | } 336 | 337 | (index, old_value) 338 | } 339 | 340 | /// Insert a new value at a given index, returning the old value if present. The entry's 341 | /// generation is set to the given index's generation. 342 | /// 343 | /// # Caveats 344 | /// 345 | /// This method is capable of "resurrecting" an old `Index`. This is unavoidable; if we already 346 | /// have an occupied entry (or had) at this index of some generation M, and then `insert_at` 347 | /// that same slot but with a generation N < M, eventually after some number of insertions and 348 | /// removals it is possible we could end up with an index matching that old index. There are few 349 | /// cases where this is likely to be a problem, but it is still possible. 350 | pub fn insert_at(&mut self, index: Index, value: T) -> Option { 351 | self.insert_at_inner(index.slot, Some(index.generation), value) 352 | .1 353 | } 354 | 355 | /// Insert a new value at a given slot, returning the old value if present. If the slot is 356 | /// already occupied, this will increment the generation of the slot, and invalidate any 357 | /// previous indices pointing to it. 358 | pub fn insert_at_slot(&mut self, slot: u32, value: T) -> (Index, Option) { 359 | self.insert_at_inner(slot, None, value) 360 | } 361 | 362 | /// Returns true if the given index is valid for the arena. 363 | pub fn contains(&self, index: Index) -> bool { 364 | match self.storage.get(index.slot as usize) { 365 | Some(Entry::Occupied(occupied)) if occupied.generation == index.generation => true, 366 | _ => false, 367 | } 368 | } 369 | 370 | /// Checks to see whether a slot is occupied in the arena, and if it is, 371 | /// returns `Some` with the true `Index` of that slot (slot plus generation.) 372 | /// Otherwise, returns `None`. 373 | pub fn contains_slot(&self, slot: u32) -> Option { 374 | match self.storage.get(slot as usize) { 375 | Some(Entry::Occupied(occupied)) => Some(Index { 376 | slot, 377 | generation: occupied.generation, 378 | }), 379 | _ => None, 380 | } 381 | } 382 | 383 | /// Get an immutable reference to a value inside the arena by 384 | /// [`Index`], returning `None` if the index is not contained in the arena. 385 | pub fn get(&self, index: Index) -> Option<&T> { 386 | match self.storage.get(index.slot as usize) { 387 | Some(Entry::Occupied(occupied)) if occupied.generation == index.generation => { 388 | Some(&occupied.value) 389 | } 390 | _ => None, 391 | } 392 | } 393 | 394 | /// Get a mutable reference to a value inside the arena by [`Index`], 395 | /// returning `None` if the index is not contained in the arena. 396 | pub fn get_mut(&mut self, index: Index) -> Option<&mut T> { 397 | match self.storage.get_mut(index.slot as usize) { 398 | Some(entry) => entry.get_value_mut(index.generation), 399 | _ => None, 400 | } 401 | } 402 | 403 | /// Get mutable references of two values inside this arena at once by 404 | /// [`Index`], returning `None` if the corresponding `index` is not 405 | /// contained in this arena. 406 | /// 407 | /// # Panics 408 | /// 409 | /// This function panics when the two indices are equal (having the same 410 | /// slot number and generation). 411 | pub fn get2_mut(&mut self, index1: Index, index2: Index) -> (Option<&mut T>, Option<&mut T>) { 412 | if index1 == index2 { 413 | panic!("Arena::get2_mut is called with two identical indices"); 414 | } 415 | 416 | // Same entry with a different generation. We'll prefer the first value 417 | // that matches. 418 | if index1.slot == index2.slot { 419 | // The borrow checker forces us to index into our storage twice here 420 | // due to `return` extending borrows. 421 | if self.get(index1).is_some() { 422 | return (self.get_mut(index1), None); 423 | } else { 424 | return (None, self.get_mut(index2)); 425 | } 426 | } 427 | 428 | // If the indices point to different slots, we can mutably split the 429 | // underlying storage to get the desired entry in each slice. 430 | let (entry1, entry2) = if index1.slot > index2.slot { 431 | let (slice1, slice2) = self.storage.split_at_mut(index1.slot as usize); 432 | (slice2.get_mut(0), slice1.get_mut(index2.slot as usize)) 433 | } else { 434 | let (slice1, slice2) = self.storage.split_at_mut(index2.slot as usize); 435 | (slice1.get_mut(index1.slot as usize), slice2.get_mut(0)) 436 | }; 437 | 438 | ( 439 | entry1.and_then(|e| e.get_value_mut(index1.generation)), 440 | entry2.and_then(|e| e.get_value_mut(index2.generation)), 441 | ) 442 | } 443 | 444 | /// Remove the value contained at the given index from the arena, returning 445 | /// it if it was present. 446 | pub fn remove(&mut self, index: Index) -> Option { 447 | let entry = self.storage.get_mut(index.slot as usize)?; 448 | 449 | match entry { 450 | Entry::Occupied(occupied) if occupied.generation == index.generation => { 451 | // We can replace an occupied entry with an empty entry with the 452 | // same generation. On next insertion, this generation will 453 | // increment. 454 | let new_entry = Entry::Empty(EmptyEntry { 455 | generation: occupied.generation, 456 | next_free: self.first_free, 457 | }); 458 | 459 | // Swap our new entry into our storage and take ownership of the 460 | // old entry. We'll consume it for its value so we can give that 461 | // back to our caller. 462 | let old_entry = replace(entry, new_entry); 463 | let value = old_entry.into_value().unwrap_or_else(|| unreachable!()); 464 | 465 | // The next time we insert, we can re-use the empty entry we 466 | // just created. If another removal happens before then, that 467 | // entry will be used before this one (FILO). 468 | self.first_free = Some(FreePointer::from_slot(index.slot)); 469 | 470 | self.len = self.len.checked_sub(1).unwrap_or_else(|| unreachable!()); 471 | 472 | Some(value) 473 | } 474 | _ => None, 475 | } 476 | } 477 | 478 | /// Invalidate the given index and return a new index to the same value. This 479 | /// is roughly equivalent to `remove` followed by `insert`, but much faster. 480 | /// If the old index is already invalid, this method returns `None`. 481 | pub fn invalidate(&mut self, index: Index) -> Option { 482 | let entry = self.storage.get_mut(index.slot as usize)?; 483 | 484 | match entry { 485 | Entry::Occupied(occupied) if occupied.generation == index.generation => { 486 | occupied.generation = occupied.generation.next(); 487 | 488 | Some(Index { 489 | generation: occupied.generation, 490 | ..index 491 | }) 492 | } 493 | _ => None, 494 | } 495 | } 496 | 497 | /// Attempt to look up the given slot in the arena, disregarding any generational 498 | /// information, and retrieve an immutable reference to it. Returns `None` if the 499 | /// slot is empty. 500 | pub fn get_by_slot(&self, slot: u32) -> Option<(Index, &T)> { 501 | match self.storage.get(slot as usize) { 502 | Some(Entry::Occupied(occupied)) => { 503 | let index = Index { 504 | slot, 505 | generation: occupied.generation, 506 | }; 507 | Some((index, &occupied.value)) 508 | } 509 | _ => None, 510 | } 511 | } 512 | 513 | /// Attempt to look up the given slot in the arena, disregarding any generational 514 | /// information, and retrieve a mutable reference to it. Returns `None` if the 515 | /// slot is empty. 516 | pub fn get_by_slot_mut(&mut self, slot: u32) -> Option<(Index, &mut T)> { 517 | match self.storage.get_mut(slot as usize) { 518 | Some(Entry::Occupied(occupied)) => { 519 | let index = Index { 520 | slot, 521 | generation: occupied.generation, 522 | }; 523 | Some((index, &mut occupied.value)) 524 | } 525 | _ => None, 526 | } 527 | } 528 | 529 | /// Remove an entry in the arena by its slot, disregarding any generational info. 530 | /// Returns `None` if the slot was already empty. 531 | pub fn remove_by_slot(&mut self, slot: u32) -> Option<(Index, T)> { 532 | let entry = self.storage.get_mut(slot as usize)?; 533 | 534 | match entry { 535 | Entry::Occupied(occupied) => { 536 | // Construct the index that would be used to access this entry. 537 | let index = Index { 538 | generation: occupied.generation, 539 | slot, 540 | }; 541 | 542 | // This occupied entry will be replaced with an empty one of the 543 | // same generation. Generation will be incremented on the next 544 | // insert. 545 | let next_entry = Entry::Empty(EmptyEntry { 546 | generation: occupied.generation, 547 | next_free: self.first_free, 548 | }); 549 | 550 | // Swap new entry into place and consume the old one. 551 | let old_entry = replace(entry, next_entry); 552 | let value = old_entry.into_value().unwrap_or_else(|| unreachable!()); 553 | 554 | // Set this entry as the next one that should be inserted into, 555 | // should an insertion happen. 556 | self.first_free = Some(FreePointer::from_slot(slot)); 557 | 558 | self.len = self.len.checked_sub(1).unwrap_or_else(|| unreachable!()); 559 | 560 | Some((index, value)) 561 | } 562 | _ => None, 563 | } 564 | } 565 | 566 | /// Clear the arena and drop all elements. 567 | pub fn clear(&mut self) { 568 | self.drain().for_each(drop); 569 | } 570 | 571 | /// Iterate over all of the indexes and values contained in the arena. 572 | /// 573 | /// Iteration order is not defined. 574 | pub fn iter(&self) -> Iter<'_, T> { 575 | Iter { 576 | inner: self.storage.iter(), 577 | slot: 0, 578 | len: self.len, 579 | } 580 | } 581 | 582 | /// Iterate over all of the indexes and values contained in the arena, with 583 | /// mutable access to each value. 584 | /// 585 | /// Iteration order is not defined. 586 | pub fn iter_mut(&mut self) -> IterMut<'_, T> { 587 | IterMut { 588 | inner: self.storage.iter_mut(), 589 | slot: 0, 590 | len: self.len, 591 | } 592 | } 593 | 594 | /// Returns an iterator that removes each element from the arena. 595 | /// 596 | /// Iteration order is not defined. 597 | /// 598 | /// If the iterator is dropped before it is fully consumed, any uniterated 599 | /// items will be dropped from the arena, and the arena will be empty. 600 | /// The arena's capacity will not be changed. 601 | pub fn drain(&mut self) -> Drain<'_, T> { 602 | Drain { 603 | arena: self, 604 | slot: 0, 605 | } 606 | } 607 | 608 | /// Remove all entries in the `Arena` which don't satisfy the provided predicate. 609 | pub fn retain bool>(&mut self, mut f: F) { 610 | for (i, entry) in self.storage.iter_mut().enumerate() { 611 | if let Entry::Occupied(occupied) = entry { 612 | let index = Index { 613 | slot: i as u32, 614 | generation: occupied.generation, 615 | }; 616 | 617 | if !f(index, &mut occupied.value) { 618 | // We can replace an occupied entry with an empty entry with the 619 | // same generation. On next insertion, this generation will 620 | // increment. 621 | *entry = Entry::Empty(EmptyEntry { 622 | generation: occupied.generation, 623 | next_free: self.first_free, 624 | }); 625 | 626 | // The next time we insert, we can re-use the empty entry we 627 | // just created. If another removal happens before then, that 628 | // entry will be used before this one (FILO). 629 | self.first_free = Some(FreePointer::from_slot(index.slot)); 630 | 631 | // We just verified that this entry is (was) occupied, so there's 632 | // trivially no way for this `checked_sub` to fail. 633 | self.len = self.len.checked_sub(1).unwrap_or_else(|| unreachable!()); 634 | } 635 | } 636 | } 637 | } 638 | } 639 | 640 | impl Default for Arena { 641 | fn default() -> Self { 642 | Arena::new() 643 | } 644 | } 645 | 646 | impl IntoIterator for Arena { 647 | type Item = (Index, T); 648 | type IntoIter = IntoIter; 649 | 650 | fn into_iter(self) -> Self::IntoIter { 651 | IntoIter { 652 | arena: self, 653 | slot: 0, 654 | } 655 | } 656 | } 657 | 658 | impl<'a, T> IntoIterator for &'a Arena { 659 | type Item = (Index, &'a T); 660 | type IntoIter = Iter<'a, T>; 661 | 662 | fn into_iter(self) -> Self::IntoIter { 663 | self.iter() 664 | } 665 | } 666 | 667 | impl<'a, T> IntoIterator for &'a mut Arena { 668 | type Item = (Index, &'a mut T); 669 | type IntoIter = IterMut<'a, T>; 670 | 671 | fn into_iter(self) -> Self::IntoIter { 672 | self.iter_mut() 673 | } 674 | } 675 | 676 | impl ops::Index for Arena { 677 | type Output = T; 678 | 679 | fn index(&self, index: Index) -> &Self::Output { 680 | self.get(index) 681 | .unwrap_or_else(|| panic!("No entry at index {:?}", index)) 682 | } 683 | } 684 | 685 | impl ops::IndexMut for Arena { 686 | fn index_mut(&mut self, index: Index) -> &mut Self::Output { 687 | self.get_mut(index) 688 | .unwrap_or_else(|| panic!("No entry at index {:?}", index)) 689 | } 690 | } 691 | 692 | #[cfg(test)] 693 | mod test { 694 | use crate::free_pointer::FreePointer; 695 | 696 | use super::{Arena, Generation, Index}; 697 | 698 | use core::mem::size_of; 699 | 700 | #[test] 701 | fn size_of_index() { 702 | assert_eq!(size_of::(), 8); 703 | assert_eq!(size_of::>(), 8); 704 | } 705 | 706 | #[test] 707 | fn new() { 708 | let arena: Arena = Arena::new(); 709 | assert_eq!(arena.len(), 0); 710 | assert_eq!(arena.capacity(), 0); 711 | } 712 | 713 | #[test] 714 | fn with_capacity() { 715 | let arena: Arena = Arena::with_capacity(8); 716 | assert_eq!(arena.len(), 0); 717 | assert_eq!(arena.capacity(), 8); 718 | } 719 | 720 | #[test] 721 | fn insert_and_get() { 722 | let mut arena = Arena::new(); 723 | 724 | let one = arena.insert(1); 725 | assert_eq!(arena.len(), 1); 726 | assert_eq!(arena.get(one), Some(&1)); 727 | 728 | let two = arena.insert(2); 729 | assert_eq!(arena.len(), 2); 730 | assert_eq!(arena.get(one), Some(&1)); 731 | assert_eq!(arena.get(two), Some(&2)); 732 | } 733 | 734 | #[test] 735 | fn insert_remove_get() { 736 | let mut arena = Arena::new(); 737 | let one = arena.insert(1); 738 | 739 | let two = arena.insert(2); 740 | assert_eq!(arena.len(), 2); 741 | assert!(arena.contains(two)); 742 | assert_eq!(arena.remove(two), Some(2)); 743 | assert!(!arena.contains(two)); 744 | 745 | let three = arena.insert(3); 746 | assert_eq!(arena.len(), 2); 747 | assert_eq!(arena.get(one), Some(&1)); 748 | assert_eq!(arena.get(three), Some(&3)); 749 | assert_eq!(arena.get(two), None); 750 | } 751 | 752 | #[test] 753 | fn insert_remove_get_by_slot() { 754 | let mut arena = Arena::new(); 755 | let one = arena.insert(1); 756 | 757 | let two = arena.insert(2); 758 | assert_eq!(arena.len(), 2); 759 | assert!(arena.contains(two)); 760 | assert_eq!(arena.remove_by_slot(two.slot()), Some((two, 2))); 761 | assert!(!arena.contains(two)); 762 | assert_eq!(arena.get_by_slot(two.slot()), None); 763 | 764 | let three = arena.insert(3); 765 | assert_eq!(arena.len(), 2); 766 | assert_eq!(arena.get(one), Some(&1)); 767 | assert_eq!(arena.get(three), Some(&3)); 768 | assert_eq!(arena.get(two), None); 769 | assert_eq!(arena.get_by_slot(two.slot()), Some((three, &3))); 770 | } 771 | 772 | #[test] 773 | fn insert_at() { 774 | let mut arena = Arena::new(); 775 | // Numbers definitely not chosen by fair dice roll 776 | let index = Index { 777 | slot: 42, 778 | generation: Generation::from_u32(78).unwrap(), 779 | }; 780 | arena.insert_at(index, 5); 781 | assert_eq!(arena.len(), 1); 782 | assert_eq!(arena.get(index), Some(&5)); 783 | assert_eq!(arena.get_by_slot(42), Some((index, &5))); 784 | } 785 | 786 | #[test] 787 | fn insert_at_first_slot() { 788 | let mut arena = Arena::new(); 789 | // Numbers definitely not chosen by fair dice roll 790 | let index = Index { 791 | slot: 0, 792 | generation: Generation::from_u32(3).unwrap(), 793 | }; 794 | arena.insert_at(index, 5); 795 | assert_eq!(arena.len(), 1); 796 | assert_eq!(arena.get(index), Some(&5)); 797 | assert_eq!(arena.get_by_slot(0), Some((index, &5))); 798 | } 799 | 800 | #[test] 801 | fn insert_at_slot() { 802 | let mut arena = Arena::new(); 803 | 804 | let (index, _) = arena.insert_at_slot(42, 5); 805 | assert_eq!(arena.len(), 1); 806 | assert_eq!(arena.get(index), Some(&5)); 807 | assert_eq!(arena.get_by_slot(42), Some((index, &5))); 808 | } 809 | 810 | #[test] 811 | fn insert_at_middle() { 812 | let mut arena = Arena::new(); 813 | arena.insert_at_slot(4, 50); 814 | arena.insert_at_slot(2, 40); 815 | 816 | let empty = arena.storage.get(3).unwrap().as_empty().unwrap(); 817 | if empty.next_free != Some(FreePointer::from_slot(1)) { 818 | panic!("Invalid free list: {:#?}", arena); 819 | } 820 | } 821 | 822 | #[test] 823 | fn get_mut() { 824 | let mut arena = Arena::new(); 825 | let foo = arena.insert(5); 826 | 827 | let handle = arena.get_mut(foo).unwrap(); 828 | *handle = 6; 829 | 830 | assert_eq!(arena.get(foo), Some(&6)); 831 | } 832 | 833 | #[test] 834 | fn get2_mut() { 835 | let mut arena = Arena::new(); 836 | let foo = arena.insert(100); 837 | let bar = arena.insert(500); 838 | 839 | let (foo_handle, bar_handle) = arena.get2_mut(foo, bar); 840 | let foo_handle = foo_handle.unwrap(); 841 | let bar_handle = bar_handle.unwrap(); 842 | *foo_handle = 105; 843 | *bar_handle = 505; 844 | 845 | assert_eq!(arena.get(foo), Some(&105)); 846 | assert_eq!(arena.get(bar), Some(&505)); 847 | } 848 | 849 | #[test] 850 | fn get2_mut_reversed_order() { 851 | let mut arena = Arena::new(); 852 | let foo = arena.insert(100); 853 | let bar = arena.insert(500); 854 | 855 | let (bar_handle, foo_handle) = arena.get2_mut(bar, foo); 856 | let foo_handle = foo_handle.unwrap(); 857 | let bar_handle = bar_handle.unwrap(); 858 | *foo_handle = 105; 859 | *bar_handle = 505; 860 | 861 | assert_eq!(arena.get(foo), Some(&105)); 862 | assert_eq!(arena.get(bar), Some(&505)); 863 | } 864 | 865 | #[test] 866 | fn get2_mut_non_exist_handle() { 867 | let mut arena = Arena::new(); 868 | let foo = arena.insert(100); 869 | let bar = arena.insert(500); 870 | arena.remove(bar); 871 | 872 | let (bar_handle, foo_handle) = arena.get2_mut(bar, foo); 873 | let foo_handle = foo_handle.unwrap(); 874 | assert!(bar_handle.is_none()); 875 | *foo_handle = 105; 876 | 877 | assert_eq!(arena.get(foo), Some(&105)); 878 | } 879 | 880 | #[test] 881 | fn get2_mut_same_slot_different_generation() { 882 | let mut arena = Arena::new(); 883 | let foo = arena.insert(100); 884 | let mut foo1 = foo; 885 | foo1.generation = foo1.generation.next(); 886 | 887 | let (foo_handle, foo1_handle) = arena.get2_mut(foo, foo1); 888 | assert!(foo_handle.is_some()); 889 | assert!(foo1_handle.is_none()); 890 | } 891 | 892 | #[test] 893 | #[should_panic] 894 | fn get2_mut_panics() { 895 | let mut arena = Arena::new(); 896 | let foo = arena.insert(100); 897 | 898 | arena.get2_mut(foo, foo); 899 | } 900 | 901 | #[test] 902 | fn insert_remove_insert_capacity() { 903 | let mut arena = Arena::with_capacity(2); 904 | assert_eq!(arena.capacity(), 2); 905 | 906 | let a = arena.insert("a"); 907 | let b = arena.insert("b"); 908 | assert_eq!(arena.len(), 2); 909 | assert_eq!(arena.capacity(), 2); 910 | 911 | arena.remove(a); 912 | arena.remove(b); 913 | assert_eq!(arena.len(), 0); 914 | assert_eq!(arena.capacity(), 2); 915 | 916 | let _a2 = arena.insert("a2"); 917 | let _b2 = arena.insert("b2"); 918 | assert_eq!(arena.len(), 2); 919 | assert_eq!(arena.capacity(), 2); 920 | } 921 | 922 | #[test] 923 | fn invalidate() { 924 | let mut arena = Arena::new(); 925 | 926 | let a = arena.insert("a"); 927 | assert_eq!(arena.get(a), Some(&"a")); 928 | 929 | let new_a = arena.invalidate(a).unwrap(); 930 | assert_eq!(arena.get(a), None); 931 | assert_eq!(arena.get(new_a), Some(&"a")); 932 | } 933 | 934 | #[test] 935 | fn retain() { 936 | let mut arena = Arena::new(); 937 | 938 | for i in 0..100 { 939 | arena.insert(i); 940 | } 941 | 942 | arena.retain(|_, &mut i| i % 2 == 1); 943 | 944 | for (_, i) in arena.iter() { 945 | assert_eq!(i % 2, 1); 946 | } 947 | 948 | assert_eq!(arena.len(), 50); 949 | } 950 | 951 | #[test] 952 | fn index_bits_roundtrip() { 953 | let index = Index::from_bits(0x1BAD_CAFE_DEAD_BEEF).unwrap(); 954 | assert_eq!(index.to_bits(), 0x1BAD_CAFE_DEAD_BEEF); 955 | } 956 | 957 | #[test] 958 | fn index_bits_none_on_zero_generation() { 959 | let index = Index::from_bits(0x0000_0000_DEAD_BEEF); 960 | assert_eq!(index, None); 961 | } 962 | } 963 | -------------------------------------------------------------------------------- /src/free_pointer.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::num::NonZeroU32; 3 | 4 | /// Contains a reference to a free slot in an arena, encapsulating NonZeroU32 5 | /// to prevent off-by-one errors and leaking unsafety. 6 | /// 7 | /// Uses NonZeroU32 to stay small when put inside an `Option`. 8 | #[derive(Clone, Copy, PartialEq)] 9 | #[repr(transparent)] 10 | pub(crate) struct FreePointer(NonZeroU32); 11 | 12 | impl FreePointer { 13 | #[must_use] 14 | pub(crate) fn from_slot(slot: u32) -> Self { 15 | let value = slot 16 | .checked_add(1) 17 | .expect("u32 overflowed calculating free pointer from u32"); 18 | 19 | // This is safe because any u32 + 1 that didn't overflow must not be 20 | // zero. 21 | FreePointer(unsafe { NonZeroU32::new_unchecked(value) }) 22 | } 23 | 24 | #[must_use] 25 | #[allow(clippy::integer_arithmetic)] 26 | pub(crate) fn slot(self) -> u32 { 27 | // This will never underflow due to the field being guaranteed non-zero. 28 | self.0.get() - 1 29 | } 30 | } 31 | 32 | impl fmt::Debug for FreePointer { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | write!(f, "FreePointer({})", self.slot()) 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod test { 40 | use super::FreePointer; 41 | 42 | #[test] 43 | fn from_slot() { 44 | let ptr = FreePointer::from_slot(0); 45 | assert_eq!(ptr.slot(), 0); 46 | } 47 | 48 | #[test] 49 | #[should_panic(expected = "u32 overflowed calculating free pointer from u32")] 50 | fn panic_on_overflow() { 51 | let _ = FreePointer::from_slot(core::u32::MAX); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/generation.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::num::NonZeroU32; 3 | 4 | /// Tracks the generation of an entry in an arena. Encapsulates NonZeroU32 to 5 | /// reduce the number of redundant checks needed, as well as enforcing checked 6 | /// arithmetic on advancing a generation. 7 | /// 8 | /// Uses NonZeroU32 to help `Index` stay the same size when put inside an 9 | /// `Option`. 10 | #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] 11 | #[repr(transparent)] 12 | pub(crate) struct Generation(NonZeroU32); 13 | 14 | impl Generation { 15 | /// Represents a generation that is unlikely to be used. This is useful for 16 | /// programs that want to do two-phase initialization in safe Rust. 17 | // This is safe because the maximum value of a u32 is not zero. 18 | pub(crate) const DANGLING: Self = unsafe { Generation(NonZeroU32::new_unchecked(u32::MAX)) }; 19 | 20 | #[must_use] 21 | pub(crate) fn first() -> Self { 22 | // This is safe because 1 is not zero. 23 | Generation(unsafe { NonZeroU32::new_unchecked(1) }) 24 | } 25 | 26 | #[must_use] 27 | pub(crate) fn next(self) -> Self { 28 | let last_generation = self.0.get(); 29 | let next_generation = last_generation.checked_add(1).unwrap_or(1); 30 | 31 | // This is safe because value that would overflow is instead made 1. 32 | Generation(unsafe { NonZeroU32::new_unchecked(next_generation) }) 33 | } 34 | 35 | pub(crate) const fn to_u32(self) -> u32 { 36 | self.0.get() 37 | } 38 | 39 | pub(crate) const fn from_u32(gen: u32) -> Option { 40 | // match like this since `map` is not constant 41 | match NonZeroU32::new(gen) { 42 | Some(v) => Some(Generation(v)), 43 | None => None, 44 | } 45 | } 46 | } 47 | 48 | impl fmt::Debug for Generation { 49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | write!(f, "Generation({})", self.to_u32()) 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod test { 56 | use super::Generation; 57 | 58 | use core::num::NonZeroU32; 59 | 60 | #[test] 61 | fn first_and_next() { 62 | let first = Generation::first(); 63 | assert_eq!(first.0.get(), 1); 64 | 65 | let second = first.next(); 66 | assert_eq!(second.0.get(), 2); 67 | } 68 | 69 | #[test] 70 | fn wrap_on_overflow() { 71 | let max = Generation(NonZeroU32::new(core::u32::MAX).unwrap()); 72 | assert_eq!(max.0.get(), core::u32::MAX); 73 | 74 | let next = max.next(); 75 | assert_eq!(next.0.get(), 1); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/iter/drain.rs: -------------------------------------------------------------------------------- 1 | use core::iter::{ExactSizeIterator, FusedIterator}; 2 | 3 | use crate::arena::{Arena, Index}; 4 | 5 | /// See [`Arena::drain`]. 6 | pub struct Drain<'a, T> { 7 | pub(crate) arena: &'a mut Arena, 8 | pub(crate) slot: u32, 9 | } 10 | 11 | impl<'a, T> Iterator for Drain<'a, T> { 12 | type Item = (Index, T); 13 | 14 | fn next(&mut self) -> Option { 15 | loop { 16 | // If there are no entries remaining in the arena, we should always 17 | // return None. Using this check instead of comparing with the 18 | // arena's size allows us to skip any trailing empty entries. 19 | if self.arena.is_empty() { 20 | return None; 21 | } 22 | 23 | // slot may overflow if the arena's underlying storage contains more 24 | // than 2^32 elements, but its internal length value was not 25 | // changed, as it overflowing would panic before reaching this code. 26 | let slot = self.slot; 27 | self.slot = self 28 | .slot 29 | .checked_add(1) 30 | .unwrap_or_else(|| panic!("Overflowed u32 trying to drain Arena")); 31 | 32 | // If this entry is occupied, this method will mark it as an empty. 33 | // Otherwise, we'll continue looping until we've drained all 34 | // occupied entries from the arena. 35 | if let Some((index, value)) = self.arena.remove_by_slot(slot) { 36 | return Some((index, value)); 37 | } 38 | } 39 | } 40 | 41 | fn size_hint(&self) -> (usize, Option) { 42 | (self.arena.len(), Some(self.arena.len())) 43 | } 44 | } 45 | 46 | impl<'a, T> FusedIterator for Drain<'a, T> {} 47 | impl<'a, T> ExactSizeIterator for Drain<'a, T> {} 48 | 49 | impl<'a, T> Drop for Drain<'a, T> { 50 | // Continue iterating/dropping if there are any elements left. 51 | fn drop(&mut self) { 52 | self.for_each(drop); 53 | } 54 | } 55 | 56 | #[cfg(all(test, feature = "std"))] 57 | mod test { 58 | use crate::Arena; 59 | 60 | use std::collections::HashSet; 61 | 62 | #[test] 63 | fn drain() { 64 | let mut arena = Arena::with_capacity(2); 65 | let one = arena.insert(1); 66 | let two = arena.insert(2); 67 | 68 | let mut drained_pairs = HashSet::new(); 69 | { 70 | let mut drain = arena.drain(); 71 | assert_eq!(drain.size_hint(), (2, Some(2))); 72 | 73 | drained_pairs.insert(drain.next().unwrap()); 74 | assert_eq!(drain.size_hint(), (1, Some(1))); 75 | 76 | // Do not fully drain so we can ensure everything is dropped when the 77 | // `Drain` is dropped. 78 | assert_eq!(drain.size_hint(), (1, Some(1))); 79 | } 80 | 81 | assert_eq!(arena.len(), 0); 82 | assert_eq!(arena.capacity(), 2); 83 | assert_eq!(drained_pairs.len(), 1); 84 | 85 | // We should still be able to use the arena after this. 86 | let one_prime = arena.insert(1); 87 | let two_prime = arena.insert(2); 88 | 89 | assert_eq!(arena.len(), 2); 90 | assert_eq!(arena.capacity(), 2); 91 | assert_eq!(arena.get(one_prime), Some(&1)); 92 | assert_eq!(arena.get(two_prime), Some(&2)); 93 | assert_eq!(arena.get(one), None); 94 | assert_eq!(arena.get(two), None); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/iter/into_iter.rs: -------------------------------------------------------------------------------- 1 | use core::iter::{ExactSizeIterator, FusedIterator}; 2 | 3 | use crate::arena::{Arena, Index}; 4 | 5 | /// Iterator typed used when an Arena is turned [`IntoIterator`]. 6 | pub struct IntoIter { 7 | pub(crate) arena: Arena, 8 | pub(crate) slot: u32, 9 | } 10 | 11 | impl Iterator for IntoIter { 12 | type Item = (Index, T); 13 | 14 | fn next(&mut self) -> Option { 15 | loop { 16 | // If there are no entries remaining in the arena, we should always 17 | // return None. Using this check instead of comparing with the 18 | // arena's size allows us to skip any trailing empty entries. 19 | if self.arena.is_empty() { 20 | return None; 21 | } 22 | 23 | // slot may overflow if the arena's underlying storage contains more 24 | // than 2^32 elements, but its internal length value was not 25 | // changed, as it overflowing would panic before reaching this code. 26 | let slot = self.slot; 27 | self.slot = self 28 | .slot 29 | .checked_add(1) 30 | .unwrap_or_else(|| panic!("Overflowed u32 trying to into_iter Arena")); 31 | 32 | // If this entry is occupied, this method will mark it as an empty. 33 | // Otherwise, we'll continue looping until we've removed all 34 | // occupied entries from the arena. 35 | if let Some((index, value)) = self.arena.remove_by_slot(slot) { 36 | return Some((index, value)); 37 | } 38 | } 39 | } 40 | 41 | fn size_hint(&self) -> (usize, Option) { 42 | (self.arena.len(), Some(self.arena.len())) 43 | } 44 | } 45 | 46 | impl FusedIterator for IntoIter {} 47 | impl ExactSizeIterator for IntoIter {} 48 | 49 | #[cfg(all(test, feature = "std"))] 50 | mod test { 51 | use crate::Arena; 52 | 53 | use std::collections::HashSet; 54 | 55 | #[test] 56 | fn into_iter() { 57 | let mut arena = Arena::with_capacity(2); 58 | let one = arena.insert(1); 59 | let two = arena.insert(2); 60 | 61 | let mut pairs = HashSet::new(); 62 | let mut into_iter = arena.into_iter(); 63 | assert_eq!(into_iter.size_hint(), (2, Some(2))); 64 | 65 | pairs.insert(into_iter.next().unwrap()); 66 | assert_eq!(into_iter.size_hint(), (1, Some(1))); 67 | 68 | pairs.insert(into_iter.next().unwrap()); 69 | assert_eq!(into_iter.size_hint(), (0, Some(0))); 70 | 71 | assert_eq!(into_iter.next(), None); 72 | assert_eq!(into_iter.next(), None); 73 | assert_eq!(into_iter.size_hint(), (0, Some(0))); 74 | 75 | assert_eq!(pairs.len(), 2); 76 | assert!(pairs.contains(&(one, 1))); 77 | assert!(pairs.contains(&(two, 2))); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/iter/iter.rs: -------------------------------------------------------------------------------- 1 | use core::iter::{ExactSizeIterator, FusedIterator}; 2 | use core::slice; 3 | 4 | use crate::arena::{Entry, Index}; 5 | 6 | /// See [`Arena::iter`](crate::Arena::iter). 7 | pub struct Iter<'a, T> { 8 | pub(crate) len: u32, 9 | pub(crate) slot: u32, 10 | pub(crate) inner: slice::Iter<'a, Entry>, 11 | } 12 | 13 | impl<'a, T> Iterator for Iter<'a, T> { 14 | type Item = (Index, &'a T); 15 | 16 | fn next(&mut self) -> Option { 17 | loop { 18 | if self.len == 0 { 19 | return None; 20 | } 21 | 22 | let slot = self.slot; 23 | self.slot = self 24 | .slot 25 | .checked_add(1) 26 | .unwrap_or_else(|| unreachable!("Overflowed u32 trying to iterate Arena")); 27 | 28 | match self.inner.next()? { 29 | Entry::Empty(_) => (), 30 | Entry::Occupied(occupied) => { 31 | self.len = self 32 | .len 33 | .checked_sub(1) 34 | .unwrap_or_else(|| unreachable!("Underflowed u32 trying to iterate Arena")); 35 | 36 | let index = Index { 37 | slot, 38 | generation: occupied.generation, 39 | }; 40 | 41 | return Some((index, &occupied.value)); 42 | } 43 | } 44 | } 45 | } 46 | 47 | fn size_hint(&self) -> (usize, Option) { 48 | (self.len as usize, Some(self.len as usize)) 49 | } 50 | } 51 | 52 | impl<'a, T> FusedIterator for Iter<'a, T> {} 53 | impl<'a, T> ExactSizeIterator for Iter<'a, T> {} 54 | 55 | #[cfg(all(test, feature = "std"))] 56 | mod test { 57 | use crate::Arena; 58 | 59 | use std::collections::HashSet; 60 | 61 | #[test] 62 | fn iter() { 63 | let mut arena = Arena::with_capacity(2); 64 | let one = arena.insert(1); 65 | let two = arena.insert(2); 66 | 67 | let mut pairs = HashSet::new(); 68 | let mut iter = arena.iter(); 69 | assert_eq!(iter.size_hint(), (2, Some(2))); 70 | 71 | pairs.insert(iter.next().unwrap()); 72 | assert_eq!(iter.size_hint(), (1, Some(1))); 73 | 74 | pairs.insert(iter.next().unwrap()); 75 | assert_eq!(iter.size_hint(), (0, Some(0))); 76 | 77 | assert_eq!(iter.next(), None); 78 | assert_eq!(iter.next(), None); 79 | assert_eq!(iter.size_hint(), (0, Some(0))); 80 | 81 | assert!(pairs.contains(&(one, &1))); 82 | assert!(pairs.contains(&(two, &2))); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/iter/iter_mut.rs: -------------------------------------------------------------------------------- 1 | use core::iter::{ExactSizeIterator, FusedIterator}; 2 | use core::slice; 3 | 4 | use crate::arena::{Entry, Index}; 5 | 6 | /// See [`Arena::iter_mut`](crate::Arena::iter_mut). 7 | pub struct IterMut<'a, T> { 8 | pub(crate) len: u32, 9 | pub(crate) slot: u32, 10 | pub(crate) inner: slice::IterMut<'a, Entry>, 11 | } 12 | 13 | impl<'a, T> Iterator for IterMut<'a, T> { 14 | type Item = (Index, &'a mut T); 15 | 16 | fn next(&mut self) -> Option { 17 | loop { 18 | if self.len == 0 { 19 | return None; 20 | } 21 | 22 | let slot = self.slot; 23 | self.slot = self 24 | .slot 25 | .checked_add(1) 26 | .unwrap_or_else(|| unreachable!("Overflowed u32 trying to iterate Arena")); 27 | 28 | match self.inner.next()? { 29 | Entry::Empty(_) => continue, 30 | Entry::Occupied(occupied) => { 31 | self.len = self 32 | .len 33 | .checked_sub(1) 34 | .unwrap_or_else(|| unreachable!("Underflowed u32 trying to iterate Arena")); 35 | 36 | let index = Index { 37 | slot, 38 | generation: occupied.generation, 39 | }; 40 | 41 | return Some((index, &mut occupied.value)); 42 | } 43 | } 44 | } 45 | } 46 | 47 | fn size_hint(&self) -> (usize, Option) { 48 | (self.len as usize, Some(self.len as usize)) 49 | } 50 | } 51 | 52 | impl<'a, T> FusedIterator for IterMut<'a, T> {} 53 | impl<'a, T> ExactSizeIterator for IterMut<'a, T> {} 54 | 55 | #[cfg(all(test, feature = "std"))] 56 | mod test { 57 | use crate::Arena; 58 | 59 | use std::collections::HashSet; 60 | 61 | #[test] 62 | fn iter_mut() { 63 | let mut arena = Arena::with_capacity(2); 64 | let one = arena.insert(1); 65 | let two = arena.insert(2); 66 | 67 | let mut pairs = HashSet::new(); 68 | let mut iter = arena.iter_mut(); 69 | assert_eq!(iter.size_hint(), (2, Some(2))); 70 | 71 | pairs.insert(iter.next().unwrap()); 72 | assert_eq!(iter.size_hint(), (1, Some(1))); 73 | 74 | pairs.insert(iter.next().unwrap()); 75 | assert_eq!(iter.size_hint(), (0, Some(0))); 76 | 77 | assert_eq!(iter.next(), None); 78 | assert_eq!(iter.next(), None); 79 | assert_eq!(iter.size_hint(), (0, Some(0))); 80 | 81 | assert!(pairs.contains(&(one, &mut 1))); 82 | assert!(pairs.contains(&(two, &mut 2))); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/iter/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains all of the iterator types for Thunderdome. 2 | 3 | mod drain; 4 | mod into_iter; 5 | mod iter; 6 | mod iter_mut; 7 | 8 | pub use drain::Drain; 9 | pub use into_iter::IntoIter; 10 | pub use iter::Iter; 11 | pub use iter_mut::IterMut; 12 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | [![GitHub CI Status](https://github.com/LPGhatguy/thunderdome/workflows/CI/badge.svg)](https://github.com/LPGhatguy/thunderdome/actions) 3 | [![thunderdome on crates.io](https://img.shields.io/crates/v/thunderdome.svg)](https://crates.io/crates/thunderdome) 4 | [![thunderdome docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/thunderdome) 5 | 6 | Thunderdome is a ~~gladitorial~~ generational arena inspired by 7 | [generational-arena](https://crates.io/crates/generational-arena), 8 | [slotmap](https://crates.io/crates/slotmap), and 9 | [slab](https://crates.io/crates/slab). It provides constant time insertion, 10 | lookup, and removal via small (8 byte) keys returned from [`Arena`]. 11 | 12 | Thunderdome's key type, [`Index`], is still 8 bytes when put inside of an 13 | `Option` thanks to Rust's `NonZero*` types. 14 | 15 | # Basic Examples 16 | 17 | ```rust 18 | # use thunderdome::{Arena, Index}; 19 | let mut arena = Arena::new(); 20 | 21 | let foo = arena.insert("Foo"); 22 | let bar = arena.insert("Bar"); 23 | 24 | assert_eq!(arena[foo], "Foo"); 25 | assert_eq!(arena[bar], "Bar"); 26 | 27 | arena[bar] = "Replaced"; 28 | assert_eq!(arena[bar], "Replaced"); 29 | 30 | let foo_value = arena.remove(foo); 31 | assert_eq!(foo_value, Some("Foo")); 32 | 33 | // The slot previously used by foo will be reused for baz 34 | let baz = arena.insert("Baz"); 35 | assert_eq!(arena[baz], "Baz"); 36 | 37 | // foo is no longer a valid key 38 | assert_eq!(arena.get(foo), None); 39 | ``` 40 | 41 | # Comparison With Similar Crates 42 | 43 | | Feature | Thunderdome | generational-arena | slotmap | slab | 44 | |------------------------------|-------------|--------------------|---------|------| 45 | | Generational Indices¹ | Yes | Yes | Yes | No | 46 | | `size_of::()` | 8 | 16 | 8 | 8 | 47 | | `size_of::>()` | 8 | 24 | 8 | 16 | 48 | | Max Elements | 2³² | 2⁶⁴ | 2³² | 2⁶⁴ | 49 | | Non-`Copy` Values | Yes | Yes | Yes | Yes | 50 | | `no_std` Support | Yes | Yes | Yes | No | 51 | | Serde Support | No | Yes | Yes | No | 52 | 53 | * Sizes calculated on rustc `1.44.0-x86_64-pc-windows-msvc` 54 | * See [the Thunderdome comparison 55 | Cargo.toml](https://github.com/LPGhatguy/thunderdome/blob/main/comparison/Cargo.toml) 56 | for versions of each library tested. 57 | 58 | 1. Generational indices help solve the [ABA 59 | Problem](https://en.wikipedia.org/wiki/ABA_problem), which can cause dangling 60 | keys to mistakenly access newly-inserted data. 61 | 62 | # Minimum Supported Rust Version (MSRV) 63 | 64 | Thunderdome supports Rust 1.47.0 and newer. Until Thunderdome reaches 1.0, 65 | changes to the MSRV will require major version bumps. After 1.0, MSRV changes 66 | will only require minor version bumps, but will need significant justification. 67 | 68 | # Crate Features 69 | * `std` (default): Use the standard library. Disable to make this crate `no-std` compatible. 70 | */ 71 | 72 | #![forbid(missing_docs)] 73 | // This crate is sensitive to integer overflow and wrapping behavior. As such, 74 | // we should usually use methods like `checked_add` and `checked_sub` instead 75 | // of the `Add` or `Sub` operators. 76 | #![deny(clippy::integer_arithmetic)] 77 | // TODO: Deny clippy::std_instead_of_core, clippy::std_instead_of_alloc and 78 | // clippy::alloc_instead_of_core when released. 79 | #![cfg_attr(not(feature = "std"), no_std)] 80 | 81 | #[cfg(not(feature = "std"))] 82 | extern crate alloc; 83 | 84 | mod arena; 85 | mod free_pointer; 86 | mod generation; 87 | pub mod iter; 88 | 89 | pub use crate::arena::{Arena, Index}; 90 | --------------------------------------------------------------------------------