Issue #1123: Decision: how the generic proofs::in_function moves into Nikaia

Open and answer this issue on GitHub.

ready · opened 2026-11-01 · updated 2026-11-03 · 1 comment

The question

The thread-local accessors of the proof engine move into Nikaia as explicit host parameters (ADR-281 D32 and ADR-289 refuse global and thread-local state). Most of them are plain values. One is not:

// crates/nikaia/src/proofs.rs:131
pub fn in_function<R>(name: &str, at: usize, run: impl FnOnce() -> R) -> R

It sets THREAD.function / function_at, runs run, and restores the old values. with_book<R> works the same way. It is generic in the result: the callers need two result types.

A host parameter in_function: fn(String, i64, fn() -> R) -> R needs a function value that is itself generic (rank-2). Nikaia has no such thing, and neither has Rust: a closure is not generic.

Options

A. One host parameter per result type (in_function_proven, in_function_vec, …), each written by hand.

B. Generic function values (rank-2) in the language.

C. A host trait with a generic method, statically dispatched.

trait ProofsHost { fn in_function[R](self, name: String, at: i64, run: fn() -> R sync) -> R sync }
fn walk[H: ProofsHost](host: H, ...) { ... host.in_function(name, at, fn() { ... }) ... }

D. Remove the hidden state: the context becomes data. (name, at) is a value Scope { function: String, at: i64 } that is passed down to the places that read it (ask and the messages). No closure and no in_function remain; in_function(name, at, || body) becomes body with scope passed in. The move rewrites the pattern by hand at each call site.

Recommendation

D, with C as the fallback.

Needs from the owner

Context: self-hosting (ADR-294, #125).


Generated by Claude Code

Comments

keywan-ghadami, 2026-11-03

Owner, 2026-11-03: the target architecture collects the facts first, and every later phase runs on those facts (#761).

Decision: D. (function, at) becomes a plain value, Scope, that is passed down to its readers. No swapped global and no generic accessor. Scope is shaped as a fact that #761’s phase A will produce, so the later phases read it rather than set it. with_book follows the same pattern.

The move may restructure the call sites. The differential test compares outputs, not structure. C (#1122) stays a defect to fix on its own merits, but it no longer blocks this move.


Generated by Claude Code

Back to Open Issues