Skip to content

Split Builder along the seam its two users already follow - #846

Merged
frankmcsherry merged 2 commits into
master-nextfrom
builder-split
Aug 26, 2026
Merged

Split Builder along the seam its two users already follow#846
frankmcsherry merged 2 commits into
master-nextfrom
builder-split

Conversation

@frankmcsherry

Copy link
Copy Markdown
Member

Builder had two consumers using disjoint halves of it.
MergeBatcher called only seal, and through it with_capacity, because it holds a whole chain and can count keys, values, and updates before allocating.
Reduce and upsert called only new, push, and done, discovering their output key by key and never sizing anything.

seal moves to a trait of its own:

pub trait Sealer<C> {
    type Output;
    fn seal(chain: &mut Vec<C>) -> Option<Self::Output>;
}

which is a fn(&mut Vec<C>) -> Option<B> in a form that can name B.
It has no receiver, so there is nowhere for an update to be retained between the chain going in and the batch coming out.

with_capacity becomes inherent on the two ord_neu builders.
It had no callers outside seal and new, so it was never vocabulary; new now carries its own one-line body instead of a default.

MergeBatcher bounds Bu: Sealer<M::Chunk> and stops naming Builder, which also drops Builder::Time from its vocabulary — it never wanted it.
The three implementors implement both traits and share their machinery.

No call site changes: the aliases name the same types, and those types satisfy both bounds.
The diff is four files, all under trace/.

This splits one question into two.
Whether reduce and upsert should take a Batcher instead, and whether Sealer should be a function value rather than a trait, can now be answered independently.

Builds warning-free, workspace tests and doctests pass.

frankmcsherry and others added 2 commits August 26, 2026 16:20
`Builder` had two consumers using disjoint halves of it. `MergeBatcher` called
only `seal`, and through it `with_capacity`, because it holds a whole chain and
can count keys, values, and updates before allocating. Reduce and upsert called
only `new`, `push`, and `done`, discovering their output key by key and never
sizing anything.

`seal` moves to a trait of its own:

    pub trait Sealer<C> {
        type Output;
        fn seal(chain: &mut Vec<C>) -> Option<Self::Output>;
    }

which is a `fn(&mut Vec<C>) -> Option<B>` in a form that can name `B`. It has no
receiver, so there is nowhere for an update to be retained between the chain
going in and the batch coming out.

`with_capacity` becomes inherent on the two `ord_neu` builders. It had no callers
outside `seal` and `new`, so it was never vocabulary; `new` now carries its own
one-line body instead of a default.

`MergeBatcher` bounds `Bu: Sealer<M::Chunk>` and stops naming `Builder`, which
also drops `Builder::Time` from its vocabulary — it never wanted it. The three
implementors implement both traits and share their machinery. No call site
changes: the aliases name the same types, and those types satisfy both bounds.

This splits one question into two. Whether reduce and upsert should take a
`Batcher` instead, and whether `Sealer` should be a function value rather than a
trait, can now be answered independently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`new` was a second name for the empty builder, kept because the trait once
offered `with_capacity` to prefer over it. It no longer does: sizing happens
inside `Sealer::seal`, which owns its own allocation. So the preference the
comment on `new` expressed is no longer expressible, or needed.

`Default` says the same thing in the standard vocabulary. Reduce and upsert
construct with `Bu::default()`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@frankmcsherry
frankmcsherry merged commit d31ebe5 into master-next Aug 26, 2026
6 checks passed
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.

1 participant