Invisible link to canonical for Microformats

ADR-033 Entrypoint semantics - module top-level execution and @main


Status

Accepted

Context

Slug needs a clear, script-friendly execution model that also supports modular reuse.

We want:

  • “Script ergonomics”: running a module should execute its top-level statements naturally.
  • “Library ergonomics”: modules can perform initialization when imported (e.g., registration), but should not accidentally run the program entrypoint.
  • A single, explicit entrypoint hook (@main) that receives parameters using the same default-parameter semantics as all other Slug functions.

Decision

Module top-level execution

  • Importing a module executes that module’s top-level statements.
  • A module’s top-level statements execute at most once per process per canonical module identity (normal module caching rules).
  • Top-level execution may transitively import other modules; those imports execute their top-level statements first ( depth-first as encountered).

@main execution

  • @main never executes on import.
  • When a module is the running module (the root module passed to the runner, e.g. slug path/to/mod.slug), the runtime executes: 1) the running module’s top-level statements, then 2) the running module’s @main function (if present)

  • If top-level execution raises an error/exception, @main is not executed.

@main arity and parameter defaults

  • @main is valid only on a function that is callable with zero arguments, using the standard Slug default-parameter semantics (no special rules for @main).
  • This allows patterns such as:
@main
fn start(args = argv(), param = cfg("param", 10)) {
  // ...
}
  • If the annotated function cannot be called with zero args, compilation fails.

Uniqueness and validation

  • A module may define zero or one @main function.
  • If a module defines multiple @main functions, compilation fails.
  • @main may only annotate functions (not structs/vals/modules).

Consequences

Positive

  • Script-friendly: running a module executes its top-level code naturally.
  • Explicit entry hook: @main provides a clear “start here” function for the running module.
  • No special-case defaults: @main uses the same parameter-default rules as all other functions, keeping the language consistent.
  • Import safety vs entry: importing a module will not accidentally start the program (no @main on import), while still allowing module initialization at import-time.

Negative

  • Import side effects: since imports execute top-level statements, importing can trigger I/O or other side effects. Tooling that loads modules must account for this.
  • Tooling complexity: doc/test/indexing tools may require a future “load without executing top-level” mode or conventions that keep import-time side effects minimal.

Neutral

  • Two-phase root execution (top-level then @main) is a deliberate model:
    • top-level is suited to wiring/registration,
    • @main is suited to CLI args/config-driven start-up.
  • Future ADRs may refine:
    • recommended conventions for keeping import-time side effects small,
    • optional runner/tooling flags for “no top-level execution” in analysis contexts.