Tier-2 captures: func!([fee] move |p| p - fee) - #764
Closed
0-jake-0 wants to merge 1 commit into
Closed
Conversation
The feature that turns "the mechanism works" into "the workload works".
A closed closure's body resolves anywhere, so tier 1 could splice it
directly. A capturing one cannot: `move |p| p * fee` refers to a binding
that exists only in the wiring, so the generator had to refuse it. That
made the case §7 justifies the whole project with — per-instrument
pipelines built from a config — only half expressible: per-instrument
*topology* and *data* configs worked, per-instrument *parameters* did not.
Found by writing the example, not by reasoning about it.
`func!` now takes an explicit capture list. Each name is recorded by
**value**, rendered through `EmitLiteral`, and `emittable_src` wraps the
body in a block that re-materialises them:
let fee = 2.5f64;
func!([fee] move |p: &f64| p - fee).emittable_src()
// "{ let fee = 2.5f64; move |p: &f64| p - fee }"
`Stream::with_src` records that emittable form rather than the bare body,
so `NodeInfo::src` is now `Option<String>` — a tier-2 quotation assembles
its text at wiring time instead of carrying a token.
`tests/codegen_emission.rs` generates a two-instrument desk end to end,
each leg with its own frozen fee, and asserts parity on values and tick
times. The expected artifact is a real `nitro!` block in the file, so the
re-materialised capture blocks are proven valid Rust by the file
compiling — not by inspection.
Bounded by `EmitLiteral`, so a capture of an arbitrary struct is a compile
error at the `func!` call site, where it can be understood. And frozen, per
§3: the emitted block carries the value quoted at generation time.
What is still not caught: an *undeclared* capture. §3 catches it by
coercing through a fn pointer, which this macro cannot do — the coercion
names an arity (`fn(&_) -> _`), so it would make `func!` unusable for
`join` and `fold`. It surfaces instead as a pass-2 compile error, with a
breadcrumb pointing at the wiring. D28 updated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X4eojqRWBJ6uK5v5JDzmkd
This was referenced Aug 8, 2026
Contributor
Author
|
Superseded by #769, which squashes this whole stack (#759–#768) onto Tier-2 captures — Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #763 → #762 → #761 → #760 → #759. Base is
codegen-generate; this diff is only the top commit.What this changes
func!takes an explicit capture list. Each name is recorded by value throughEmitLiteral, and the emittable form wraps the body in a block that re-materialises them:Why
This is what turns "the mechanism works" into "the workload works".
A closed closure's body resolves anywhere, so tier 1 could splice it directly. A capturing one cannot —
move |p| p * feerefers to a binding that exists only in the wiring — so the generator refused it. That left the case §7 justifies the whole project with, per-instrument pipelines built from a config, only half expressible: per-instrument topology and data configs worked; per-instrument parameters did not.I found that by writing the example, not by reasoning about it — I'd been filing tier 2 as a nice-to-have right up until the demo wouldn't generate.
How it was verified
cargo fmt --allcargo lint✅ —cargo lint-allnot run:--all-featurescannot build here (aeron needs CMake ≥3.30, box has 3.28; etcd needs protoc). Documented prerequisites, unrelated to this diff; CI is the first real check on that feature set.--all-featuresblocked as above. Full default suite plusbench,async,csv,cache,augurs,market,ws,redis,postgres,kafka,web,kdb,prometheus,fix,dynamic-graph,tracing— green. Doctests green.tests/codegen_emission.rsgenerates a two-instrument desk end to end, each leg with its own frozen fee, and asserts parity against the graph it came from. The expected artifact is a realnitro!block in the file, so the re-materialised capture blocks are proven valid Rust by the file compiling — not by inspection.tests/quotation.rsadds six more, including one that splicesemittable_src's exact text at a call site where the binding is not in scope and checks it computes the same thing.Notes for the reviewer
NodeInfo::srcbecomesOption<String>. A tier-2 quotation assembles its text at wiring time rather than carrying a&'static strtoken, so the node can no longer hold a borrowed str. Mechanical change at every call site;cfg_srcwas alreadyString.Captures are bounded by
EmitLiteral, so capturing an arbitrary struct is a compile error at thefunc!call site — where it can be understood — rather than a mystery at generation time.They are frozen, per §3. The emitted block carries the value quoted at generation time, so changing a fee means regenerating. That is the point (a per-instrument parameter baked into a per-instrument pipeline) and also how a stale threshold ships.
Still not caught: an undeclared capture. §3 catches this by coercing the expansion through a fn pointer; this macro cannot, because the coercion has to name an arity (
fn(&_) -> _) and would makefunc!unusable forjoinandfold. It surfaces as a pass-2 compile error with a breadcrumb pointing at the wiring. D28 records it.Stack: seven PRs, none reviewed, six chained on #759.
Generated by Claude Code