Skip to content

feat(storable): implement Storable for unbounded 2-tuples#434

Merged
lpahlavi merged 1 commit intomainfrom
louis/unbounded-tuple-storable
Apr 21, 2026
Merged

feat(storable): implement Storable for unbounded 2-tuples#434
lpahlavi merged 1 commit intomainfrom
louis/unbounded-tuple-storable

Conversation

@lpahlavi
Copy link
Copy Markdown
Contributor

@lpahlavi lpahlavi commented Apr 21, 2026

Summary

  • Replaces the two todo!() panics in the (A, B) Storable impl (into_bytes_inner_2 and from_bytes) with a working implementation for unbounded tuples.
  • The bounded 2-tuple wire format is unchanged — no backwards-compatibility break.

Wire format for unbounded (A, B)

Documented in a comment inside the impl block, mirroring the 3-tuple style:

If A is fixed-size:
  <a_bytes> <b_bytes>
Otherwise:
  <size_lengths (1B)> <size_a (1-4B)> <a_bytes> <b_bytes>

size_lengths encodes (in 2 bits) how many bytes were used to represent A's length — identical to the encoding already used by the 3-tuple path. B is always the last element so its length is inferred from the remaining bytes and never stored explicitly.

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements support for Storable serialization/deserialization of unbounded (A, B) tuples by replacing the existing todo!() paths, while keeping the bounded 2-tuple wire format unchanged.

Changes:

  • Add unbounded (A, B) decoding logic in from_bytes (including the size_lengths + size_a header when A is not fixed-size).
  • Add unbounded (A, B) encoding logic in to_bytes/into_bytes with dynamic size-length selection for A.
  • Add unit + proptest coverage for unbounded 2-tuple roundtrips and exact byte layouts.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/storable/tuples.rs Implements unbounded 2-tuple wire format for encoding/decoding in the (A, B): Storable impl.
src/storable/tests.rs Adds targeted unit tests and proptest roundtrip tests for the new unbounded 2-tuple behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/storable/tuples.rs
Comment thread src/storable/tuples.rs
@lpahlavi lpahlavi force-pushed the louis/unbounded-tuple-storable branch from 368467d to 9c779c2 Compare April 21, 2026 12:02
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

canbench 🏋 (dir: ./benchmarks/btreemap) a584f0b 2026-04-21 13:04:07 UTC

./benchmarks/btreemap/canbench_results.yml is up to date
📦 canbench_results_btreemap.csv available in artifacts

---------------------------------------------------

Summary:
  instructions:
    status:   No significant changes 👍
    counts:   [total 252 | regressed 0 | improved 0 | new 0 | unchanged 252]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  heap_increase:
    status:   No significant changes 👍
    counts:   [total 252 | regressed 0 | improved 0 | new 0 | unchanged 252]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  stable_memory_increase:
    status:   No significant changes 👍
    counts:   [total 252 | regressed 0 | improved 0 | new 0 | unchanged 252]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

---------------------------------------------------
CSV results saved to canbench_results.csv

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

canbench 🏋 (dir: ./benchmarks/nns) a584f0b 2026-04-21 13:03:03 UTC

./benchmarks/nns/canbench_results.yml is up to date
📦 canbench_results_nns.csv available in artifacts

---------------------------------------------------

Summary:
  instructions:
    status:   No significant changes 👍
    counts:   [total 16 | regressed 0 | improved 0 | new 0 | unchanged 16]
    change:   [max +2.83M | p75 +146.70K | median +37 | p25 0 | min 0]
    change %: [max +0.11% | p75 +0.05% | median 0.01% | p25 0.00% | min 0.00%]

  heap_increase:
    status:   No significant changes 👍
    counts:   [total 16 | regressed 0 | improved 0 | new 0 | unchanged 16]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  stable_memory_increase:
    status:   No significant changes 👍
    counts:   [total 16 | regressed 0 | improved 0 | new 0 | unchanged 16]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

---------------------------------------------------
CSV results saved to canbench_results.csv

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

canbench 🏋 (dir: ./benchmarks/io_chunks) a584f0b 2026-04-21 13:03:27 UTC

./benchmarks/io_chunks/canbench_results.yml is up to date
📦 canbench_results_io_chunks.csv available in artifacts

---------------------------------------------------

Summary:
  instructions:
    status:   No significant changes 👍
    counts:   [total 18 | regressed 0 | improved 0 | new 0 | unchanged 18]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  heap_increase:
    status:   No significant changes 👍
    counts:   [total 18 | regressed 0 | improved 0 | new 0 | unchanged 18]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  stable_memory_increase:
    status:   No significant changes 👍
    counts:   [total 18 | regressed 0 | improved 0 | new 0 | unchanged 18]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

---------------------------------------------------
CSV results saved to canbench_results.csv

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

canbench 🏋 (dir: ./benchmarks/memory_manager) a584f0b 2026-04-21 13:02:49 UTC

./benchmarks/memory_manager/canbench_results.yml is up to date
📦 canbench_results_memory-manager.csv available in artifacts

---------------------------------------------------

Summary:
  instructions:
    status:   No significant changes 👍
    counts:   [total 3 | regressed 0 | improved 0 | new 0 | unchanged 3]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  heap_increase:
    status:   No significant changes 👍
    counts:   [total 3 | regressed 0 | improved 0 | new 0 | unchanged 3]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  stable_memory_increase:
    status:   No significant changes 👍
    counts:   [total 3 | regressed 0 | improved 0 | new 0 | unchanged 3]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

