Silk

Program termination and reporting

Silk distinguishes normal completion, unhandled typed failure, and fatal trap. These rules describe the generated host boundary after a valid main has been selected. They do not add a source-visible exception system, ambient console, or formatting requirement to error values.

TERM-001–012 are confirmed stabilization rules.

TERM-001 — An ordinary entry explicitly returns unit or one status value

Status: Confirmed

pub fn main() -> () completes normally with machine status 0. pub fn main() -> i32 completes normally with that exact i32. Neither form automatically reports an error, even when the integer is nonzero.

pub fn main() -> () {
}
pub fn main() -> i32 {
  return 7
}

Boundary: The result annotation is required. pub fn main() {} remains invalid until Silk adopts a general return-omission rule. A native host may expose only a target-defined subset of the i32; a direct Wasm caller observes the exact machine-entry result.

Diagnostics: Any other ordinary result reports an invalid entry shape naming () and i32 as the permitted results. A missing annotation receives the ordinary missing-result diagnostic.

Evidence: ordinary entry rule.

TERM-002 — A successful effect entry returns zero

Status: Confirmed

pub effect fn main() is constructed and executed exactly once. When it succeeds with (), the generated machine entry returns status 0 and prints nothing automatically.

pub effect fn main() {
}

Boundary: Effect-entry success must be unit. Returning an integer success value does not turn it into an ordinary entry or choose a process status.

Diagnostics: A non-unit effect-entry success type reports the invalid entry shape before backend emission.

Evidence: effect entry execution, effect entry failure boundary.

TERM-003 — Every unhandled typed failure uses status one

Status: Confirmed

Any valid concrete error reaching the effect-entry boundary produces one structured unhandled-error outcome and machine status 1. The actual error identity belongs to the structured outcome and report, not to a numeric status ordinal.

pub struct NotFoundError {}
pub struct OfflineError {}

pub effect fn main() ! NotFoundError | OfflineError {
  fail OfflineError {}
}

This terminates with status 1 whether the active error is NotFoundError or OfflineError.

Boundary: Adding, removing, or reordering structural-union members cannot change the public status. Application-specific statuses require handling the error explicitly before entry.

Diagnostics: A valid unhandled entry error receives no compile-time diagnostic. The runtime outcome is an unhandled error, not a source diagnostic.

Implementation: Internal entry tags still select the active failure identity, but standalone adapters normalize every recognized failure tag to public status 1; evaluator outcomes expose that same public status independently of member order.

Evidence: entry failure rule, current backend contract.

TERM-004 — A failure report has one stable minimum

Status: Confirmed

Every unhandled typed-error report contains, in order:

  1. an explicit unhandled error classification;
  2. the canonical fully qualified type identity of the active error;
  3. the source origin where the failure was created, when valid provenance exists; and
  4. the logical named function and Effect path from that origin outward to main.
unhandled error: app.NotFoundError
  at app.load (src/app.silk:12)
  at app.main (src/app.silk:27)

The exact wording is illustrative; the information and order are the contract.

Boundary: Colors, source excerpts, absolute path spelling, target information, and additional debug frames are optional decoration and cannot replace the stable minimum.

Diagnostics: No compile-time diagnostic applies. Missing provenance caused by a compiler defect must not be disguised as an empty successful report.

Evidence: failure diagnostic context.

TERM-005 — Automatic reporting does not inspect error payload fields

Status: Confirmed

The entry adapter reports canonical error identity and retained diagnostic context. It does not reflect over, serialize, or generically format the payload's fields.

pub struct NotFoundError {
  resourceId: i32
}

An automatic report identifies NotFoundError; it does not automatically print resourceId.

Boundary: The adapter cannot expose private fields accidentally, require a display operation, or fail because one payload field has no formatter. Source recovery code may inspect and report the payload explicitly before it reaches entry. A future opt-in error-formatting interface may customize the report, but implementing it must never be required for a value to participate in the typed-error channel or reach the automatic entry boundary.

Diagnostics: No marker interface or formatting diagnostic applies to a valid error type.

Evidence: ordinary error values, removal of Report ceremony.

TERM-006 — Recovery history becomes causal report context

Status: Confirmed

If a recovery handler fails while handling an earlier error, the new error is primary. Its report retains the earlier identity and logical trace under an explicit while handling cause. Ordinary propagation does not duplicate the same cause, and a successful handler removes the handled context from the final outcome.

unhandled error: app.OfflineError
  at app.recover (...)
while handling: app.NotFoundError
  at app.load (...)

