├── .github └── workflows │ ├── autofix.yml │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── benches └── operations.rs ├── renovate.json ├── src ├── chunk.rs └── lib.rs └── tests └── ci.rs /.github/workflows/autofix.yml: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------- 2 | # ------------------------------- WARNING --------------------------- 3 | # ------------------------------------------------------------------- 4 | # 5 | # This file was automatically generated by gh-workflows using the 6 | # gh-workflow-gen bin. You should add and commit this file to your 7 | # git repository. **DO NOT EDIT THIS FILE BY HAND!** Any manual changes 8 | # will be lost if the file is regenerated. 9 | # 10 | # To make modifications, update your `build.rs` configuration to adjust 11 | # the workflow description as needed, then regenerate this file to apply 12 | # those changes. 13 | # 14 | # ------------------------------------------------------------------- 15 | # ----------------------------- END WARNING ------------------------- 16 | # ------------------------------------------------------------------- 17 | 18 | name: autofix.ci 19 | env: 20 | RUSTFLAGS: -Dwarnings 21 | on: 22 | pull_request: 23 | types: 24 | - opened 25 | - synchronize 26 | - reopened 27 | branches: 28 | - main 29 | push: 30 | branches: 31 | - main 32 | jobs: 33 | lint: 34 | name: Lint Fix 35 | runs-on: ubuntu-latest 36 | permissions: 37 | contents: read 38 | steps: 39 | - name: Checkout Code 40 | uses: actions/checkout@v4 41 | - name: Setup Rust Toolchain 42 | uses: actions-rust-lang/setup-rust-toolchain@v1 43 | with: 44 | toolchain: nightly 45 | components: clippy, rustfmt 46 | - name: Cargo Fmt 47 | run: cargo +nightly fmt --all 48 | - name: Cargo Clippy 49 | run: cargo +nightly clippy --fix --allow-dirty --all-features --workspace -- -D warnings 50 | - uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c 51 | concurrency: 52 | group: autofix-${{github.ref}} 53 | cancel-in-progress: false 54 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------- 2 | # ------------------------------- WARNING --------------------------- 3 | # ------------------------------------------------------------------- 4 | # 5 | # This file was automatically generated by gh-workflows using the 6 | # gh-workflow-gen bin. You should add and commit this file to your 7 | # git repository. **DO NOT EDIT THIS FILE BY HAND!** Any manual changes 8 | # will be lost if the file is regenerated. 9 | # 10 | # To make modifications, update your `build.rs` configuration to adjust 11 | # the workflow description as needed, then regenerate this file to apply 12 | # those changes. 13 | # 14 | # ------------------------------------------------------------------- 15 | # ----------------------------- END WARNING ------------------------- 16 | # ------------------------------------------------------------------- 17 | 18 | name: ci 19 | env: 20 | RUSTFLAGS: -Dwarnings 21 | on: 22 | pull_request: 23 | types: 24 | - opened 25 | - synchronize 26 | - reopened 27 | branches: 28 | - main 29 | push: 30 | branches: 31 | - main 32 | jobs: 33 | build: 34 | name: Build and Test 35 | runs-on: ubuntu-latest 36 | permissions: 37 | contents: read 38 | steps: 39 | - name: Checkout Code 40 | uses: actions/checkout@v4 41 | - name: Setup Rust Toolchain 42 | uses: actions-rust-lang/setup-rust-toolchain@v1 43 | with: 44 | toolchain: stable 45 | - name: Cargo Test 46 | run: cargo test --all-features --workspace 47 | - name: Cargo Bench 48 | run: cargo bench --workspace 49 | lint: 50 | name: Lint 51 | runs-on: ubuntu-latest 52 | permissions: 53 | contents: read 54 | steps: 55 | - name: Checkout Code 56 | uses: actions/checkout@v4 57 | - name: Setup Rust Toolchain 58 | uses: actions-rust-lang/setup-rust-toolchain@v1 59 | with: 60 | toolchain: nightly 61 | components: clippy, rustfmt 62 | - name: Cargo Fmt 63 | run: cargo +nightly fmt --all --check 64 | - name: Cargo Clippy 65 | run: cargo +nightly clippy --all-features --workspace -- -D warnings 66 | release: 67 | needs: 68 | - build 69 | - lint 70 | if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} 71 | name: Release 72 | runs-on: ubuntu-latest 73 | permissions: 74 | contents: write 75 | pull-requests: write 76 | packages: write 77 | env: 78 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 79 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 80 | steps: 81 | - name: Checkout Code 82 | uses: actions/checkout@v4 83 | - name: Release Plz 84 | uses: release-plz/action@v0.5 85 | with: 86 | command: release 87 | concurrency: 88 | group: release-${{github.ref}} 89 | cancel-in-progress: false 90 | release-pr: 91 | needs: 92 | - build 93 | - lint 94 | if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} 95 | name: Release Pr 96 | runs-on: ubuntu-latest 97 | permissions: 98 | contents: write 99 | pull-requests: write 100 | packages: write 101 | env: 102 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 103 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 104 | steps: 105 | - name: Checkout Code 106 | uses: actions/checkout@v4 107 | - name: Release Plz 108 | uses: release-plz/action@v0.5 109 | with: 110 | command: release-pr 111 | concurrency: 112 | group: release-${{github.ref}} 113 | cancel-in-progress: false 114 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | .vscode 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.3.1](https://github.com/tailcallhq/tailcall-chunk/compare/v0.3.0...v0.3.1) - 2024-11-26 11 | 12 | ### Other 13 | 14 | - use size hint when creating Chunk::Collect ([#28](https://github.com/tailcallhq/tailcall-chunk/pull/28)) 15 | 16 | ## [0.3.0](https://github.com/tailcallhq/tailcall-chunk/compare/v0.2.5...v0.3.0) - 2024-11-19 17 | 18 | ### Other 19 | 20 | - Update README.md 21 | 22 | ## [0.2.5](https://github.com/tailcallhq/tailcall-chunk/compare/v0.2.4...v0.2.5) - 2024-11-16 23 | 24 | ### Other 25 | 26 | - add documentation for lib 27 | 28 | ## [0.2.4](https://github.com/tailcallhq/tailcall-chunk/compare/v0.2.3...v0.2.4) - 2024-11-16 29 | 30 | ### Other 31 | 32 | - update README.md 33 | 34 | ## [0.2.3](https://github.com/tailcallhq/tailcall-chunk/compare/v0.2.2...v0.2.3) - 2024-11-16 35 | 36 | ### Other 37 | 38 | - drop module from ci.rs 39 | 40 | ## [0.2.2](https://github.com/tailcallhq/tailcall-chunk/compare/v0.2.1...v0.2.2) - 2024-11-16 41 | 42 | ### Other 43 | 44 | - Configure Renovate ([#2](https://github.com/tailcallhq/tailcall-chunk/pull/2)) 45 | 46 | ## [0.2.1](https://github.com/tailcallhq/tailcall-chunk/compare/v0.2.0...v0.2.1) - 2024-11-16 47 | 48 | ### Other 49 | 50 | - lint fixes 51 | 52 | ## [0.2.0](https://github.com/tailcallhq/tailcall-chunk/compare/v0.1.0...v0.2.0) - 2024-11-16 53 | 54 | ### Other 55 | 56 | - update readme badges 57 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anes" 16 | version = "0.1.6" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.10" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 25 | 26 | [[package]] 27 | name = "async-trait" 28 | version = "0.1.83" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" 31 | dependencies = [ 32 | "proc-macro2", 33 | "quote", 34 | "syn 2.0.87", 35 | ] 36 | 37 | [[package]] 38 | name = "autocfg" 39 | version = "1.4.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 42 | 43 | [[package]] 44 | name = "bumpalo" 45 | version = "3.16.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 48 | 49 | [[package]] 50 | name = "cast" 51 | version = "0.3.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 54 | 55 | [[package]] 56 | name = "cfg-if" 57 | version = "1.0.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 60 | 61 | [[package]] 62 | name = "ciborium" 63 | version = "0.2.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 66 | dependencies = [ 67 | "ciborium-io", 68 | "ciborium-ll", 69 | "serde", 70 | ] 71 | 72 | [[package]] 73 | name = "ciborium-io" 74 | version = "0.2.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 77 | 78 | [[package]] 79 | name = "ciborium-ll" 80 | version = "0.2.2" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 83 | dependencies = [ 84 | "ciborium-io", 85 | "half", 86 | ] 87 | 88 | [[package]] 89 | name = "clap" 90 | version = "4.5.21" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" 93 | dependencies = [ 94 | "clap_builder", 95 | ] 96 | 97 | [[package]] 98 | name = "clap_builder" 99 | version = "4.5.21" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" 102 | dependencies = [ 103 | "anstyle", 104 | "clap_lex", 105 | ] 106 | 107 | [[package]] 108 | name = "clap_lex" 109 | version = "0.7.3" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" 112 | 113 | [[package]] 114 | name = "criterion" 115 | version = "0.5.1" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" 118 | dependencies = [ 119 | "anes", 120 | "cast", 121 | "ciborium", 122 | "clap", 123 | "criterion-plot", 124 | "is-terminal", 125 | "itertools", 126 | "num-traits", 127 | "once_cell", 128 | "oorandom", 129 | "plotters", 130 | "rayon", 131 | "regex", 132 | "serde", 133 | "serde_derive", 134 | "serde_json", 135 | "tinytemplate", 136 | "walkdir", 137 | ] 138 | 139 | [[package]] 140 | name = "criterion-plot" 141 | version = "0.5.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 144 | dependencies = [ 145 | "cast", 146 | "itertools", 147 | ] 148 | 149 | [[package]] 150 | name = "crossbeam-deque" 151 | version = "0.8.5" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 154 | dependencies = [ 155 | "crossbeam-epoch", 156 | "crossbeam-utils", 157 | ] 158 | 159 | [[package]] 160 | name = "crossbeam-epoch" 161 | version = "0.9.18" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 164 | dependencies = [ 165 | "crossbeam-utils", 166 | ] 167 | 168 | [[package]] 169 | name = "crossbeam-utils" 170 | version = "0.8.20" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 173 | 174 | [[package]] 175 | name = "crunchy" 176 | version = "0.2.2" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 179 | 180 | [[package]] 181 | name = "darling" 182 | version = "0.20.10" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 185 | dependencies = [ 186 | "darling_core", 187 | "darling_macro", 188 | ] 189 | 190 | [[package]] 191 | name = "darling_core" 192 | version = "0.20.10" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 195 | dependencies = [ 196 | "fnv", 197 | "ident_case", 198 | "proc-macro2", 199 | "quote", 200 | "strsim", 201 | "syn 2.0.87", 202 | ] 203 | 204 | [[package]] 205 | name = "darling_macro" 206 | version = "0.20.10" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 209 | dependencies = [ 210 | "darling_core", 211 | "quote", 212 | "syn 2.0.87", 213 | ] 214 | 215 | [[package]] 216 | name = "derive_more" 217 | version = "1.0.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" 220 | dependencies = [ 221 | "derive_more-impl", 222 | ] 223 | 224 | [[package]] 225 | name = "derive_more-impl" 226 | version = "1.0.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" 229 | dependencies = [ 230 | "proc-macro2", 231 | "quote", 232 | "syn 2.0.87", 233 | ] 234 | 235 | [[package]] 236 | name = "derive_setters" 237 | version = "0.1.6" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "4e8ef033054e131169b8f0f9a7af8f5533a9436fadf3c500ed547f730f07090d" 240 | dependencies = [ 241 | "darling", 242 | "proc-macro2", 243 | "quote", 244 | "syn 2.0.87", 245 | ] 246 | 247 | [[package]] 248 | name = "either" 249 | version = "1.13.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 252 | 253 | [[package]] 254 | name = "equivalent" 255 | version = "1.0.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 258 | 259 | [[package]] 260 | name = "fnv" 261 | version = "1.0.7" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 264 | 265 | [[package]] 266 | name = "gh-workflow" 267 | version = "0.5.6" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "6ea61127f989ca4a152b22b48bb712e012a332f5720ebffb79d36b5f17d21db4" 270 | dependencies = [ 271 | "async-trait", 272 | "derive_more", 273 | "derive_setters", 274 | "gh-workflow-macros", 275 | "indexmap", 276 | "merge", 277 | "serde", 278 | "serde_json", 279 | "serde_yaml", 280 | "strum_macros", 281 | ] 282 | 283 | [[package]] 284 | name = "gh-workflow-macros" 285 | version = "0.5.6" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "3415ff89464a801cd240df6035bbd203960cdb6618f7de68c9e38eeadd8a0cf6" 288 | dependencies = [ 289 | "heck", 290 | "quote", 291 | "syn 2.0.87", 292 | ] 293 | 294 | [[package]] 295 | name = "gh-workflow-tailcall" 296 | version = "0.2.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "42f01a60e065d545d613b0ac1a22f8310308d608354bcafa81cb53a2f5746869" 299 | dependencies = [ 300 | "derive_setters", 301 | "gh-workflow", 302 | "heck", 303 | ] 304 | 305 | [[package]] 306 | name = "half" 307 | version = "2.4.1" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" 310 | dependencies = [ 311 | "cfg-if", 312 | "crunchy", 313 | ] 314 | 315 | [[package]] 316 | name = "hashbrown" 317 | version = "0.15.1" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" 320 | 321 | [[package]] 322 | name = "heck" 323 | version = "0.5.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 326 | 327 | [[package]] 328 | name = "hermit-abi" 329 | version = "0.4.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 332 | 333 | [[package]] 334 | name = "ident_case" 335 | version = "1.0.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 338 | 339 | [[package]] 340 | name = "indexmap" 341 | version = "2.6.0" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 344 | dependencies = [ 345 | "equivalent", 346 | "hashbrown", 347 | "serde", 348 | ] 349 | 350 | [[package]] 351 | name = "is-terminal" 352 | version = "0.4.13" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" 355 | dependencies = [ 356 | "hermit-abi", 357 | "libc", 358 | "windows-sys 0.52.0", 359 | ] 360 | 361 | [[package]] 362 | name = "itertools" 363 | version = "0.10.5" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 366 | dependencies = [ 367 | "either", 368 | ] 369 | 370 | [[package]] 371 | name = "itoa" 372 | version = "1.0.11" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 375 | 376 | [[package]] 377 | name = "js-sys" 378 | version = "0.3.72" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 381 | dependencies = [ 382 | "wasm-bindgen", 383 | ] 384 | 385 | [[package]] 386 | name = "libc" 387 | version = "0.2.164" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" 390 | 391 | [[package]] 392 | name = "log" 393 | version = "0.4.22" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 396 | 397 | [[package]] 398 | name = "memchr" 399 | version = "2.7.4" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 402 | 403 | [[package]] 404 | name = "merge" 405 | version = "0.1.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9" 408 | dependencies = [ 409 | "merge_derive", 410 | "num-traits", 411 | ] 412 | 413 | [[package]] 414 | name = "merge_derive" 415 | version = "0.1.0" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07" 418 | dependencies = [ 419 | "proc-macro-error", 420 | "proc-macro2", 421 | "quote", 422 | "syn 1.0.109", 423 | ] 424 | 425 | [[package]] 426 | name = "num-traits" 427 | version = "0.2.19" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 430 | dependencies = [ 431 | "autocfg", 432 | ] 433 | 434 | [[package]] 435 | name = "once_cell" 436 | version = "1.20.2" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 439 | 440 | [[package]] 441 | name = "oorandom" 442 | version = "11.1.4" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" 445 | 446 | [[package]] 447 | name = "plotters" 448 | version = "0.3.7" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" 451 | dependencies = [ 452 | "num-traits", 453 | "plotters-backend", 454 | "plotters-svg", 455 | "wasm-bindgen", 456 | "web-sys", 457 | ] 458 | 459 | [[package]] 460 | name = "plotters-backend" 461 | version = "0.3.7" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" 464 | 465 | [[package]] 466 | name = "plotters-svg" 467 | version = "0.3.7" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" 470 | dependencies = [ 471 | "plotters-backend", 472 | ] 473 | 474 | [[package]] 475 | name = "proc-macro-error" 476 | version = "1.0.4" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 479 | dependencies = [ 480 | "proc-macro-error-attr", 481 | "proc-macro2", 482 | "quote", 483 | "syn 1.0.109", 484 | "version_check", 485 | ] 486 | 487 | [[package]] 488 | name = "proc-macro-error-attr" 489 | version = "1.0.4" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 492 | dependencies = [ 493 | "proc-macro2", 494 | "quote", 495 | "version_check", 496 | ] 497 | 498 | [[package]] 499 | name = "proc-macro2" 500 | version = "1.0.89" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 503 | dependencies = [ 504 | "unicode-ident", 505 | ] 506 | 507 | [[package]] 508 | name = "quote" 509 | version = "1.0.37" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 512 | dependencies = [ 513 | "proc-macro2", 514 | ] 515 | 516 | [[package]] 517 | name = "rayon" 518 | version = "1.10.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 521 | dependencies = [ 522 | "either", 523 | "rayon-core", 524 | ] 525 | 526 | [[package]] 527 | name = "rayon-core" 528 | version = "1.12.1" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 531 | dependencies = [ 532 | "crossbeam-deque", 533 | "crossbeam-utils", 534 | ] 535 | 536 | [[package]] 537 | name = "regex" 538 | version = "1.11.1" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 541 | dependencies = [ 542 | "aho-corasick", 543 | "memchr", 544 | "regex-automata", 545 | "regex-syntax", 546 | ] 547 | 548 | [[package]] 549 | name = "regex-automata" 550 | version = "0.4.9" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 553 | dependencies = [ 554 | "aho-corasick", 555 | "memchr", 556 | "regex-syntax", 557 | ] 558 | 559 | [[package]] 560 | name = "regex-syntax" 561 | version = "0.8.5" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 564 | 565 | [[package]] 566 | name = "rustversion" 567 | version = "1.0.18" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 570 | 571 | [[package]] 572 | name = "ryu" 573 | version = "1.0.18" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 576 | 577 | [[package]] 578 | name = "same-file" 579 | version = "1.0.6" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 582 | dependencies = [ 583 | "winapi-util", 584 | ] 585 | 586 | [[package]] 587 | name = "serde" 588 | version = "1.0.215" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 591 | dependencies = [ 592 | "serde_derive", 593 | ] 594 | 595 | [[package]] 596 | name = "serde_derive" 597 | version = "1.0.215" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 600 | dependencies = [ 601 | "proc-macro2", 602 | "quote", 603 | "syn 2.0.87", 604 | ] 605 | 606 | [[package]] 607 | name = "serde_json" 608 | version = "1.0.132" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 611 | dependencies = [ 612 | "itoa", 613 | "memchr", 614 | "ryu", 615 | "serde", 616 | ] 617 | 618 | [[package]] 619 | name = "serde_yaml" 620 | version = "0.9.34+deprecated" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 623 | dependencies = [ 624 | "indexmap", 625 | "itoa", 626 | "ryu", 627 | "serde", 628 | "unsafe-libyaml", 629 | ] 630 | 631 | [[package]] 632 | name = "strsim" 633 | version = "0.11.1" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 636 | 637 | [[package]] 638 | name = "strum_macros" 639 | version = "0.26.4" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 642 | dependencies = [ 643 | "heck", 644 | "proc-macro2", 645 | "quote", 646 | "rustversion", 647 | "syn 2.0.87", 648 | ] 649 | 650 | [[package]] 651 | name = "syn" 652 | version = "1.0.109" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 655 | dependencies = [ 656 | "proc-macro2", 657 | "quote", 658 | "unicode-ident", 659 | ] 660 | 661 | [[package]] 662 | name = "syn" 663 | version = "2.0.87" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 666 | dependencies = [ 667 | "proc-macro2", 668 | "quote", 669 | "unicode-ident", 670 | ] 671 | 672 | [[package]] 673 | name = "tailcall-chunk" 674 | version = "0.3.1" 675 | dependencies = [ 676 | "criterion", 677 | "gh-workflow-tailcall", 678 | ] 679 | 680 | [[package]] 681 | name = "tinytemplate" 682 | version = "1.2.1" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 685 | dependencies = [ 686 | "serde", 687 | "serde_json", 688 | ] 689 | 690 | [[package]] 691 | name = "unicode-ident" 692 | version = "1.0.13" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 695 | 696 | [[package]] 697 | name = "unsafe-libyaml" 698 | version = "0.2.11" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 701 | 702 | [[package]] 703 | name = "version_check" 704 | version = "0.9.5" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 707 | 708 | [[package]] 709 | name = "walkdir" 710 | version = "2.5.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 713 | dependencies = [ 714 | "same-file", 715 | "winapi-util", 716 | ] 717 | 718 | [[package]] 719 | name = "wasm-bindgen" 720 | version = "0.2.95" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 723 | dependencies = [ 724 | "cfg-if", 725 | "once_cell", 726 | "wasm-bindgen-macro", 727 | ] 728 | 729 | [[package]] 730 | name = "wasm-bindgen-backend" 731 | version = "0.2.95" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 734 | dependencies = [ 735 | "bumpalo", 736 | "log", 737 | "once_cell", 738 | "proc-macro2", 739 | "quote", 740 | "syn 2.0.87", 741 | "wasm-bindgen-shared", 742 | ] 743 | 744 | [[package]] 745 | name = "wasm-bindgen-macro" 746 | version = "0.2.95" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 749 | dependencies = [ 750 | "quote", 751 | "wasm-bindgen-macro-support", 752 | ] 753 | 754 | [[package]] 755 | name = "wasm-bindgen-macro-support" 756 | version = "0.2.95" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 759 | dependencies = [ 760 | "proc-macro2", 761 | "quote", 762 | "syn 2.0.87", 763 | "wasm-bindgen-backend", 764 | "wasm-bindgen-shared", 765 | ] 766 | 767 | [[package]] 768 | name = "wasm-bindgen-shared" 769 | version = "0.2.95" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 772 | 773 | [[package]] 774 | name = "web-sys" 775 | version = "0.3.72" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" 778 | dependencies = [ 779 | "js-sys", 780 | "wasm-bindgen", 781 | ] 782 | 783 | [[package]] 784 | name = "winapi-util" 785 | version = "0.1.9" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 788 | dependencies = [ 789 | "windows-sys 0.59.0", 790 | ] 791 | 792 | [[package]] 793 | name = "windows-sys" 794 | version = "0.52.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 797 | dependencies = [ 798 | "windows-targets", 799 | ] 800 | 801 | [[package]] 802 | name = "windows-sys" 803 | version = "0.59.0" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 806 | dependencies = [ 807 | "windows-targets", 808 | ] 809 | 810 | [[package]] 811 | name = "windows-targets" 812 | version = "0.52.6" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 815 | dependencies = [ 816 | "windows_aarch64_gnullvm", 817 | "windows_aarch64_msvc", 818 | "windows_i686_gnu", 819 | "windows_i686_gnullvm", 820 | "windows_i686_msvc", 821 | "windows_x86_64_gnu", 822 | "windows_x86_64_gnullvm", 823 | "windows_x86_64_msvc", 824 | ] 825 | 826 | [[package]] 827 | name = "windows_aarch64_gnullvm" 828 | version = "0.52.6" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 831 | 832 | [[package]] 833 | name = "windows_aarch64_msvc" 834 | version = "0.52.6" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 837 | 838 | [[package]] 839 | name = "windows_i686_gnu" 840 | version = "0.52.6" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 843 | 844 | [[package]] 845 | name = "windows_i686_gnullvm" 846 | version = "0.52.6" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 849 | 850 | [[package]] 851 | name = "windows_i686_msvc" 852 | version = "0.52.6" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 855 | 856 | [[package]] 857 | name = "windows_x86_64_gnu" 858 | version = "0.52.6" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 861 | 862 | [[package]] 863 | name = "windows_x86_64_gnullvm" 864 | version = "0.52.6" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 867 | 868 | [[package]] 869 | name = "windows_x86_64_msvc" 870 | version = "0.52.6" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 873 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tailcall-chunk" 3 | version = "0.3.1" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description = "A Rust implementation of a persistent data structure for efficient append and concatenation operations." 7 | repository = "https://github.com/tailcallhq/tailcall-chunk" 8 | documentation = "https://docs.rs/tailcall-chunk" 9 | 10 | [dependencies] 11 | 12 | [dev-dependencies] 13 | gh-workflow-tailcall = "0.2.0" 14 | criterion = "0.5" 15 | 16 | [[bench]] 17 | name = "operations" 18 | harness = false 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chunk 2 | 3 | [![Crates.io Version](https://img.shields.io/crates/v/tailcall-chunk?style=flat-square)](https://crates.io/crates/tailcall-chunk) 4 | [![Documentation](https://img.shields.io/docsrs/tailcall-chunk?style=flat-square)](https://docs.rs/tailcall-chunk) 5 | [![Build Status](https://img.shields.io/github/actions/workflow/status/tailcallhq/tailcall-chunk/ci.yml?style=flat-square)](https://github.com/tailcallhq/tailcall-chunk/actions) 6 | [![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue?style=flat-square)](LICENSE) 7 | 8 | A Rust implementation of a persistent data structure that provides O(1) append and concatenation operations through structural sharing. 9 | 10 | - [Chunk](#chunk) 11 | - [Features](#features) 12 | - [Theoretical Background](#theoretical-background) 13 | - [Relationship to Finger Trees](#relationship-to-finger-trees) 14 | - [Performance Trade-offs](#performance-trade-offs) 15 | - [Installation](#installation) 16 | - [Quick Start](#quick-start) 17 | - [Detailed Usage](#detailed-usage) 18 | - [Working with Custom Types](#working-with-custom-types) 19 | - [Memory Efficiency](#memory-efficiency) 20 | - [Performance Characteristics](#performance-characteristics) 21 | - [Benchmark Comparison](#benchmark-comparison) 22 | - [Implementation Details](#implementation-details) 23 | - [Contributing](#contributing) 24 | - [License](#license) 25 | - [References](#references) 26 | 27 | ## Features 28 | 29 | - **O(1) Append/Prepend Operations**: Add elements to your chunk in constant time 30 | - **O(1) Concatenation**: Combine two chunks efficiently 31 | - **Immutable/Persistent**: All operations create new versions while preserving the original 32 | - **Memory Efficient**: Uses structural sharing via reference counting 33 | - **Safe Rust**: Implemented using 100% safe Rust 34 | 35 | ## Theoretical Background 36 | 37 | This implementation is inspired by the concepts presented in Hinze and Paterson's work on Finger Trees[^1], though simplified for our specific use case. While our implementation differs in structure, it shares similar performance goals and theoretical foundations. 38 | 39 | ### Relationship to Finger Trees 40 | 41 | Finger Trees are a functional data structure that supports: 42 | 43 | - Access to both ends in amortized constant time 44 | - Concatenation in logarithmic time 45 | - Persistence through structural sharing 46 | 47 | Our `Chunk` implementation achieves similar goals through a simplified approach: 48 | 49 | - We use `Append` nodes for constant-time additions 50 | - The `Concat` variant enables efficient concatenation 51 | - `Rc` (Reference Counting) provides persistence and structural sharing 52 | 53 | Like Finger Trees, our structure can be viewed as an extension of Okasaki's implicit deques[^2], but optimized for our specific use cases. While Finger Trees offer a more general-purpose solution with additional capabilities, our implementation focuses on providing: 54 | 55 | - Simpler implementation 56 | - More straightforward mental model 57 | - Specialized performance characteristics for append/concat operations 58 | 59 | ### Performance Trade-offs 60 | 61 | While Finger Trees achieve logarithmic time for concatenation, our implementation optimizes for constant-time operations through lazy evaluation. This means: 62 | 63 | - Append, Prepend and concatenation are always O(1) 64 | - The cost is deferred to when we need to materialize the sequence (via `as_vec()`) 65 | - Memory usage grows with the number of operations until materialization 66 | 67 | This trade-off is particularly beneficial in scenarios where: 68 | 69 | - Write operations need to be performed extensively 70 | - Multiple transformations are chained 71 | - Not all elements need to be materialized 72 | - Structural sharing can be leveraged across operations 73 | 74 | ## Installation 75 | 76 | Add this to your `Cargo.toml`: 77 | 78 | ```toml 79 | [dependencies] 80 | tailcall-chunk = "0.1.0" 81 | ``` 82 | 83 | ## Quick Start 84 | 85 | ```rust 86 | use chunk::Chunk; 87 | 88 | // Create a new chunk and append some elements 89 | let chunk1 = Chunk::default() 90 | .append(1) 91 | .append(2); 92 | 93 | // Create another chunk 94 | let chunk2 = Chunk::default() 95 | .append(3) 96 | .append(4); 97 | 98 | // Concatenate chunks in O(1) time 99 | let combined = chunk1.concat(chunk2); 100 | 101 | // Convert to vector when needed 102 | assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]); 103 | ``` 104 | 105 | ## Detailed Usage 106 | 107 | ### Working with Custom Types 108 | 109 | ```rust 110 | use chunk::Chunk; 111 | 112 | #[derive(Debug, PartialEq)] 113 | struct Person { 114 | name: String, 115 | age: u32, 116 | } 117 | 118 | let people = Chunk::default() 119 | .append(Person { 120 | name: "Alice".to_string(), 121 | age: 30 122 | }) 123 | .append(Person { 124 | name: "Bob".to_string(), 125 | age: 25 126 | }); 127 | 128 | // Access elements 129 | let people_vec = people.as_vec(); 130 | assert_eq!(people_vec[0].name, "Alice"); 131 | assert_eq!(people_vec[1].name, "Bob"); 132 | ``` 133 | 134 | ### Memory Efficiency 135 | 136 | The `Chunk` type uses structural sharing through reference counting (`Rc`), which means: 137 | 138 | - Appending or concatenating chunks doesn't copy the existing elements 139 | - Memory is automatically freed when no references remain 140 | - Multiple versions of the data structure can coexist efficiently 141 | 142 | ```rust 143 | use chunk::Chunk; 144 | 145 | let original = Chunk::default().append(1).append(2); 146 | let version1 = original.clone().append(3); // Efficient cloning 147 | let version2 = original.clone().append(4); // Both versions share data 148 | ``` 149 | 150 | ## Performance Characteristics 151 | 152 | | Operation | Time Complexity | Space Complexity | 153 | | --------------------- | --------------- | ---------------- | 154 | | `new()` | O(1) | O(1) | 155 | | `append()` | O(1) | O(1) | 156 | | `concat()` | O(1) | O(1) | 157 | | `transform()` | O(1) | O(1) | 158 | | `transform_flatten()` | O(1) | O(1) | 159 | | `as_vec()` | O(n) | O(n) | 160 | | `clone()` | O(1) | O(1) | 161 | 162 | ### Benchmark Comparison 163 | 164 | The following table compares the actual performance of Chunk vs Vector operations based on [our benchmarks](benches/operations.rs) (lower is better): 165 | 166 | | Operation | Chunk Performance | Vector Performance | Faster | 167 | | --------- | ----------------- | ------------------ | ------------------------------ | 168 | | Append | 604.02 µs | 560.58 µs | Vec is `~1.08` times faster | 169 | | Prepend | 1.63 ms | 21.95 ms | Chunk is `~13.47` times faster | 170 | | Concat | 71.17 ns | 494.45 µs | Chunk is `~6,947` times faster | 171 | | Clone | 4.16 ns | 1.10 µs | Chunk is `~264` times faster | 172 | 173 | Note: These benchmarks represent specific test scenarios and actual performance may vary based on usage patterns. Chunk operations are optimized for bulk operations and scenarios where structural sharing provides benefits. View the complete benchmark code and results in our [operations.rs](benches/operations.rs) benchmark file. 174 | 175 | ## Implementation Details 176 | 177 | The `Chunk` type is implemented as an enum with four variants: 178 | 179 | - `Empty`: Represents an empty chunk 180 | - `Single`: Represents a chunk with a single element 181 | - `Concat`: Represents the concatenation of two chunks 182 | - `TransformFlatten`: Represents a lazy transformation and flattening of elements 183 | 184 | The data structure achieves its performance characteristics through: 185 | 186 | - Structural sharing using `Rc` 187 | - Lazy evaluation of concatenation and transformations 188 | - Immutable operations that preserve previous versions 189 | 190 | ## Contributing 191 | 192 | We welcome contributions! Here's how you can help: 193 | 194 | 1. Fork the repository 195 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 196 | 3. Commit your changes (`git commit -am 'Add some amazing feature'`) 197 | 4. Push to the branch (`git push origin feature/amazing-feature`) 198 | 5. Open a Pull Request 199 | 200 | Please make sure to: 201 | 202 | - Update documentation 203 | - Add tests for new features 204 | - Follow the existing code style 205 | - Update the README.md if needed 206 | 207 | ## License 208 | 209 | This project is licensed under either of 210 | 211 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 212 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 213 | 214 | at your option. 215 | 216 | ## References 217 | 218 | [^1]: Ralf Hinze and Ross Paterson. "Finger Trees: A Simple General-purpose Data Structure", Journal of Functional Programming 16(2):197-217, 2006. 219 | [^2]: Chris Okasaki. "Purely Functional Data Structures", Cambridge University Press, 1998. 220 | -------------------------------------------------------------------------------- /benches/operations.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use tailcall_chunk::Chunk; 3 | 4 | const N: usize = 10000; 5 | 6 | fn bench_operations(c: &mut Criterion) { 7 | // Benchmark append operations 8 | c.benchmark_group("append") 9 | .bench_function("chunk_append", |b| { 10 | b.iter(|| { 11 | let mut chunk = Chunk::default(); 12 | for i in 0..10000 { 13 | chunk = chunk.append(i.to_string()); 14 | } 15 | 16 | black_box(chunk); 17 | }) 18 | }) 19 | .bench_function("vec_append", |b| { 20 | b.iter(|| { 21 | let mut vec = Vec::new(); 22 | for i in 0..10000 { 23 | vec.push(i.to_string()); 24 | } 25 | black_box(vec); 26 | }) 27 | }); 28 | 29 | // Benchmark prepend operations 30 | c.benchmark_group("prepend") 31 | .bench_function("chunk_prepend", |b| { 32 | b.iter(|| { 33 | let mut chunk = Chunk::default(); 34 | for i in 0..10000 { 35 | chunk = chunk.prepend(i.to_string()); 36 | } 37 | black_box(chunk); 38 | }) 39 | }) 40 | .bench_function("vec_prepend", |b| { 41 | b.iter(|| { 42 | let mut vec = Vec::new(); 43 | for i in 0..10000 { 44 | vec.insert(0, i.to_string()); 45 | } 46 | black_box(vec); 47 | }) 48 | }); 49 | 50 | // Benchmark concat operations 51 | c.benchmark_group("concat") 52 | .bench_function("chunk_concat", |b| { 53 | let chunk1: Chunk<_> = (0..5000).map(|i| i.to_string()).collect(); 54 | let chunk2: Chunk<_> = (5000..10000).map(|i| i.to_string()).collect(); 55 | b.iter(|| { 56 | black_box(chunk1.clone().concat(chunk2.clone())); 57 | }) 58 | }) 59 | .bench_function("vec_concat", |b| { 60 | let vec1: Vec<_> = (0..5000).map(|i| i.to_string()).collect(); 61 | let vec2: Vec<_> = (5000..10000).map(|i| i.to_string()).collect(); 62 | b.iter(|| { 63 | let mut result = vec1.clone(); 64 | result.extend(vec2.iter().cloned()); 65 | black_box(result) 66 | }) 67 | }); 68 | 69 | // Benchmark clone operations 70 | c.benchmark_group("clone") 71 | .bench_function("chunk_clone", |b| { 72 | let chunk: Chunk<_> = (0..10000).collect(); 73 | b.iter(|| { 74 | black_box(chunk.clone()); 75 | }) 76 | }) 77 | .bench_function("vec_clone", |b| { 78 | let vec: Vec<_> = (0..10000).collect(); 79 | b.iter(|| { 80 | black_box(vec.clone()); 81 | }) 82 | }); 83 | 84 | // Benchmark from_iter operation 85 | c.benchmark_group("from_iter") 86 | .bench_function("Chunk", |b| { 87 | b.iter(|| Chunk::from_iter(0..N)); 88 | }) 89 | .bench_function("Vec", |b| b.iter(|| Vec::from_iter(0..N))); 90 | } 91 | 92 | criterion_group!(benches, bench_operations); 93 | criterion_main!(benches); 94 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/chunk.rs: -------------------------------------------------------------------------------- 1 | //! A Rust implementation of a persistent data structure for efficient append and concatenation operations. 2 | //! 3 | //! This crate provides the [`Chunk`] type, which implements a persistent data structure 4 | //! that allows O(1) append and concatenation operations through structural sharing. 5 | //! 6 | //! # Features 7 | //! - O(1) append operations 8 | //! - O(1) concatenation operations 9 | //! - Immutable/persistent data structure 10 | //! - Memory efficient through structural sharing 11 | //! 12 | //! # Example 13 | //! ``` 14 | //! use tailcall_chunk::Chunk; 15 | //! 16 | //! let chunk1 = Chunk::default().append(1).append(2); 17 | //! let chunk2 = Chunk::default().append(3).append(4); 18 | //! let combined = chunk1.concat(chunk2); 19 | //! 20 | //! assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]); 21 | //! ``` 22 | 23 | use std::{cell::RefCell, rc::Rc}; 24 | 25 | /// A persistent data structure that provides efficient append and concatenation operations. 26 | /// 27 | /// # Overview 28 | /// `Chunk` is an immutable data structure that allows O(1) complexity for append and 29 | /// concatenation operations through structural sharing. It uses [`Rc`] (Reference Counting) 30 | /// for efficient memory management. 31 | /// 32 | /// # Performance 33 | /// - Append operation: O(1) 34 | /// - Concatenation operation: O(1) 35 | /// - Converting to Vec: O(n) 36 | /// 37 | /// # Implementation Details 38 | /// The data structure is implemented as an enum with three variants: 39 | /// - `Empty`: Represents an empty chunk 40 | /// - `Append`: Represents a single element appended to another chunk 41 | /// - `Concat`: Represents the concatenation of two chunks 42 | /// 43 | /// # Examples 44 | /// ``` 45 | /// use tailcall_chunk::Chunk; 46 | /// 47 | /// let mut chunk = Chunk::default(); 48 | /// chunk = chunk.append(1); 49 | /// chunk = chunk.append(2); 50 | /// 51 | /// let other_chunk = Chunk::default().append(3).append(4); 52 | /// let combined = chunk.concat(other_chunk); 53 | /// 54 | /// assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]); 55 | /// ``` 56 | /// 57 | /// # References 58 | /// - [Persistent Data Structures](https://en.wikipedia.org/wiki/Persistent_data_structure) 59 | /// - [Structural Sharing](https://hypirion.com/musings/understanding-persistent-vector-pt-1) 60 | #[derive(Clone)] 61 | pub enum Chunk { 62 | /// Represents an empty chunk with no elements 63 | Empty, 64 | /// Represents a chunk containing exactly one element 65 | Single(A), 66 | /// Represents the concatenation of two chunks, enabling O(1) concatenation 67 | Concat(Rc>, Rc>), 68 | /// Represents a collection of elements 69 | Collect(Rc>>), 70 | /// Represents a lazy transformation that flattens elements 71 | TransformFlatten(Rc>, Rc Chunk>), 72 | } 73 | 74 | impl Default for Chunk { 75 | /// Creates a new empty chunk. 76 | /// 77 | /// This is equivalent to using [`Chunk::Empty`]. 78 | fn default() -> Self { 79 | Chunk::Empty 80 | } 81 | } 82 | 83 | impl Chunk { 84 | /// Creates a new chunk containing a single element. 85 | /// 86 | /// # Arguments 87 | /// * `a` - The element to store in the chunk 88 | /// 89 | /// # Examples 90 | /// ``` 91 | /// use tailcall_chunk::Chunk; 92 | /// 93 | /// let chunk: Chunk = Chunk::new(100); 94 | /// assert!(!chunk.is_null()); 95 | /// ``` 96 | pub fn new(a: A) -> Self { 97 | Chunk::Single(a) 98 | } 99 | 100 | /// Returns `true` if the chunk is empty. 101 | /// 102 | /// # Examples 103 | /// ``` 104 | /// use tailcall_chunk::Chunk; 105 | /// 106 | /// let chunk: Chunk = Chunk::default(); 107 | /// assert!(chunk.is_null()); 108 | /// 109 | /// let non_empty = chunk.append(42); 110 | /// assert!(!non_empty.is_null()); 111 | /// ``` 112 | pub fn is_null(&self) -> bool { 113 | match self { 114 | Chunk::Empty => true, 115 | Chunk::Collect(vec) => vec.borrow().is_empty(), 116 | _ => false, 117 | } 118 | } 119 | 120 | /// Append a new element to the chunk. 121 | /// 122 | /// This operation has O(1) complexity as it creates a new `Append` variant 123 | /// that references the existing chunk through an [`Rc`]. 124 | /// 125 | /// # Examples 126 | /// ``` 127 | /// use tailcall_chunk::Chunk; 128 | /// 129 | /// let chunk = Chunk::default().append(1).append(2); 130 | /// assert_eq!(chunk.as_vec(), vec![1, 2]); 131 | /// ``` 132 | pub fn append(self, a: A) -> Self { 133 | self.concat(Chunk::new(a)) 134 | } 135 | 136 | /// Prepend a new element to the beginning of the chunk. 137 | /// 138 | /// This operation has O(1) complexity as it creates a new `Concat` variant 139 | /// that references the existing chunk through an [`Rc`]. 140 | /// 141 | /// # Examples 142 | /// ``` 143 | /// use tailcall_chunk::Chunk; 144 | /// 145 | /// let chunk = Chunk::default().prepend(1).prepend(2); 146 | /// assert_eq!(chunk.as_vec(), vec![2, 1]); 147 | /// ``` 148 | pub fn prepend(self, a: A) -> Self { 149 | if self.is_null() { 150 | Chunk::new(a) 151 | } else { 152 | Chunk::new(a).concat(self) 153 | } 154 | } 155 | 156 | /// Concatenates this chunk with another chunk. 157 | /// 158 | /// This operation has O(1) complexity as it creates a new `Concat` variant 159 | /// that references both chunks through [`Rc`]s. 160 | /// 161 | /// # Performance Optimization 162 | /// If either chunk is empty, returns the other chunk instead of creating 163 | /// a new `Concat` variant. 164 | /// 165 | /// # Examples 166 | /// ``` 167 | /// use tailcall_chunk::Chunk; 168 | /// 169 | /// let chunk1 = Chunk::default().append(1).append(2); 170 | /// let chunk2 = Chunk::default().append(3).append(4); 171 | /// let combined = chunk1.concat(chunk2); 172 | /// assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]); 173 | /// ``` 174 | pub fn concat(self, other: Chunk) -> Chunk { 175 | match (self, other) { 176 | // Handle null cases 177 | (Chunk::Empty, other) => other, 178 | (this, Chunk::Empty) => this, 179 | (Chunk::Single(a), Chunk::Single(b)) => { 180 | Chunk::Collect(Rc::new(RefCell::new(vec![a, b]))) 181 | } 182 | (Chunk::Collect(vec), Chunk::Single(a)) => { 183 | if Rc::strong_count(&vec) == 1 { 184 | // Only clone if there are no other references 185 | vec.borrow_mut().push(a); 186 | Chunk::Collect(vec) 187 | } else { 188 | Chunk::Concat(Rc::new(Chunk::Collect(vec)), Rc::new(Chunk::Single(a))) 189 | } 190 | } 191 | // Handle all other cases with Concat 192 | (this, that) => Chunk::Concat(Rc::new(this), Rc::new(that)), 193 | } 194 | } 195 | 196 | /// Transforms each element in the chunk using the provided function. 197 | /// 198 | /// This method creates a lazy representation of the transformation without actually 199 | /// performing it. The transformation is only executed when [`as_vec`](Chunk::as_vec) 200 | /// or [`as_vec_mut`](Chunk::as_vec_mut) is called. 201 | /// 202 | /// # Performance 203 | /// - Creating the transformation: O(1) 204 | /// - Executing the transformation (during [`as_vec`](Chunk::as_vec)): O(n) 205 | /// 206 | /// # Arguments 207 | /// * `f` - A function that takes a reference to an element of type `A` and returns 208 | /// a new element of type `A` 209 | /// 210 | /// # Examples 211 | /// ``` 212 | /// use tailcall_chunk::Chunk; 213 | /// 214 | /// let chunk = Chunk::default().append(1).append(2).append(3); 215 | /// // This operation is O(1) and doesn't actually transform the elements 216 | /// let doubled = chunk.transform(|x| x * 2); 217 | /// // The transformation happens here, when we call as_vec() 218 | /// assert_eq!(doubled.as_vec(), vec![2, 4, 6]); 219 | /// ``` 220 | pub fn transform(self, f: impl Fn(A) -> A + 'static) -> Self { 221 | self.transform_flatten(move |a| Chunk::new(f(a))) 222 | } 223 | 224 | /// Materializes a chunk by converting it into a collected form. 225 | /// 226 | /// This method evaluates any lazy transformations and creates a new chunk containing 227 | /// all elements in a `Collect` variant. This can be useful for performance when you 228 | /// plan to reuse the chunk multiple times, as it prevents re-evaluation of transformations. 229 | /// 230 | /// # Performance 231 | /// - Time complexity: O(n) where n is the number of elements 232 | /// - Space complexity: O(n) as it creates a new vector containing all elements 233 | /// 234 | /// # Examples 235 | /// ``` 236 | /// use tailcall_chunk::Chunk; 237 | /// 238 | /// let chunk = Chunk::default() 239 | /// .append(1) 240 | /// .append(2) 241 | /// .transform(|x| x * 2); // Lazy transformation 242 | /// 243 | /// // Materialize the chunk to evaluate the transformation once 244 | /// let materialized = chunk.materialize(); 245 | /// 246 | /// assert_eq!(materialized.as_vec(), vec![2, 4]); 247 | /// ``` 248 | pub fn materialize(self) -> Chunk 249 | where 250 | A: Clone, 251 | { 252 | Chunk::Collect(Rc::new(RefCell::new(self.as_vec()))) 253 | } 254 | 255 | /// Transforms each element in the chunk into a new chunk and flattens the result. 256 | /// 257 | /// This method creates a lazy representation of the transformation without actually 258 | /// performing it. The transformation is only executed when [`as_vec`](Chunk::as_vec) 259 | /// or [`as_vec_mut`](Chunk::as_vec_mut) is called. 260 | /// 261 | /// # Performance 262 | /// - Creating the transformation: O(1) 263 | /// - Executing the transformation (during [`as_vec`](Chunk::as_vec)): O(n) 264 | /// 265 | /// # Arguments 266 | /// * `f` - A function that takes an element of type `A` and returns 267 | /// a new `Chunk` 268 | /// 269 | /// # Examples 270 | /// ``` 271 | /// use tailcall_chunk::Chunk; 272 | /// 273 | /// let chunk = Chunk::default().append(1).append(2); 274 | /// // Transform each number x into a chunk containing [x, x+1] 275 | /// let expanded = chunk.transform_flatten(|x| { 276 | /// Chunk::default().append(x).append(x + 1) 277 | /// }); 278 | /// assert_eq!(expanded.as_vec(), vec![1, 2, 2, 3]); 279 | /// ``` 280 | pub fn transform_flatten(self, f: impl Fn(A) -> Chunk + 'static) -> Self { 281 | Chunk::TransformFlatten(Rc::new(self), Rc::new(f)) 282 | } 283 | 284 | /// Converts the chunk into a vector of references to its elements. 285 | /// 286 | /// This operation has O(n) complexity where n is the number of elements 287 | /// in the chunk. 288 | /// 289 | /// # Examples 290 | /// ``` 291 | /// use tailcall_chunk::Chunk; 292 | /// 293 | /// let chunk = Chunk::default().append(1).append(2).append(3); 294 | /// assert_eq!(chunk.as_vec(), vec![1, 2, 3]); 295 | /// ``` 296 | pub fn as_vec(&self) -> Vec 297 | where 298 | A: Clone, 299 | { 300 | let mut vec = Vec::new(); 301 | self.as_vec_mut(&mut vec); 302 | vec 303 | } 304 | 305 | /// Helper method that populates a vector with references to the chunk's elements. 306 | /// 307 | /// This method is used internally by [`as_vec`](Chunk::as_vec) to avoid 308 | /// allocating multiple vectors during the traversal. 309 | /// 310 | /// # Arguments 311 | /// * `buf` - A mutable reference to a vector that will be populated with 312 | /// references to the chunk's elements 313 | pub fn as_vec_mut(&self, buf: &mut Vec) 314 | where 315 | A: Clone, 316 | { 317 | match self { 318 | Chunk::Empty => {} 319 | Chunk::Single(a) => { 320 | buf.push(a.clone()); 321 | } 322 | Chunk::Concat(a, b) => { 323 | a.as_vec_mut(buf); 324 | b.as_vec_mut(buf); 325 | } 326 | Chunk::TransformFlatten(a, f) => { 327 | let mut tmp = Vec::new(); 328 | a.as_vec_mut(&mut tmp); 329 | for elem in tmp.into_iter() { 330 | f(elem).as_vec_mut(buf); 331 | } 332 | } 333 | Chunk::Collect(vec) => { 334 | buf.extend(vec.borrow().iter().cloned()); 335 | } 336 | } 337 | } 338 | } 339 | 340 | impl FromIterator for Chunk { 341 | /// Creates a chunk from an iterator. 342 | /// 343 | /// # Examples 344 | /// ``` 345 | /// use tailcall_chunk::Chunk; 346 | /// 347 | /// let vec = vec![1, 2, 3]; 348 | /// let chunk: Chunk<_> = vec.into_iter().collect(); 349 | /// assert_eq!(chunk.as_vec(), vec![1, 2, 3]); 350 | /// ``` 351 | fn from_iter>(iter: T) -> Self { 352 | let vec: Vec<_> = iter.into_iter().collect(); 353 | 354 | Chunk::Collect(Rc::new(RefCell::new(vec))) 355 | } 356 | } 357 | 358 | #[cfg(test)] 359 | mod tests { 360 | use super::*; 361 | 362 | #[test] 363 | fn test_new() { 364 | let chunk: Chunk = Chunk::default(); 365 | assert!(chunk.is_null()); 366 | } 367 | 368 | #[test] 369 | fn test_default() { 370 | let chunk: Chunk = Chunk::default(); 371 | assert!(chunk.is_null()); 372 | } 373 | 374 | #[test] 375 | fn test_is_null() { 376 | let empty: Chunk = Chunk::default(); 377 | assert!(empty.is_null()); 378 | 379 | let non_empty = empty.append(1); 380 | assert!(!non_empty.is_null()); 381 | } 382 | 383 | #[test] 384 | fn test_append() { 385 | let chunk = Chunk::default().append(1).append(2).append(3); 386 | assert_eq!(chunk.as_vec(), vec![1, 2, 3]); 387 | 388 | // Test that original chunk remains unchanged (persistence) 389 | let chunk1 = Chunk::default().append(1); 390 | let chunk2 = chunk1.clone().append(2); 391 | assert_eq!(chunk1.as_vec(), vec![1]); 392 | assert_eq!(chunk2.as_vec(), vec![1, 2]); 393 | } 394 | 395 | #[test] 396 | fn test_concat() { 397 | let chunk1 = Chunk::default().append(1).append(2); 398 | let chunk2 = Chunk::default().append(3).append(4); 399 | let combined = chunk1.clone().concat(chunk2.clone()); 400 | 401 | assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]); 402 | 403 | // Test concatenation with empty chunks 404 | let empty = Chunk::default(); 405 | assert_eq!( 406 | empty.clone().concat(chunk1.clone()).as_vec(), 407 | chunk1.as_vec() 408 | ); 409 | assert_eq!( 410 | chunk1.clone().concat(empty.clone()).as_vec(), 411 | chunk1.as_vec() 412 | ); 413 | assert_eq!(empty.clone().concat(empty).as_vec(), Vec::::new()); 414 | } 415 | 416 | #[test] 417 | fn test_as_vec() { 418 | // Test empty chunk 419 | let empty: Chunk = Chunk::default(); 420 | assert_eq!(empty.as_vec(), Vec::::new()); 421 | 422 | // Test single element 423 | let single = Chunk::default().append(42); 424 | assert_eq!(single.as_vec(), vec![42]); 425 | 426 | // Test multiple elements 427 | let multiple = Chunk::default().append(1).append(2).append(3); 428 | assert_eq!(multiple.as_vec(), vec![1, 2, 3]); 429 | 430 | // Test complex structure with concatenation 431 | let chunk1 = Chunk::default().append(1).append(2); 432 | let chunk2 = Chunk::default().append(3).append(4); 433 | let complex = chunk1.concat(chunk2); 434 | assert_eq!(complex.as_vec(), vec![1, 2, 3, 4]); 435 | } 436 | 437 | #[test] 438 | fn test_structural_sharing() { 439 | let chunk1 = Chunk::default().append(1).append(2); 440 | let chunk2 = chunk1.clone().append(3); 441 | let chunk3 = chunk1.clone().append(4); 442 | 443 | // Verify that modifications create new structures while preserving the original 444 | assert_eq!(chunk1.as_vec(), vec![1, 2]); 445 | assert_eq!(chunk2.as_vec(), vec![1, 2, 3]); 446 | assert_eq!(chunk3.as_vec(), vec![1, 2, 4]); 447 | } 448 | 449 | #[test] 450 | fn test_with_different_types() { 451 | // Test with strings 452 | let string_chunk = Chunk::default() 453 | .append(String::from("hello")) 454 | .append(String::from("world")); 455 | assert_eq!(string_chunk.as_vec().len(), 2); 456 | 457 | // Test with floating point numbers - using standard constants 458 | let float_chunk = Chunk::default() 459 | .append(std::f64::consts::PI) 460 | .append(std::f64::consts::E); 461 | assert_eq!( 462 | float_chunk.as_vec(), 463 | vec![std::f64::consts::PI, std::f64::consts::E] 464 | ); 465 | 466 | // Test with boolean values 467 | let bool_chunk = Chunk::default().append(true).append(false).append(true); 468 | assert_eq!(bool_chunk.as_vec(), vec![true, false, true]); 469 | } 470 | 471 | #[test] 472 | fn test_transform() { 473 | // Test transform on empty chunk 474 | let empty: Chunk = Chunk::default(); 475 | let transformed_empty = empty.transform(|x| x * 2); 476 | assert_eq!(transformed_empty.as_vec(), Vec::::new()); 477 | 478 | // Test transform on single element 479 | let single = Chunk::default().append(5); 480 | let doubled = single.transform(|x| x * 2); 481 | assert_eq!(doubled.as_vec(), vec![10]); 482 | 483 | // Test transform on multiple elements 484 | let multiple = Chunk::default().append(1).append(2).append(3); 485 | let doubled = multiple.transform(|x| x * 2); 486 | assert_eq!(doubled.as_vec(), vec![2, 4, 6]); 487 | 488 | // Test transform with string manipulation 489 | let string_chunk = Chunk::default() 490 | .append(String::from("hello")) 491 | .append(String::from("world")); 492 | let uppercase = string_chunk.transform(|s| s.to_uppercase()); 493 | assert_eq!(uppercase.as_vec(), vec!["HELLO", "WORLD"]); 494 | 495 | // Test chaining multiple transforms 496 | let numbers = Chunk::default().append(1).append(2).append(3); 497 | let result = numbers 498 | .transform(|x| x * 2) 499 | .transform(|x| x + 1) 500 | .transform(|x| x * 3); 501 | assert_eq!(result.as_vec(), vec![9, 15, 21]); 502 | } 503 | 504 | #[test] 505 | fn test_transform_flatten() { 506 | // Test transform_flatten on empty chunk 507 | let empty: Chunk = Chunk::default(); 508 | let transformed_empty = empty.transform_flatten(|x| Chunk::new(x * 2)); 509 | assert_eq!(transformed_empty.as_vec(), Vec::::new()); 510 | 511 | // Test transform_flatten on single element 512 | let single = Chunk::default().append(5); 513 | let doubled = single.transform_flatten(|x| Chunk::new(x * 2)); 514 | assert_eq!(doubled.as_vec(), vec![10]); 515 | 516 | // Test expanding each element into multiple elements 517 | let numbers = Chunk::default().append(1).append(2); 518 | let expanded = numbers.transform_flatten(|x| Chunk::default().append(x + 1).append(x)); 519 | assert_eq!(expanded.as_vec(), vec![2, 1, 3, 2]); 520 | 521 | // Test with nested chunks 522 | let chunk = Chunk::default().append(1).append(2).append(3); 523 | let nested = chunk.transform_flatten(|x| { 524 | if x % 2 == 0 { 525 | // Even numbers expand to [x, x+1] 526 | Chunk::default().append(x).append(x + 1) 527 | } else { 528 | // Odd numbers expand to [x] 529 | Chunk::new(x) 530 | } 531 | }); 532 | assert_eq!(nested.as_vec(), vec![1, 2, 3, 3]); 533 | 534 | // Test chaining transform_flatten operations 535 | let numbers = Chunk::default().append(1).append(2); 536 | let result = numbers 537 | .transform_flatten(|x| Chunk::default().append(x).append(x)) 538 | .transform_flatten(|x| Chunk::default().append(x).append(x + 1)); 539 | assert_eq!(result.as_vec(), vec![1, 2, 1, 2, 2, 3, 2, 3]); 540 | 541 | // Test with empty chunk results 542 | let chunk = Chunk::default().append(1).append(2); 543 | let filtered = chunk.transform_flatten(|x| { 544 | if x % 2 == 0 { 545 | Chunk::new(x) 546 | } else { 547 | Chunk::default() // Empty chunk for odd numbers 548 | } 549 | }); 550 | assert_eq!(filtered.as_vec(), vec![2]); 551 | } 552 | 553 | #[test] 554 | fn test_prepend() { 555 | let chunk = Chunk::default().prepend(1).prepend(2).prepend(3); 556 | assert_eq!(chunk.as_vec(), vec![3, 2, 1]); 557 | 558 | // Test that original chunk remains unchanged (persistence) 559 | let chunk1 = Chunk::default().prepend(1); 560 | let chunk2 = chunk1.clone().prepend(2); 561 | assert_eq!(chunk1.as_vec(), vec![1]); 562 | assert_eq!(chunk2.as_vec(), vec![2, 1]); 563 | 564 | // Test mixing prepend and append 565 | let mixed = Chunk::default() 566 | .prepend(1) // [1] 567 | .append(2) // [1, 2] 568 | .prepend(3); // [3, 1, 2] 569 | assert_eq!(mixed.as_vec(), vec![3, 1, 2]); 570 | } 571 | 572 | #[test] 573 | fn test_from_iterator() { 574 | // Test collecting from an empty iterator 575 | let empty_vec: Vec = vec![]; 576 | let empty_chunk: Chunk = empty_vec.into_iter().collect(); 577 | assert!(empty_chunk.is_null()); 578 | 579 | // Test collecting from a vector 580 | let vec = vec![1, 2, 3]; 581 | let chunk: Chunk<_> = vec.into_iter().collect(); 582 | assert_eq!(chunk.as_vec(), vec![1, 2, 3]); 583 | 584 | // Test collecting from a range 585 | let range_chunk: Chunk<_> = (1..=5).collect(); 586 | assert_eq!(range_chunk.as_vec(), vec![1, 2, 3, 4, 5]); 587 | 588 | // Test collecting from map iterator 589 | let doubled: Chunk<_> = vec![1, 2, 3].into_iter().map(|x| x * 2).collect(); 590 | assert_eq!(doubled.as_vec(), vec![2, 4, 6]); 591 | } 592 | 593 | #[test] 594 | fn test_concat_optimization() { 595 | // Create a collected chunk 596 | let collected: Chunk = vec![1, 2, 3].into_iter().collect(); 597 | 598 | // Concat a single element 599 | let result = collected.concat(Chunk::Single(4)); 600 | 601 | // Verify the result 602 | assert_eq!(result.as_vec(), vec![1, 2, 3, 4]); 603 | 604 | // Verify it's still a Collect variant (not a Concat) 605 | match result { 606 | Chunk::Collect(_) => (), // This is what we want 607 | _ => panic!("Expected Collect variant after optimization"), 608 | } 609 | } 610 | } 611 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A Rust implementation of a persistent data structure that provides O(1) append and concatenation operations 2 | //! through structural sharing. 3 | //! 4 | //! # Overview 5 | //! `Chunk` is a persistent data structure that offers: 6 | //! - **O(1) Append Operations**: Add elements to your chunk in constant time 7 | //! - **O(1) Concatenation**: Combine two chunks efficiently 8 | //! - **Immutable/Persistent**: All operations create new versions while preserving the original 9 | //! - **Memory Efficient**: Uses structural sharing via reference counting 10 | //! - **Safe Rust**: Implemented using 100% safe Rust 11 | //! 12 | //! # Theoretical Background 13 | //! 14 | //! This implementation is inspired by the concepts presented in Hinze and Paterson's work on 15 | //! [Finger Trees](https://en.wikipedia.org/wiki/Finger_tree), though simplified for our specific use case. 16 | //! While our implementation differs in structure, it shares similar performance goals and theoretical foundations. 17 | //! 18 | //! ## Relationship to Finger Trees 19 | //! 20 | //! Finger Trees are a functional data structure that supports: 21 | //! - Access to both ends in amortized constant time 22 | //! - Concatenation in logarithmic time 23 | //! - Persistence through structural sharing 24 | //! 25 | //! Our `Chunk` implementation achieves similar goals through a simplified approach: 26 | //! - We use `Append` nodes for constant-time additions 27 | //! - The `Concat` variant enables efficient concatenation 28 | //! - `Rc` (Reference Counting) provides persistence and structural sharing 29 | //! 30 | //! # Example Usage 31 | //! ```rust 32 | //! use tailcall_chunk::Chunk; 33 | //! 34 | //! // Create a new chunk and append some elements 35 | //! let chunk1 = Chunk::default() 36 | //! .append(1) 37 | //! .append(2); 38 | //! 39 | //! // Create another chunk 40 | //! let chunk2 = Chunk::default() 41 | //! .append(3) 42 | //! .append(4); 43 | //! 44 | //! // Concatenate chunks in O(1) time 45 | //! let combined = chunk1.concat(chunk2); 46 | //! 47 | //! // Convert to vector when needed 48 | //! assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]); 49 | //! ``` 50 | //! 51 | //! # Performance Characteristics 52 | //! 53 | //! ## Time Complexity Analysis 54 | //! 55 | //! | Operation | Worst Case | Amortized | Space | 56 | //! | --------------------- | ---------- | ------------ | ------------ | 57 | //! | `new()` | O(1) | O(1) | O(1) | 58 | //! | `append()` | O(1) | O(1) | O(1) | 59 | //! | `concat()` | O(1) | O(1) | O(1) | 60 | //! | `transform()` | O(1) | O(1) | O(1) | 61 | //! | `transform_flatten()` | O(1) | O(1) | O(1) | 62 | //! | `as_vec()` | O(n) | O(n) | O(n) | 63 | //! | `clone()` | O(1) | O(1) | O(1) | 64 | //! 65 | //! ## Amortized Analysis Details 66 | //! 67 | //! ### Append Operation 68 | //! The `append` operation is O(1) amortized because: 69 | //! - The actual append is always O(1) as it only creates a new `Append` node 70 | //! - No rebalancing is required 71 | //! - Memory allocation is constant time 72 | //! 73 | //! ### Concat Operation 74 | //! The `concat` operation achieves O(1) amortized time through: 75 | //! - Lazy evaluation: immediate concatenation is O(1) 76 | //! - The actual work is deferred until `as_vec()` is called 77 | //! - No immediate copying or restructuring of data 78 | //! 79 | //! ### Transform Operations 80 | //! Both `transform` and `transform_flatten` are O(1) amortized because: 81 | //! - They create a new node with a transformation function 82 | //! - Actual transformation is deferred until materialization 83 | //! - No immediate computation is performed on elements 84 | //! 85 | //! ### as_vec Operation 86 | //! The `as_vec` operation is O(n) because: 87 | //! - It must process all elements to create the final vector 88 | //! - For a chunk with n elements: 89 | //! - Basic traversal: O(n) 90 | //! - Applying deferred transformations: O(n) 91 | //! - Memory allocation and copying: O(n) 92 | //! 93 | //! ### Memory Usage Patterns 94 | //! 95 | //! The space complexity has interesting properties: 96 | //! - Immediate space usage for operations is O(1) 97 | //! - Deferred space cost accumulates with operations 98 | //! - Final materialization requires O(n) space 99 | //! - Structural sharing reduces memory overhead for clones and versions 100 | //! 101 | //! ```rust 102 | //! use tailcall_chunk::Chunk; 103 | //! 104 | //! // Each operation has O(1) immediate cost 105 | //! let chunk = Chunk::default() 106 | //! .append(1) // O(1) time and space 107 | //! .append(2) // O(1) time and space 108 | //! .transform(|x| x + 1); // O(1) time and space 109 | //! 110 | //! // O(n) cost is paid here 111 | //! let vec = chunk.as_vec(); 112 | //! ``` 113 | //! 114 | //! # Implementation Details 115 | //! 116 | //! The `Chunk` type is implemented as an enum with four variants: 117 | //! - `Empty`: Represents an empty chunk 118 | //! - `Append`: Represents a single element appended to another chunk 119 | //! - `Concat`: Represents the concatenation of two chunks 120 | //! - `TransformFlatten`: Represents a lazy transformation and flattening of elements 121 | //! 122 | //! The data structure achieves its performance characteristics through: 123 | //! - Structural sharing using `Rc` 124 | //! - Lazy evaluation of concatenation and transformations 125 | //! - Immutable operations that preserve previous versions 126 | //! 127 | //! # Memory Efficiency 128 | //! 129 | //! The `Chunk` type uses structural sharing through reference counting (`Rc`), which means: 130 | //! - Appending or concatenating chunks doesn't copy the existing elements 131 | //! - Memory is automatically freed when no references remain 132 | //! - Multiple versions of the data structure can coexist efficiently 133 | //! 134 | //! ```rust 135 | //! use tailcall_chunk::Chunk; 136 | //! 137 | //! let original = Chunk::default().append(1).append(2); 138 | //! let version1 = original.clone().append(3); // Efficient cloning 139 | //! let version2 = original.clone().append(4); // Both versions share data 140 | //! ``` 141 | //! 142 | //! # References 143 | //! 144 | //! 1. Ralf Hinze and Ross Paterson. "Finger Trees: A Simple General-purpose Data Structure", 145 | //! Journal of Functional Programming 16(2):197-217, 2006. 146 | //! 2. Chris Okasaki. "Purely Functional Data Structures", Cambridge University Press, 1998. 147 | 148 | mod chunk; 149 | pub use chunk::*; 150 | -------------------------------------------------------------------------------- /tests/ci.rs: -------------------------------------------------------------------------------- 1 | use gh_workflow_tailcall::*; 2 | 3 | #[test] 4 | fn generate_ci_workflow() { 5 | let workflow = Workflow::default().auto_release(true).benchmarks(true); 6 | workflow.generate().unwrap(); 7 | } 8 | --------------------------------------------------------------------------------