Skip to content

Tier-2 captures: func!([fee] move |p| p - fee) - #764

Closed
0-jake-0 wants to merge 1 commit into
codegen-generatefrom
tier2-captures
Closed

Tier-2 captures: func!([fee] move |p| p - fee)#764
0-jake-0 wants to merge 1 commit into
codegen-generatefrom
tier2-captures

Conversation

@0-jake-0

@0-jake-0 0-jake-0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

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 through EmitLiteral, and the emittable form 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 }"

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 * fee refers 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 --all
  • cargo lint ✅ — cargo lint-all not run: --all-features cannot 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-features blocked as above. Full default suite plus bench,async,csv,cache,augurs,market,ws,redis,postgres,kafka,web,kdb,prometheus,fix,dynamic-graph,tracing — green. Doctests green.
  • New behaviour covered by tests asserting values and tick times

tests/codegen_emission.rs generates 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 real nitro! block in the file, so the re-materialised capture blocks are proven valid Rust by the file compiling — not by inspection. tests/quotation.rs adds six more, including one that splices emittable_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::src becomes Option<String>. A tier-2 quotation assembles its text at wiring time rather than carrying a &'static str token, so the node can no longer hold a borrowed str. Mechanical change at every call site; cfg_src was already String.

Captures are bounded by EmitLiteral, so capturing an arbitrary struct is a compile error at the func! 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 make func! unusable for join and fold. 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

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

0-jake-0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #769, which squashes this whole stack (#759#768) onto main as a single reviewable change.

Tier-2 captures — func!([fee] move |p| p - fee) and emittable_src — are carried forward in full. Closing to keep the queue readable; no content is being dropped.


Generated by Claude Code

@0-jake-0 0-jake-0 closed this Aug 8, 2026
@0-jake-0
0-jake-0 deleted the tier2-captures branch August 9, 2026 23:07
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.

2 participants