Issue #635: main’s parameters are the command line; its preconditions are checked where the words arrive, a breach exits with status 2

Open and answer this issue on GitHub.

ready · opened 2026-10-17 · updated 2026-10-17 · 1 comment

How the question came up

The owner wrote, as the next target after #632/#633:

fn main(a: int) {
   assert(a>0)
   let b = 4*example(a)
   let c = (0*4+1)
   assert(c==1)
   assert(b>40)
   println(f"Hello, Nikaia! {b} {c}")
}
fn positive(whole: int) -> int { assert(whole > 0); return whole }
fn example(a: int) -> int { let whole = 10 + a; return positive(whole) }

Checked with the compiler on October 17, 2026 (with i64 for int):

The owner’s direction: an assert about main’s parameter is documentation, the contract with the caller (checked at run time, where the arguments arrive), and a fact for every later proof; the contract is derived by the ordinary rules of carrying back, as for any function or method. A decision basis compared clap (derive), Swift ArgumentParser, Python typer and Go flag; the owner chose: parameters of main are the command line (option C), exit status 2 for a wrong word, a small first scope.

Decision

ADR-360, specification Part I 5.5, Part III 14.2, 17.1, A.2, NK1273, NK1274:

Not decided: short flags, subcommands, enums as choices, environment defaults, a version flag.

To build

  1. Checker: accept parameters on main per D2; NK1273, NK1274 (codes reserved, text in Part III’s table). Tests: each refused type; help; max_depth + max-depth; wasm32-unknown.
  2. Prover/report: main’s carried-back preconditions are reported by --asserts as precondition of main, checked where the words arrive (not checked at run time: main has no callers), and are facts in the body. Test: the owner’s program above (once #630 and ADR-359 D1-D3 are in) reports a > 0 as main’s precondition, b > 40 proved; a variant without assert(a > 0) reports 10 + a > 0 as main’s precondition.
  3. Lowering + std::cli: Rust fn main() reads std::env::args, fills the parameters per D1/D2, checks the preconditions, on failure prints the line and usage to stderr and exits 2; --help/-h print the help and exit 0; then calls the program’s main body. Tests (run the binary): prog 5 runs; prog / prog x / prog 5 6 / prog -3 exit 2 with the message; prog --times=3 5, prog --verbose 5, prog -- 5; prog --help output and status 0; a precondition carried back from a callee names the assert it came from.
  4. Ledger: main’s parameters, defaults and preconditions in nikaia.contracts.
  5. lower-std, commit package.rs.
  6. examples/ (no version raise): move the cli::args().nth(1) ?? "…" programs to a main option where it reads better.

Order: after #630 (int); step 2’s full test after #632’s D1-D3.

Comments

keywan-ghadami, 2026-10-17

Built in 42aa0fa (step 2, prover) and 0f78405 (steps 1, 3, 4, 5), with i64 and the other types:

Still missing:

  1. int (#630): int is already on the checker’s list of word types (main_word_type), but there is no test with int. If int lowers to something other than i64, std::cli::Word needs an impl for it. The owner’s program as written (step 2’s full test) waits on #630 and ADR-359 D1-D3.
  2. The variant without assert(a > 0): 10 + a > 0 is main’s precondition in the ledger, in --help, and in the exit-2 message. But --asserts lists asserts by line and has no row for a carried-back precondition. positive’s assert still reads precondition of positive: proved at every call. A row for a computed precondition of main would be new prover-report work.
  3. NK1273 on wasm32-unknown: no check exists. Every build for that target is refused before the source is read (Target::unbuildable).
  4. NK1274 for “two options one name once _ is -“: a name cannot contain -, so max_depth + max-depth does not parse. The check is in place but cannot be reached.
  5. Step 6 (examples): not done.

Generated by Claude Code

Back to Open Issues