Boundary: Causal context remains hidden runtime metadata. It does not change E, enter pattern matching, or wrap the handler's ordinary error parameter.

Diagnostics: No compile-time diagnostic applies. Causal presentation must not reorder the primary and handled errors.

Evidence: failure recovery context.

TERM-007 — Logical traces survive optimization and explicit suspension

Status: Confirmed

The stable trace contains source-declared function and Effect boundaries. It excludes compiler helpers, physical machine frames, and private coroutine transitions. Optimization cannot erase a logical frame required by the report, and Effect.suspend preserves the suspended invocation's place in the logical path.

Boundary: Debug builds may add internal detail. Release builds still retain the stable minimum for fallible entry paths. This rule does not promise future async task or scheduler traces.

Diagnostics: No compile-time diagnostic applies. A runner must not present a physical helper frame as if the programmer declared it.

Evidence: Effect suspension rules, failure diagnostic context.

TERM-008 — Fatal traps use a distinct best-effort report

Status: Confirmed

A trap terminates abnormally outside the typed-error outcome. When runtime state remains trustworthy, the host report contains an explicit fatal trap classification, the reason, source origin, and available logical path.

fatal trap: division by zero
  at app.calculate (src/app.silk:8)
  at app.main (src/app.silk:14)

Boundary: Corrupted memory or a violated unsafe contract may prevent a complete report. Silk guarantees neither a portable numeric trap status nor structured cleanup after the trap.

Diagnostics: A compile-time trap in required constant evaluation is a source diagnostic. A runtime trap is abnormal termination and must not be reported as an unhandled typed error.

Evidence: fatal trap rule, trap cleanup boundary.

TERM-009 — Automatic entry reporting is not an ambient service

Status: Confirmed

Terminal reporting belongs to the compiler-generated host adapter and matched toolchain support. It adds no Logger, Console, filesystem, environment, allocator, or other source requirement and makes none of those facilities ambient inside the program.

Boundary: Failure while writing a best-effort report cannot become a new typed error or replace the original termination. Ordinary program logging still requires its explicit service.

Diagnostics: No requirement-row entry is added for automatic reporting. A source log operation with an unresolved service remains an ordinary open-requirement error.

Evidence: generated adapter boundary, no ambient facilities.

TERM-010 — Standalone and embedded hosts expose equivalent termination data

Status: Confirmed

A standalone executable writes its automatic report to the host diagnostic stream, conventionally standard error. A target without such a stream, including import-free direct Wasm, receives structured termination data through its runner or embedding boundary and chooses how to present it.

Boundary: Silk does not invent a console or hidden import for embedded targets. Exact embedding ABI is target policy; semantic parity requires the same classification, identity or reason, origin, and logical path rather than identical output bytes.

Diagnostics: A target incapable of satisfying its declared runner contract is a toolchain or target-compatibility failure, not a source error in main.

Evidence: runtime layers, direct Wasm entry contract.

TERM-011 — Typed-failure reporting follows completed cleanup

Status: Confirmed

On unhandled typed failure, every exited owner except the failure payload is cleaned during propagation. The entry boundary then cleans the payload exactly once while retaining its hidden diagnostic context for reporting.

Boundary: Reporting does not keep arbitrary user payload storage alive. Fatal traps retain their separate no-cleanup guarantee.

Diagnostics: Invalid cleanup ownership receives its ordinary compile-time diagnostic. A valid terminal failure adds no cleanup diagnostic.

Evidence: typed-failure cleanup, cleanup follows ownership.

TERM-012 — Custom presentation and statuses are explicit source policy

Status: Confirmed

A program wanting a custom message, payload rendering, telemetry event, or application-specific status handles the typed error before it reaches the automatic entry boundary. It closes every Effect error and requirement and returns an ordinary i32 entry result when it wants to select a status.

Boundary: No error naming convention, marker interface, or automatically discovered operation changes entry behavior. Future standard-library helpers remain ordinary source APIs.

Diagnostics: An ordinary entry that attempts to run an Effect with unhandled errors or requirements receives the existing boundary diagnostic before backend emission.

Evidence: ordinary execution boundary, explicit entry requirements, style guide.

Future direction: custom error formatting

Silk may later define an ordinary opt-in interface for customizing how an unhandled error payload is presented. Such an interface must remain separate from error eligibility: every valid detached owned value can still be an error without implementing it. The future design must also define how formatting obtains allocation or output capabilities, what happens if formatting cannot complete, and how embedded targets request structured rather than textual output.

On this page