---------------------------------------------------
CSV results saved to canbench_results.csv

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

canbench 🏋 (dir: ./benchmarks/btreeset) a584f0b 2026-04-21 13:03:13 UTC

./benchmarks/btreeset/canbench_results.yml is up to date
📦 canbench_results_btreeset.csv available in artifacts

---------------------------------------------------

Summary:
  instructions:
    status:   No significant changes 👍
    counts:   [total 100 | regressed 0 | improved 0 | new 0 | unchanged 100]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  heap_increase:
    status:   No significant changes 👍
    counts:   [total 100 | regressed 0 | improved 0 | new 0 | unchanged 100]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  stable_memory_increase:
    status:   No significant changes 👍
    counts:   [total 100 | regressed 0 | improved 0 | new 0 | unchanged 100]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

---------------------------------------------------
CSV results saved to canbench_results.csv

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

canbench 🏋 (dir: ./benchmarks/vec) a584f0b 2026-04-21 13:02:59 UTC

./benchmarks/vec/canbench_results.yml is up to date
📦 canbench_results_vec.csv available in artifacts

---------------------------------------------------

Summary:
  instructions:
    status:   No significant changes 👍
    counts:   [total 16 | regressed 0 | improved 0 | new 0 | unchanged 16]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  heap_increase:
    status:   No significant changes 👍
    counts:   [total 16 | regressed 0 | improved 0 | new 0 | unchanged 16]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  stable_memory_increase:
    status:   No significant changes 👍
    counts:   [total 16 | regressed 0 | improved 0 | new 0 | unchanged 16]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

---------------------------------------------------
CSV results saved to canbench_results.csv

@lpahlavi lpahlavi marked this pull request as ready for review April 21, 2026 12:14
@lpahlavi lpahlavi requested a review from a team as a code owner April 21, 2026 12:14
Comment thread src/storable/tuples.rs Outdated
Replaces the two `todo!()` panics in the `(A, B)` `Storable` impl
(`into_bytes_inner_2` and `from_bytes`) with a working implementation
for unbounded tuples. The bounded 2-tuple wire format is unchanged.

Wire format for unbounded `(A, B)`, documented in a comment inside the
impl block mirroring the 3-tuple style:

  If A is fixed-size:
    <a_bytes> <b_bytes>
  Otherwise:
    <size_lengths (1B)> <size_a (1-4B)> <a_bytes> <b_bytes>

`size_lengths` encodes (in 2 bits) the number of bytes used to store
`size_a`, matching the encoding already used by the 3-tuple path.
`B`'s length is always inferred from the remaining bytes.

Tests mirror the 3-tuple unbounded test coverage:
- `tuple_with_two_unbounded_elements_roundtrip`
- `tuple_with_two_elements_bounded_and_unbounded_roundtrip` (with byte count)
- `optional_tuple_with_two_unbounded_elements_roundtrip`
- `optional_tuple_with_two_elements_bounded_and_unbounded_roundtrip`
- `tuple_with_two_elements_test_bound`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lpahlavi lpahlavi force-pushed the louis/unbounded-tuple-storable branch from 9c779c2 to 3423b16 Compare April 21, 2026 12:55
@lpahlavi lpahlavi merged commit bbc94de into main Apr 21, 2026
16 checks passed
@lpahlavi lpahlavi deleted the louis/unbounded-tuple-storable branch April 21, 2026 13:08
@maksymar
Copy link
Copy Markdown
Contributor

maksymar commented Apr 21, 2026

(for posterity)

I just noticed this PR and I would like to provide some context.

Unbounded 2-tuple was originally left unimplemented because EXC-1550 explored a smooth Bounded-to-Unbounded transition and found it infeasible. The legacy bounded layout (<a(padded)> <b(padded)> <size_a> <size_b>) stores sizes at offsets derived from a_max/b_max, which aren't recoverable without the Bound metadata. The ticket was descoped.

The concern this PR introduces: the 2-tuple now carries two completely different wire formats, so a user who flips a type's BOUND from Bounded to Unbounded will silently mis-decode existing data. The 3-tuple doesn't have this problem, it uses one unified wire format for both variants, so as long as is_fixed_size() is preserved, a flip is a no-op at the tuple's wire level.

Worth re-evaluating whether shipping this is better than the previous todo!(), which at least failed loudly.

If we keep it I'd want at minimum:

  • doc warning on the tuple Storable impl that flipping type's bound is a silent wire-format break for 2-tuples
  • document the bounded format in the 2-tuple impl comment alongside the unbounded one
  • add (fixed, unbounded) and maybe other combinations of roundtrip tests
  • and long-term, if we keep it now, then if we decide to unify the 2-tuple later it'll be a breaking change

@lpahlavi what do you think on whether we should keep it and face a silent-breakage risk or revert it?

lpahlavi added a commit that referenced this pull request Apr 22, 2026
## Summary

- Reverts #434, which implemented `Storable` for unbounded 2-tuples.
- Restores the `todo!()` panics that fail loudly instead of silently
mis-decoding data.

## Motivation

As noted in [this
comment](#434 (comment))
by @maksymar:

The 2-tuple now carries two completely different wire formats (bounded
vs unbounded), so a user who flips a type's `BOUND` from `Bounded` to
`Unbounded` will silently mis-decode existing data. The 3-tuple doesn't
have this problem since it uses one unified wire format for both
variants.

Reverting to `todo!()` is safer — it fails loudly rather than risking
silent data corruption.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants