├── .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 | [](https://crates.io/crates/tailcall-chunk)
4 | [](https://docs.rs/tailcall-chunk)
5 | [](https://github.com/tailcallhq/tailcall-chunk/actions)
6 | [](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 |
--------------------------------------------------------------------------------