

<!--
This file is generated. Do not edit it by hand.
-->

# Silk Specification (2026)

This is the single-file edition of the Silk language specification for 2026.

---
## Language Cheat Sheet

This document summarizes the key syntax and concepts from the Silk language in a condensed form. It is meant as a quick reference; detailed semantics live in the chapters below.

### Notes

This cheat sheet includes **both**:

- the full language design (where some features are still evolving), and
- the **currently implemented compiler subset**.

For the authoritative “current implementation notes”, prefer:

- `the implementation status` (implementation status), and
- any “notes sections inside the relevant concept documents.

In particular, features such as regions (beyond the current `with` + `new`
subset), concurrency runtime (scheduler/event loop), and
dependent types are **not** implemented end-to-end yet. Value constraints are
expressed via Formal Silk (`#require` / `#assure`, including `#require` on
`struct` declarations).

In Silk currently:

- Runtime `let`/`var` bindings and compile-time `const` bindings must have an initializer ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2015`).
- Destructuring `let` bindings from structs are supported:
 - positional: `let (id, name) = User{ ... };`
 - named + aliasing: `let { data as d, id as i } = Record{ ... };`
- Array destructuring is supported:
 - arrays/slices: `let [a, b] = xs;`
- Enum destructuring is supported:
 - variants: `let Ok(v) = expr;`, `let Pair(a, b) = expr;`, `let E::Variant(x) = expr;` (traps on non-matching variants)
- Refutable `let` bindings are supported:
 - `let <pattern> = <expr> else { ... };` (the `else` block must be terminal)
- `const` initializers must be compile-time evaluable ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2041`); in the Supported forms this is restricted to scalar expressions and calls to `const fn` functions (still no `/` or `%`), plus string literals / `const` string aliases.
- Monomorphized generics are supported for `struct`/`interface`/`impl` and applied types (`Name(args...)`):
 - const parameters/arguments and generic functions are still rejected (`E2016`),
- A small concurrency subset is implemented (`Task(T)` / `Promise(T)` plus `yield`/`await`; see [`Concurrency`](/silk/docs/language/concurrency/)).
- The builtin `map(K, V)` type form is removed; use `std::map::{HashMap, TreeMap}` instead (`E2017`).
- Function expressions are implemented as first-class function values:
 - non-capturing: inferred `pure` — `let add = fn (x: int, y: int) -> x + y;`
 - capturing closures: may capture immutable scalar locals/parameters by value;
 forming captures inside `pure` code is rejected in the Supported forms.

### Types (Surface Forms)

- Booleans: `bool` — `true`, `false`.
- Integers: `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `u128`, `i128`, `int`.
- Floats: `f32`, `f64`, `f128`.
- Char: `char`.
- String: `string`.
- Time: `Instant`, `Duration`.
- Optional: `T?` (sugar for `Option(T)`).
- References: `&T`.
- Arrays / slices: `T[]`, `T[N]`.
- Maps / dictionaries: `std::map::{HashMap, TreeMap}` (standard library).
- Function types: `fn(params) -> R` (discipline modifiers apply to function
 declarations; function types are unmodified in the Supported forms).
- Function expressions (non-capturing, inferred `pure`):
 - expression body: `fn (x: int, y: int) -> x + y`
 - block body: `fn (x: int, y: int) -> int { return x + y; }`
 - block body `void` shorthand: `fn (x: int, y: int) { ... }` (implicit `void`)
 - capturing closures are supported as a subset; see `Types`.
- Structs / enums / interfaces:
 - `struct Name { ... }`, `struct Name extends Base { ... }`
 - `enum Name { ... }`
 - `interface Name { ... }`, `interface Name extends Base { ... }`

### Literals

- Integers: `0`, `42`, with base/suffixes as per the spec.
- Floats: `3.14`, `1.0e-9`.
- Booleans: `true`, `false`.
- Chars: `'A'`, escape sequences.
- Strings:
 - single-line: `"hello"`,
 - multi-line: multi-line quoted forms.
- Durations: numeric + unit, e.g. `10ms`, `2s`, `5min`.
- Aggregates:
 - arrays: `[1, 2, 3]`,
 - structs: `Point { x: 1, y: 2 }`.

### Operators

- Arithmetic: `+`, `-`, `*`, `/`, `%`.
- Bitwise: `&`, `|`, `^`, `~`, `<<`, `>>`.
- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`.
- Logical: `!`, `&&`, `||`.
- Assignment: `=`, `+=`, `-=`, `*=`, `/=`.
- Increment/decrement: `++`, `--` (statement-like `void`).
- Optional / nullability:
 - optional chaining: `?.`,
 - coalescing: `??`.
- Member/scope: `.`, `::`.
- Ranges: `..`, `..=`, `...`.
- Other punctuation: `,`, `;`, `:`, `->`, `=>`.

Operator precedence and associativity follow the rules in `Operators`.

### Flow Control

- `if cond { ... } else { ... }` (statement form)
- `if let [mut] <pattern> = <expr> { ... } else { ... }` (refutable pattern statement form; supports `else if let` / `else let` chains and chained `&& let [mut]`)
- `let v = if cond { a } else { b };` (`if` expression)
- `loop { ... }` (infinite loop; exits via `break`/`return`).
- `while (cond) { ... }`
- `while let [mut] <pattern> = <expr> { ... }`
- `for pattern in iterable { ... }` (ordinary binder form for ranges, builtin arrays/slices, and iterators).
- `for let [mut] pattern in iterable { ... }` (pattern-filtered iteration; matching elements run the body, non-matching elements are skipped).
- `for (init; cond; step) { ... }` (C-style loop header).
- `async loop { ... }` / `task loop { ... }` (loop forms in async context).
- `match value { ... }` — pattern matching.
- `return expr;`
- `assert expr;` or `assert(expr, "message");`
- `break;`
- `continue;`
- Blocks: `{ stmt* }`.
- Expression statements: `expr;` (where allowed).

See the relevant chapters of this specification for details.

Executable entrypoint (initial rule):

- A minimal executable module defines exactly one top-level function:

  ```silk
  fn main() -> int {
    return 0;
  }
  ```

- This `main` function takes no parameters and returns `int`. The front-end
 enforces this shape for executable builds before code generation.

### Optionals & Mutability

- Declare optionals: `let x: T? = None;` or `let x: Option(T) = None;`.
- Create values: `None`, `Some(value)`.
- Use:
 - `user.profile?.email` — optional chaining.
 - `email ?? "default@example.com"` — coalescing.

Mutability:

- Parameters and references are immutable by default.
- Grant mutation via `mut`:
 - in function definition: `fn reset(mut r: &Runner) { ... }`,
 - at call site (syntax per spec).

### Structs, Impl Blocks, Interfaces

- Structs: `struct Frame { seq: u32, size: u16, flag: u8 }`
 - pure data, well-defined layout.
- Impl blocks: `impl Frame { fn size_bits(self: &Frame) -> u32 { ... } }`
- Interfaces:

  ```silk
  interface Element {
    fn onclick(event: &Event) -> void;
  }

  impl Button as Element {
    fn onclick(self: &Button, event: &Event) -> void { ... }
  }
  ```

See [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/) and `Interfaces` for details.

### Regions & Buffers

- Regions (fixed-size allocation context):
 - declare: `const region region_buf: u8[1024];`
 - use: `with region_buf { let p: &Frame = new Frame{ ... }; }`
 - anonymous: `with 1024 { let p: &Frame = new Frame{ ... }; }`
- Buffers:
 - intrinsic `Buffer(T)` with `(ptr, capacity)`,
 - unsafe primitive underpinning higher-level collections.
- Allocation:
 - `new` uses the active region inside `with` (see [`Regions`](/silk/docs/language/regions/)).

### Concurrency

- Function modifiers:
 - `fn` — normal.
 - `async fn` — `await`-able; calling yields `Promise(T)`.
 - `task fn` — runs in parallel on a worker thread; calling yields `Task(T)`.
 - `async task fn` — `async` + `task`; calling yields `Promise(Task(T))`.
- Structured block:

	  ```silk
	  async fn get_dashboard_data() -> Dashboard {
	    // Note: the scheduler-backed `async { ... }` semantics are still design work,
	    // but the compiler implements `Task(T)`/`Promise(T)` handles, `yield`, and `await`.
	    let mut user: User;
	    let mut orders: Order[];

	    async {
	      let user_promise = fetch_user_profile(123);
	      let orders_promise = fetch_recent_orders(123);
	      user = await user_promise;
	      orders = await orders_promise;
	    }

	    return Dashboard(user, orders);
	  }
	  ```

 To receive task values, use `yield` inside a task context (`task { ... }` or `task fn`):

  ```silk
  task fn worker () -> int { return 42; }

  async fn main () -> int {
    let h = worker();
    task {
      let value: int = yield h;
      return value;
    }
  }
  ```

See [`Concurrency`](/silk/docs/language/concurrency/) for deeper semantics.

### Formal Silk

- `#const` — formal Silk declarations used inside specifications (not available at runtime).
- `#require` — preconditions.
- `#assure` — postconditions.
- `#assert` — block-local proof obligations.
- `#invariant` — invariants.
- `#variant` — termination measures.
- `#monovariant` — monotonic measures.
- `theory` / `#theory` — reusable proof obligations.

`#require` / `#assure` appear before functions; `#invariant` / `#variant` / `#monovariant` appear before loops; `#const` and `#assert` appear inside blocks. See `Formal Silk`.

### External Declarations & ABI (Quick View)

- Declare external bindings:

  ```silk
  ext foo = fn (string) -> void;
  ext bar = u32;
  ```

- Strings:
 - Silk `string` is internally `{ ptr, len }`,
 - C side uses `SilkString { char *ptr; int64_t len; }` for embedding,
 - `ext` calls to typical C APIs may pass `const char *` derived from `string` where appropriate.

See `External Declarations (`ext`)` and `C99 ABI and `libsilk.a`` for full details.

## Silk Syntax Tour (Soup to Nuts)

This document is an example-driven tour of Silk’s **surface syntax**, from a
single-file “hello world” through modules/packages, declarations, statements,
expressions, and the Formal Silk verification directives.

This guide complements (not replaces):

- [`Formal Grammar Spec`](/silk/docs/language/grammar/) (the exact grammar the parser accepts),
- the concept chapters in this specification (semantics and checker rules),
- `the implementation status` (what works end-to-end today),
- and [`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/) (error codes for unsupported forms).

### Notes

This specification is the canonical specification, and many documents describe both:

- the full language design, and
- Silk currently (what parses, type-checks, and code-generates today).

This tour follows the same approach:

- examples labeled “Example” are intended to compile in the Supported forms,
- examples labeled “Design” illustrate planned syntax and are not necessarily implemented.

When in doubt, prefer:

- [`Formal Grammar Spec`](/silk/docs/language/grammar/) for syntax,
- `the implementation status` for current end-to-end support,
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/) for working example programs.

### 0. Minimal Executable Module

The smallest executable is a module with a `main` function:

```silk
fn main () -> int {
  return 0;
}
```

Notes:

- Most statements end with `;`.
- Blocks are `{ stmt* }`.
- The entrypoint for an executable build is `main` returning `int` (see
 ``silk` CLI` for the CLI rules and supported targets).

### 1. Lexical Basics

#### Whitespace and comments

Whitespace (spaces, tabs, newlines) is generally allowed between tokens.

Comments:

```silk
// Line comment
/* Block comment (non-nesting) */
```

Doc comments (tooling-only; see [`Silkdoc (Documentation Comments)`](/silk/docs/language/doc-comments/)):

```silk
/// Line doc comment
/**
 * Block doc comment
 *
 * @example silk
 * fn main () -> int { return 0; }
 */
fn main () -> int {
  return 0;
}
```

#### Identifiers and qualified names

Names are often qualified with `::`:

```silk
package my_app::core;

import std::strings;

fn main () -> int {
  let s: string = std::strings::trim(" hi ");
  return 0;
}
```

#### Formal Silk directive tokens (`#...`)

Formal Silk directives like `#require` and `#invariant` are **not comments**.
They are real tokens and are parsed as part of the language (see
`Formal Silk`).

```silk
#require x >= 0;
#assure result == x + 1;
fn inc (x: int) -> int {
  return x + 1;
}
```

### 2. Source File Structure: `package`/`module`, `import`, then declarations

Top-level ordering is enforced (see [`Packages, Imports, and Exports`](/silk/docs/language/packages-imports-exports/)):

1. Optional `package ...;` **or** `module ...;`
2. Zero or more `import ...;` declarations as a contiguous block
3. All other top-level declarations (`fn`, `let`, `struct`, `enum`, `impl`, …)

#### `package`

```silk
// app/main.slk
package app;

fn main () -> int {
  return 0;
}
```

#### `module` (compile-time-only module values)

```silk
// crypto/sha256.slk
module crypto::sha256;
```

Modules can declare interface conformance (design surface is implemented):

```silk
// drivers/uart.slk
module drivers::uart as Device;
```

### 3. Imports and Exports

See [`Packages, Imports, and Exports`](/silk/docs/language/packages-imports-exports/) for the full import/export model.

#### Package imports

```silk
package app;

import std::strings;

fn main () -> int {
  let s: string = trim(" hi "); // may be visible unqualified in the current subset
  let t: string = std::strings::trim(" hi ");
  return 0;
}
```

#### File imports (`from "..."`)

Named import:

```silk
// main.slk
import { answer as the_answer } from "./util.slk";

fn main () -> int {
  return the_answer;
}
```

Default import (binds a default export if present, otherwise a namespace):

```silk
// module.slk
export default fn () -> int {
  return 3;
}
```

```silk
// main.slk
import foo from "./module.slk";

fn main () -> int {
  return foo();
}
```

#### Named exports and re-exports

Export a declaration directly:

```silk
// util.slk
export let answer: int = 42;

export fn add1 (x: int) -> int {
  return x + 1;
}
```

Re-export an in-scope name:

```silk
// api.slk
import { answer } from "./util.slk";
export { answer as the_answer };
```

### 4. Top-Level Declarations (Overview + Examples)

This section shows the core top-level declaration forms:

- bindings: `const`, `let`, `var`
- functions: `fn` (plus `pure`/`async`/`task`)
- type aliases: `type`
- types: `struct`, `enum`, `interface`, `impl`, `error`
- external declarations: `ext`
- tests: `test`
- Formal Silk: `theory` (and `#...` directives)

#### 4.1 Bindings: `const`, `let`, `let mut`, `var`

Supported forms (Supported forms requires initializers; see `E2015`):

```silk
fn main () -> int {
  const answer: int = 42;
  let x: int = answer;
  let mut y: int = 0;
  var z: int = 1; // `var` is an alias for `let mut` (current subset)

  y = y + 1;
  z += 2;
  return x + y + z;
}
```

Notes:

- `const` initializers must be compile-time evaluable in the Supported forms
 (see `E2041`).
- Only `let mut`/`var` bindings are assignable lvalues (see
 [`Mutability`](/silk/docs/language/mutability/) and `Operators`).
- Destructuring `let` bindings are supported for struct values:

  ```silk
  struct User { id: u64, name: string }
  let (id, name) = User{ id: 123, name: "alice" };

  struct Record { id: u64, data: string }
  let { data as d, id as i } = Record{ id: 456, data: "other" };
  ```

 Array destructuring is also supported:

  ```silk
  let records: Record[] = [{ id: 123, data: "a" }, { id: 456, data: "b" }];
  let [a, b] = records;
  ```

 Enum destructuring is also supported:

  ```silk
  import std::result;

  fn main () -> int {
    type R = std::result::Result(int, int);
    let Ok(value) = R.ok(7);
    return value;
  }
  ```

#### 4.2 Functions: `fn` (plus `pure`, `async`, `task`)

Basic function declaration:

```silk
fn add (x: int, y: int) -> int {
  return x + y;
}
```

`pure fn` (restricted subset; see `Function Disciplines (`pure`, `task`, `async`)`):

```silk
pure fn inc (x: int) -> int {
  return x + 1;
}
```

`async fn` / `task fn` / `async task fn` (handles; see [`Concurrency`](/silk/docs/language/concurrency/)):

```silk
task fn worker () -> int {
  return 7;
}

async fn main () -> int {
  task {
    let t = worker(); // Task(int)
    let value: int = yield t;
    return value;
  }
}
```

##### Parameters: `mut`, defaults, and varargs

Mutable reference parameters require `mut` both in the signature and at the
call site (see [`Mutability`](/silk/docs/language/mutability/)):

```silk
struct Pair { a: int, b: int }

fn bump_a (mut p: &Pair) -> void {
  p.a += 1;
}

fn main () -> int {
  let mut p: Pair = Pair{ a: 1, b: 2 };
  bump_a(mut p);
  return p.a;
}
```

Default arguments (Supported forms restricts default expressions to a constant/literal subset):

```silk
fn add2 (x: int, y: int = 2) -> int {
  return x + y;
}
```

Varargs (final parameter prefixed by `...`; see [`Varargs (Variable Arguments)`](/silk/docs/language/varargs/)):

```silk
fn log (fmt: string, ...args: std::fmt::Arg) -> void {
  std::io::println(fmt, args);
}
```

##### Generic function parameter split (`;`) (Design, parsed but rejected)

Generic functions use `;` to separate compile-time parameters from value parameters:

```silk
// Design (currently rejected with `E2016`).
fn get_first(T, N: int; xs: &T[N]) -> T {
  return xs[0];
}
```

#### 4.3 Function expressions (lambdas)

Supported forms (non-capturing expression body):

```silk
fn main () -> int {
  let add = fn (x: int, y: int) -> x + y;
  return add(1, 2);
}
```

Supported forms (block body with explicit return type):

```silk
fn main () -> int {
  let add = fn (x: int, y: int) -> int {
    return x + y;
  };
  return add(1, 2);
}
```

Capturing closures are supported as a restricted subset; see `Types`
and [`Memory Model (Stack, Heap, and Moves)`](/silk/docs/language/memory-model/).

#### 4.4 Type aliases: `type`

Basic alias:

```silk
type I = int;
```

Optional kind tags (validated by the checker; see `Types`):

```silk
type struct UserId = int;
type fn IntAdder = fn(int, int) -> int;
type pure fn PureIntAdder = fn(int, int) -> int;
```

#### 4.5 Structs: `struct` and `impl`

Struct declarations (fields use `name: Type`, optional default with `=`):

```silk
struct Point {
  x: int = 0,
  y: int = 0,
}
```

Struct literals:

```silk
fn main () -> int {
  let p1: Point = Point{ x: 1, y: 2 };
  let x = p1.x;

  // Shorthand field init (`x` means `x: x`):
  let y: int = 3;
  let p2: Point = Point{ x, y };

  return x + p2.y;
}
```

Inferred struct literals require an expected struct type context:

```silk
fn main () -> int {
  let p: Point = { x: 1, y: 2 };
  return p.x + p.y;
}
```

Heap allocation (`new`) produces a `&Struct` reference in the Supported forms:

```silk
struct Boxed { value: int }

fn main () -> int {
  let b: &Boxed = new Boxed{ value: 7 };
  return b.value;
}
```

Attach methods with `impl` (see [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)):

```silk
impl Point {
  public fn sum (self: &Point) -> int {
    return self.x + self.y;
  }
}

fn main () -> int {
  let p: Point = Point{ x: 1, y: 2 };
  return p.sum();
}
```

Single inheritance (current surface is implemented; see [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)):

```silk
// Design shape (field/layout rules and current subset limits are documented).
struct Base { x: int = 0 }
struct Derived extends Base { y: int = 0 }
```

#### 4.6 Enums: `enum` + [`match`](/silk/wiki/language/flow-match/) expression

```silk
enum Color {
  Red,
  Rgb(u8, u8, u8),
}

fn to_int (c: Color) -> int {
  return match c {
    Color::Red => 0,
    Color::Rgb(r, g, b) => (r as int) + (g as int) + (b as int),
  };
}
```

See ``enum` Types` and ``match` Expression (and Statement)`.

#### 4.7 Interfaces and `impl ... as ...`

```silk
interface Counter {
  fn inc() -> void;
  fn get() -> int;
}

struct Cell { value: int = 0 }

impl Cell as Counter {
  fn inc (mut self: &Cell) -> void {
    self.value += 1;
  }

  fn get (self: &Cell) -> int {
    return self.value;
  }
}
```

See `Interfaces`.

#### 4.8 Typed errors: `error`, `panic`, `T | ErrorType...`, [`match`](/silk/wiki/language/flow-match/) statement, `?`

Error type declaration:

```silk
import std::arrays;

error OutOfBounds {
  index: i64,
  len: i64
}
```

Error-producing signatures use `|`:

```silk
fn get_at (xs: std::arrays::Slice(u8), index: i64) -> u8 | OutOfBounds {
  if index < 0 || index >= xs.len() {
    panic OutOfBounds { index: index, len: xs.len() };
  }
  return xs.get(index);
}
```

Handling typed errors uses the [`match`](/silk/wiki/language/flow-match/) **statement** form:

```silk
fn main () -> int {
  let xs_arr: u8[3] = [1, 2, 3];
  let xs: std::arrays::Slice(u8) = { ptr: xs_arr as u64, len: 3 };

  match (get_at(xs, 10)) {
    value => {
      return value as int;
    },
    err: OutOfBounds => {
      std::abort();
    }
  }
}
```

Propagating errors from calls uses postfix `?`:

```silk
// Supported when `main` declares a compatible error set.
fn main () -> int | OutOfBounds {
  let xs_arr: u8[3] = [1, 2, 3];
  let xs: std::arrays::Slice(u8) = { ptr: xs_arr as u64, len: 3 };

  let x: u8 = get_at(xs, 0)?;
  return x as int;
}
```

See `Typed Errors (`error`, `panic`, and `T | ErrorType...`)`.

#### 4.9 External declarations: `ext`

External function binding (symbol name optional; see `External Declarations (`ext`)`):

```silk
export ext puts = fn(string) -> int;
export ext c_abort "abort" = fn() -> void;
export ext errno "errno" = int;
```

Note: C variadics (`printf`-style `...`) via `ext` are not implemented yet; see
[`Varargs (Variable Arguments)`](/silk/docs/language/varargs/) and `External Declarations (`ext`)`.

See also: `C99 ABI and `libsilk.a`` (C ABI) and [`include/silk/silk.h`](https://github.com/oro-computer/silk/blob/master/include/silk/silk.h).

#### 4.10 Tests: `test`

```silk
test "addition works" {
  if (1 + 2) != 3 {
    std::abort();
  }
}
```

See `Testing` and run with `silk test`.

#### 4.11 Formal Silk theories: `theory`

Top-level theory (exportable/importable):

```silk
export theory nonzero (x: int) {
  #require x != 0;
}
```

Apply a theory inside a function:

```silk
import { nonzero } from "./theories.slk";

fn main () -> int {
  let x: int = 1;
  #theory nonzero(x);
  return 0;
}
```

Inline (block-local) theories use the same `#theory` token and are
disambiguated from theory use by `{ ... }` (inline declaration) vs `;` (use):

```silk
fn main (x: int, y: int) -> int {
  #theory local_sum_nonzero (x: int, y: int) {
    #const z = x + y;
    #assure z != 0;
  }

  #theory local_sum_nonzero(x, y);
  return 0;
}
```

See `Formal Silk`.

### 5. Types (Surface Forms)

See `Types` for full details and implementation limits.

#### Primitive types

```text
bool, i8/u8, i16/u16, i32/u32, i64/u64, int, f32/f64, char, string, void, Instant, Duration
```

#### Optional types

```silk
fn main () -> int {
  let a: int? = None;
  let b: int? = Some(7);
  let c: int = b ?? 0;
  return c;
}
```

Nested optionals use `??` in type position (`T??` means “optional of optional”):

```silk
fn main () -> int {
  let x: int?? = Some(Some(1));
  let y: int? = x ?? None;
  return (y ?? 0);
}
```

Optional [`match`](/silk/wiki/language/flow-match/) expressions are the explicit form of optional consumption:

```silk
fn main () -> int {
  let x: int? = Some(7);
  let y: int = match x {
    None => 0,
    Some(v) => v,
  };
  return y;
}
```

#### References

```silk
fn sum (p: &Point) -> int {
  return p.x + p.y;
}
```

#### Arrays and slices

```silk
fn main () -> int {
  let xs: int[] = [1, 2, 3];
  return xs[0];
}
```

Fixed-length arrays use `T[N]`:

```silk
fn main () -> int {
  let xs: int[3] = [1, 2, 3];
  return xs[2];
}
```

#### Function types

```silk
type IntBinOp = fn(int, int) -> int;

fn main () -> int {
  let add: IntBinOp = fn (x: int, y: int) -> x + y;
  return add(1, 2);
}
```

#### Applied types and generics

Generic parameter lists on `struct`/`interface`/`impl` are implemented:

```silk
struct Box(T) { value: T }

fn main () -> int {
  let b: Box(int) = { value: 1 };
  return b.value;
}
```

See [`Generics (Monomorphized)`](/silk/docs/language/generics/) for Supported forms limits (notably `E2016` for
const parameters/arguments and generic functions).

### 6. Statements (Inside Blocks)

The statement grammar is summarized in [`Formal Grammar Spec`](/silk/docs/language/grammar/) and detailed
in the relevant chapters of this specification.

#### `if` / `else`

```silk
fn main () -> int {
  let x: int = 1;
  if x == 0 {
    return 0;
  } else {
    return 1;
  }
}
```

#### [`loop`](/silk/wiki/language/flow-loop/), [`while`](/silk/wiki/language/flow-while/), `for`

```silk
fn main () -> int {
  let mut i: int = 0;
  while i < 3 {
    i += 1;
  }
  return i;
}
```

`for` over a range form (special-cased surface; see ``for` Loop`):

```silk
fn main () -> int {
  let mut sum: int = 0;
  for i in 0 .. 5 {
    sum += i;
  }
  return sum;
}
```

C-style `for` header:

```silk
fn main () -> int {
  let mut sum: int = 0;
  for (let mut i: int = 0; i < 5; i += 1) {
    sum += i;
  }
  return sum;
}
```

#### `break`, `continue`, `return`

```silk
fn main () -> int {
  let mut i: int = 0;
  loop {
    i += 1;
    if i < 3 {
      continue;
    }
    break;
  }
  return i;
}
```

#### `assert` and `panic`

```silk
fn main () -> int {
  assert 1 + 2 == 3;
  assert(2 + 2 == 4, "math is broken");
  return 0;
}
```

`panic` is used for typed errors (see `Typed Errors (`error`, `panic`, and `T | ErrorType...`)`):

```silk
panic OutOfBounds { index: 1, len: 0 };
```

#### [`match`](/silk/wiki/language/flow-match/) statement (typed errors)

See `Typed Errors (`error`, `panic`, and `T | ErrorType...`)` for the Terminal Arm Rule and the supported
pattern forms.

#### `async { ... }` and `task { ... }`

Structured blocks (implemented as lexical blocks in the Supported forms; see
[`Concurrency`](/silk/docs/language/concurrency/)):

```silk
async fn main () -> int {
  async {
    // async region
  }
  task {
    // task region
  }
  return 0;
}
```

#### Concurrency operators: `await`, `await *`, `yield`, `yield *`

`await` unwraps `Promise(T)` values inside `async fn`:

```silk
async fn add2 (x: int) -> int {
  return x + 2;
}

async fn main () -> int {
  let p = add2(1); // Promise(int)
  let v: int = await p;
  return v;
}
```

`await *` awaits a collection of promises and yields a collected `T[]`:

```silk
async fn add1 (x: int) -> int {
  return x + 1;
}

async fn main () -> int {
  let values: int[] = await * [add1(1), add1(2), add1(3)];
  return values[0] + values[1] + values[2];
}
```

`yield` / `yield *` interact with `Task(T)` values (used inside `task` regions in
the Supported forms):

```silk
task fn producer (n: int) -> int {
  var i: int = 0;
  while i < n {
    yield i;
    i += 1;
  }
  return n;
}

async fn main () -> int {
  task {
    let t = producer(2); // Task(int)
    let values: int[] = yield * t;
    return values[0] + values[1] + values[2];
  }
}
```

### 7. Expressions (Precedence + Demonstrations)

Silk expressions follow a conventional precedence hierarchy. For the exact
productions, see [`Formal Grammar Spec`](/silk/docs/language/grammar/).

#### Literals and other primary expressions

See the the relevant chapters of this specification concept documents for precise rules.

```silk
fn main () -> int {
  // Booleans.
  let b: bool = true;

  // Integers and floats.
  let i: int = 42;
  let u: u8 = 0xFF;
  let f: f64 = 3.14;

  // Characters and strings.
  let ch: char = 'A';
  let s1: string = "hello";
  let s2: string = `raw \n no escapes`;

  // Durations.
  let d: Duration = 10ms;

  // Optionals.
  let opt: int? = Some(i);
  let x: int = opt ?? 0;

  // Arrays.
  let xs: int[] = [1, 2, 3];

  assert b;
  assert x == 42;
  assert xs[0] == 1;
  assert u == 0xFF;
  assert s1 == "hello";
  assert s2 == `raw \n no escapes`;
  assert ch == 'A';
  assert (f as int) == 3;
  assert (d as int) == (d as int);

  // `d` exists to demonstrate duration literal syntax. See `Duration Literals`.
  return 0;
}
```

#### Postfix forms: calls, fields, indexing, casts, `?`, `++/--`

```silk
struct Point { x: int, y: int }

fn main () -> int {
  let xs: int[] = [10, 20, 30];
  let a: int = xs[0];
  let b: int = (a + 1) as int;
  let c: int = Point{ x: 1, y: 2 }.x;
  return b + c;
}
```

#### `as` and `as raw`

`as` performs explicit numeric/shape casts and `as raw` performs raw bit casts
for scalar types (see `Operators`).

```silk
fn main () -> int {
  let bits: u64 = (1.0 as f32) as raw u64;
  let f: f32 = bits as raw f32;
  return f as int;
}
```

#### Unary forms: `!`, `~`, `-`, `new`, `await`, `yield`, `mut`, `++/--`

```silk
fn main () -> int {
  let mut x: int = 0;
  ++x;
  x++;
  if !(x == 2) {
    return 1;
  }
  return 0;
}
```

`mut <expr>` is permitted only where a mutable borrow is required (most
commonly, in call arguments and method receivers):

```silk
struct Pair { a: int, b: int }

fn bump (mut p: &Pair) -> void {
  p.a += 1;
}

fn main () -> int {
  let mut p: Pair = Pair{ a: 0, b: 0 };
  bump(mut p);
  return p.a;
}
```

#### Arithmetic, bitwise, comparisons, and boolean operators

```silk
fn main () -> int {
  let a: int = 1 + 2 * 3;
  let b: int = (a << 1) | 1;
  if (b >= 0) && (b != 0) {
    return b;
  }
  return 0;
}
```

#### Optional operators: `?.` and `??`

```silk
struct User { email: string }

fn main () -> int {
  let user: User? = Some(User{ email: "a@b.c" });
  let email: string = user?.email ?? "unknown";
  if email == "a@b.c" {
    return 0;
  }
  return 1;
}
```

#### Typed error propagation: postfix `?` on calls

```silk
fn main () -> int | OutOfBounds {
  let x: u8 = get_at([1, 2, 3], 0)?;
  return x as int;
}
```

### 8. Formal Silk (Verification) Syntax

Formal Silk is Silk’s compile-time verification surface (Z3-backed). It uses
directive tokens that attach to functions and loops:

- function contracts: `#require`, `#assure`, `#theory`
- loop contracts: `#invariant`, `#variant`, `#monovariant`
- formal Silk declarations: `#const`
- block-local proof obligations: `#assert`
- reusable proof bundles: `theory` / `#theory`

See `Formal Silk` for the exact verifier model and
current restrictions.

#### Contracts on functions

```silk
#require x >= 0;
#assure result == x + 1;
fn inc (x: int) -> int {
  return x + 1;
}
```

#### Loop invariants, variants, and monovariants

```silk
fn main () -> int {
  let limit: int = 3;
  #const original_limit = limit;

  let mut i: int = 0;
  #invariant i >= 0;
  #invariant i <= original_limit;
  #variant original_limit - i;
  #monovariant i;
  while i < limit {
    i += 1;
  }
  return 0;
}
```

#### Theories (`theory` / `#theory`)

```silk
export theory add_commutes (x: int, y: int) {
  #assure (x + y) == (y + x);
}

#theory add_commutes(x, y);
fn add (x: int, y: int) -> int {
  return x + y;
}
```

### 9. Next References

If you want more detail on a specific construct, jump to:

- Syntax: [`Formal Grammar Spec`](/silk/docs/language/grammar/)
- Types: `Types`, [`Generics (Monomorphized)`](/silk/docs/language/generics/)
- Operators: `Operators`
- Flow control: [`Flow Control Overview`](/silk/docs/language/flow-overview/) and the relevant chapters of this specification
- Modules/imports/exports: [`Packages, Imports, and Exports`](/silk/docs/language/packages-imports-exports/)
- Optionals: [`Optional`](/silk/docs/language/optional/)
- Typed errors: `Typed Errors (`error`, `panic`, and `T | ErrorType...`)`
- Concurrency: [`Concurrency`](/silk/docs/language/concurrency/)
- Formal verification: `Formal Silk`

## Formal Grammar Spec

This document contains the formal grammar and lexical specification for Silk as used by the compiler implementation.

### Lexical Structure (Overview)

The lexer operates over:

- Whitespace and comments (trivia):
 - spaces, tabs, newlines;
 - line comments starting with `//` and continuing to the end of the line;
 doc line comments start with `///` and follow the same lexical rules;
 - block comments starting with `/*` and ending with the next `*/` (non‑nesting);
 doc block comments start with `/**` and also end at the next `*/`.

 Both doc-comment forms are still **trivia** for the parser (they do not
 affect the syntax tree), but tooling may preserve and parse their text for
 documentation generation as specified in [`Silkdoc (Documentation Comments)`](/silk/docs/language/doc-comments/).
- Identifiers:
 - sequences of Unicode letters, digits, and `_`, with language-specific rules for leading characters.
 - Keywords:
 - packages and imports: `package`, `module`, `import`,
 file imports: `from`,
 - control flow: `if`, `else`, [`loop`](/silk/wiki/language/flow-loop/), [`while`](/silk/wiki/language/flow-while/), `for`, `in`, [`match`](/silk/wiki/language/flow-match/), `return`, `panic`, `break`, `continue`, `assert`, `await`, `yield`,
 - attributes and conditional compilation: `attr`,
 - declarations and mutability: `export`, `public`, `private`, `default`, `const`, `let`, `var`, `mut`, `fn`, `test`, `theory`, `type`,
 - types and declarations: `struct`, `enum`, `error`, `interface`, `impl`, `as`, `raw`, `extends`, `pure`, `task`, `async`,
 - memory and regions: `move`, `region`, `with`, `new`,
 - other operators: `sizeof`, `alignof`, `offsetof`, `typename`,
 - optionals and literals: `None`/`none`, `Some`, `true`, `false`, `null`,
 - verification, compile-time embedding, and external declarations: `ext`,
 Formal Silk directives `#const`, `#require`, `#assure`, `#assert`,
 `#invariant`, `#variant`, `#monovariant`, `#theory`, and the
 compile-time file embed expression `#embed`,
 - other keywords as listed in the spec.

 Keywords are lexed as distinct tokens, but in name positions (for example
 qualified-name segments like [`std::test`](/silk/docs/std/test/), function/method names, and member
 access like `value.test`) the parser accepts keywords anywhere an
 identifier is expected.

 The `#require` / `#assure` / `#assert` / `#invariant` / `#variant` /
 `#monovariant` / `#const` / `#theory` / `#embed` forms are **not** comments;
 they are first-class lexical tokens that participate in the normal grammar.
 Formal Silk directives are handled by the verifier; `#embed` is an ordinary
 expression that embeds a file at compile time. A directive token begins with
 `#` followed by optional horizontal whitespace and the directive name (so
 `#require` and `# require` are equivalent spellings). When these sequences
 appear inside a `//` line comment or anywhere inside a `/* ... */` block
 comment, the surrounding comment is treated as trivia and the directive
 spellings are ignored by the lexer.
- Literals:
 - numeric, duration, boolean, character, string, regexp, aggregate (see `*literals-*.md`).
 - String literals have two surface forms:
 - `"..."` (escape sequences),
 - `` `...` `` (raw/no-escape).
 - Regular expression literals have a JavaScript-style surface form:
 - `/pattern/flags` (see [`Regular Expression Literals`](/silk/docs/language/literals-regexp/)).
 - Disambiguation is context-sensitive (like JavaScript): the parser only
 recognizes a regex literal in expression-start positions where a
 `Primary` expression may begin; otherwise `/` is parsed as the division
 operator.
 - Because `//` starts a line comment, an empty regex literal `//` is not a
 valid token sequence; use `/(?:)/` for an explicit empty pattern.
- Operators and delimiters:
 - as listed in `Operators` (assignment, arithmetic, logical, bitwise, ranges, `?.`, `??`, `::`, `->`, `=>`, etc.).

The lexer must implement longest-token rules for ambiguous prefixes (e.g. `...` vs `..=` vs `..`).

#### Lexical Grammar for Whitespace and Comments

For the purposes of the grammar, whitespace and comments are treated as **trivia**
that may appear between any two tokens and are ignored by the parser:

- `Trivia ::= (Whitespace | LineComment | BlockComment)+`
- `Whitespace ::= ' ' | '\t' | '\r' | '\n'`
- `LineComment ::= '//' LineCommentChar* Newline?`
- `LineCommentChar ::= any character except '\n'`
- `Newline ::= '\n'`
- `BlockComment ::= '/*' BlockCommentChar* '*/'`
- `BlockCommentChar ::= any character except the end of input`

Block comments are **not nesting**: a `/*` sequence inside an existing
`BlockComment` has no special meaning and is treated as part of the comment
text until the first following `*/`. Doc-style comments such as `/** ... */`
are just syntactic sugar for `BlockComment` at the lexical level.

### High-Level Grammar Skeleton

At a high level, the language can be structured as:

- Compilation unit:

 - `Module ::= (PackageDecl | ModuleDecl)? ImportDecl* TopLevelDecl*`

- Top-level declarations:

 - `AttrAnnot ::= 'attr' '(' AttrItemListOpt ')'`
 - `AttrItemListOpt ::= AttrItemList`
 - `AttrItemList ::= AttrItem (',' AttrItem)* ','?`
 - `AttrItem ::= NameToken ( AttrOp AttrValue )?`
 - `AttrOp ::= '=' | '<' | '<=' | '>' | '>='`
 - `AttrValue ::= 'true' | 'false' | IntLiteral | StringLiteral | NameToken`

 - `TopLevelDecl ::= AttrAnnot* (PackageDecl | ModuleDecl | ImportDecl | InlineModuleDecl | UsingDecl | ReExportDecl | DefaultExportDecl | ExportableDecl | TestDecl)`
 - `ExportableDecl ::= FnDecl | LetDecl | TypeDecl | StructDecl | EnumDecl | InterfaceDecl | ImplDecl | ExtDecl | ...`

- Packages and imports:

 The surface syntax for packages, imports, and exports is specified in detail in
 [`Packages, Imports, and Exports`](/silk/docs/language/packages-imports-exports/). The grammar in this file mirrors
 the currently Supported forms:

 - `PackageDecl ::= 'package' PackagePath ';'`
 - `ModuleDecl ::= 'module' PackagePath ModuleAsOpt ';'`
 - `InlineModuleDecl ::= ExportModifier 'module' NameToken '{' InlineModuleItem* '}'`
 - `InlineModuleItem ::= InlineModuleDecl | UsingDecl | ExportableDecl`
 - `NameToken ::= Identifier | Keyword`
 - `PackagePath ::= NameToken ('::' NameToken)*`
 - `ModuleAsOpt ::= ('as' QualifiedName TypeArgListOpt)`

 - `ImportDecl ::= 'import' ImportSpec ';'`
 - `ImportSpec ::= ImportPath | FileImportSpec | FileDefaultImportSpec | AmbientFileImportSpec`
 - `ImportPath ::= ('::')? NameToken ('::' NameToken)*`
 - `ImportFrom ::= StringLiteral | PackagePath`
 - `AmbientFileImportSpec ::= StringLiteral`
 - `FileImportSpec ::= '{' ImportBindingListOpt '}' 'from' ImportFrom`
 - `FileDefaultImportSpec ::= NameToken 'from' ImportFrom`
 - `ImportBindingListOpt ::= ImportBindingList`
 - `ImportBindingList ::= ImportBinding (',' ImportBinding)* ','?`
 - `ImportBinding ::= NameToken ('as' NameToken)?`

 - `ReExportDecl ::= 'export' '{' ReExportBindingListOpt '}' ';'`
 - `ReExportBindingListOpt ::= ReExportBindingList`
 - `ReExportBindingList ::= ReExportBinding (',' ReExportBinding)* ','?`
 - `ReExportBinding ::= NameToken ('as' NameToken)?`

 - `DefaultExportDecl ::= 'export' 'default' NameToken ';'`

 - `UsingDecl ::= 'using' Identifier '=' QualifiedName ';' | 'using' QualifiedName UsingAsOpt ';'`
 - `UsingAsOpt ::= ('as' Identifier)`

 - `ExportModifier ::= 'export'?`
 - `FnExportModifier ::= ('export' AttrAnnot* ('default')?)?`

 For top-level declarations that can be exported, the `ExportModifier`
 appears before the declaration keyword (currently `fn`, `let`, `ext`, `type`,
 `struct`, `enum`, `theory`, `error`, and `interface`):

 - `FnDecl ::= FnSpecs FnExportModifier FnModifierOpt 'fn' FnGenericParamListOpt FnNameOpt FnSignature FnBody`
 - `FnBody ::= Block | ';'`
 - `FnModifierOpt ::= FnModifier*`
 - `FnModifier ::= 'const' | 'pure' | 'task' | 'async'`
 - `FnGenericParamListOpt ::= GenericParamList`
 - `FnNameOpt ::= NameToken`
 - `FnSpecs ::= (FnPrecondition | FnPostcondition | FnContractTheory)*`
 - `FnPrecondition ::= '#require' Expr ';'`
 - `FnPostcondition ::= '#assure' Expr ';'`
 - `FnContractTheory ::= '#theory' QualifiedName '(' ArgListOpt ')' ';'`

 Declaration attributes may also appear immediately after `export` /
 `public` before the exported declaration keyword. This supports forms such
 as `export attr(abi=c) fn add_i64 (...) -> i64 { ... }`; it is equivalent to
 the prefix annotation form `attr(abi=c) export fn add_i64 (...) -> i64 { ... }`.

 Formal Silk theories may be declared either at top level (exportable) or
 inline inside blocks (non-exportable):

 - `TheoryDecl ::= TheorySpecs ExportModifier 'theory' Identifier '(' TheoryParamsOpt ')' '{' TheoryBodyItem* '}'`
 - `TheorySpecs ::= (TheoryPrecondition | TheoryPostcondition)*`
 - `TheoryPrecondition ::= '#require' Expr ';'`
 - `TheoryPostcondition ::= '#assure' Expr ';'`
 - `TheoryParamsOpt ::= TheoryParams`
 - `TheoryParams ::= TheoryParam (',' TheoryParam)* ','?`
 - `TheoryParam ::= Identifier ':' Type`
 - `TheoryBodyItem ::= SpecConstStmt | SpecTheoryStmt | TheoryRequires | TheoryInvariant | TheoryEnsures | TheoryVariant`
 - `TheoryRequires ::= '#require' Expr ';'`
 - `TheoryInvariant ::= '#invariant' Expr ';'`
 - `TheoryEnsures ::= '#assure' Expr ';'`
 - `TheoryVariant ::= '#variant' Expr ';'`

 - `FnSignature ::= '(' FnParamsOpt ')' ResultTypeOpt`
 - `FnParamsOpt ::= FnParams`
 - `FnParams ::= GenericParamSectionOpt ';' ParamListOpt | ParamList`
 - `GenericParamSectionOpt ::= GenericParamSection`
 - `GenericParamSection ::= GenericParam (',' GenericParam)* ','?`
 - `ParamListOpt ::= ParamList`
 - `ParamList ::= Param (',' Param)*`
 - `Param ::= VarArgsOpt MutOpt Identifier TypeAnnotationOpt DefaultArgOpt`
 - `VarArgsOpt ::= '...'`
 - `DefaultArgOpt ::= ('=' Expr)`
 - `ResultTypeOpt ::= ('->' TypeNoPipe TypedErrorTypesOpt)?`
 - `TypedErrorTypesOpt ::= ('|' TypeNoPipe)+`

 Notes:

 - When a top-level `;` appears inside the function parameter list, it splits
 **compile-time** parameters (type/const parameters) from **run-time** value
 parameters.
 - The compile-time side uses `GenericParam` syntax (`T` and `N: int`) and does
 not permit `mut`.
 - `FnNameOpt` is currently permitted only for default-exported functions
 (`export default fn (...) { ... }`). For non-default functions, the `fn`
 identifier is required.
 - Default export statements (`export default Name;`) always name an existing
 symbol; they do not permit anonymous exports.
 - The run-time side uses ordinary `Param` syntax (`mut x: T` or `x: T`, with
 the type annotation optional in the Supported forms).
 - If there is no `;`, the entire list is treated as run-time parameters.
 - Default arguments are supported in function parameter lists:
 - any parameter may provide a default expression (`x: int = 1`),
 - defaulted parameters must be **trailing** (once a parameter has a
 default, all subsequent parameters must also have defaults) because
 call syntax is positional-only in the current language subset,
 - and in the initial subset, default expressions are restricted to
 a constant/literal expression subset (no name references), so they can be
 inlined at call sites during lowering.
 - a parameter declared as `T?` with a default has two effective behaviors
 in the initial subset:
 - if the default expression has type `T`, the parameter has effective
 type `T` (the argument may be omitted at call sites, but the callee
 sees a non‑optional value),
 - if the default expression is `None`/`Null`, the parameter remains `T?`
 (the argument may be omitted, and callers may still pass optional
 values explicitly).

 - Varargs parameters are supported:
 - a varargs parameter is declared by prefixing the final parameter with
 `...` (for example `fn f(x: int, ...rest: int) { ... }`),
 - only one varargs parameter is permitted per function and it must be
 **final**,
 - in the Supported forms, varargs parameters:
 - must have an explicit type annotation,
 - may not be `mut`,
 - and may not have a default expression.

 - The `from` string literal is a *module specifier* (either `"..."` or `` `...` ``):
 - strings starting with `./` or `../` are treated as file specifiers and
 resolve to a module by file path,
 - strings starting with [`std/`](https://github.com/oro-computer/silk/tree/master/std/) are treated as std package specifiers and
 resolve through package lookup after `/` is normalized to `::` (a
 trailing `.slk` is stripped for compatibility),
 - other strings are dependency-rooted POSIX module paths matched against
 `[dependencies]` keys. Dot-separated keys match slash-separated path
 prefixes (`my.dep.b` matches `"my/dep/b"`), the longest matching key
 selects the dependency root, and the remainder is resolved under that
 dependency's source-module directory,
 - quoted strings must not contain `::`; package namespace specifiers use the
 unquoted `PackagePath` branch of `ImportFrom`.

 - `BindingDecl ::= LetIntroducer Identifier TypeAnnotationOpt InitializerOpt ';'`
 - `LetIntroducer ::= 'const' | 'let' LetModifierOpt | 'var' LetModifierOpt`
 - `LetModifierOpt ::= LetModifier*`
 - `LetModifier ::= 'mut' | 'move'`
 - `LetDecl ::= ExportModifier BindingDecl`
 - `TypeAnnotationOpt ::= (':' Type)?`
 - `InitializerOpt ::= ('=' Expr)?`

 Type aliases are supported:

 - `TypeDecl ::= ExportModifier 'type' TypeDeclKindOpt Identifier '=' Type ';'`
 - `TypeDeclKindOpt ::= TypeDeclKind`
 - `TypeDeclKind ::= 'struct' | 'enum' | 'error' | 'interface' | 'fn' | 'pure' 'fn'`

 `test` declarations are supported as Zig-inspired top-level test blocks:

 - `TestDecl ::= 'test' StringLiteral? Block`

 FFI declarations are also part of the language grammar:

 - `ExtDecl ::= ExportModifier 'ext' NameToken ExtExternNameOpt '=' Type ';'`
 - `ExtExternNameOpt ::= StringLiteral`

 When `ExtExternNameOpt` is present, it sets the linked external symbol name.
 This allows Silk code to bind a local name that differs from the C/FFI symbol
 name (for example to avoid name collisions in wrapper modules).

 The current compiler implementation supports external declarations (`ext`) whose type
 is either:

 - a `FunctionType` (external functions, callable from Silk), or
 - a supported scalar type (external variables, readable as values in Silk).

 Silk currently **requires an initializer** for runtime bindings
 (`let`/`var`) and for compile-time constant bindings (`const`). Uninitialized
 declarations like `let x: int;` / `const x: int;` are parsed but rejected by
 the checker (see [`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2015`).

 Additionally, `const` initializers must be compile-time evaluable; otherwise
 the compiler reports an error (see [`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2041`).

 In practice, prefer:

 - `let x: int = 0;` for a zero value, or
 - `let x: T? = None;` for an “empty” optional.

 Struct declarations are also accepted by the current parser:

 - `StructDecl ::= StructSpecs ExportModifier 'struct' Identifier GenericParamListOpt StructExtendsOpt ';' | StructSpecs ExportModifier 'struct' Identifier GenericParamListOpt StructExtendsOpt '{' StructFieldListOpt '}'`
 - `StructSpecs ::= StructRequirement*`
 - `StructRequirement ::= '#require' Expr ';'`
 - `StructExtendsOpt ::= ('extends' QualifiedName)`
 - `GenericParamListOpt ::= GenericParamList`
 - `GenericParamList ::= '(' GenericParamListInnerOpt ')'`
 - `GenericParamListInnerOpt ::= GenericParamListInner`
 - `GenericParamListInner ::= GenericParam (',' GenericParam)* ','?`
 - `GenericParam ::= Identifier (':' Type)? ('=' Type)?`
 - `StructFieldListOpt ::= StructFieldList`
 - `StructFieldList ::= StructField (',' StructField)* ','?`
 - `StructField ::= Identifier ':' Type StructFieldDefaultOpt`
 - `StructFieldDefaultOpt ::= ('=' Expr)`

 Notes:

 - Only `#require` directives may appear in `StructSpecs` in the current
 language subset (`#assure` / `#theory` are rejected on `struct`).

 Enum declarations are part of the core language design. They are specified in
 ``enum` Types`.

 - `EnumDecl ::= ExportModifier 'enum' Identifier GenericParamListOpt '{' EnumVariantListOpt '}'`
 - `EnumVariantListOpt ::= EnumVariantList`
 - `EnumVariantList ::= EnumVariant (',' EnumVariant)* ','?`
 - `EnumVariant ::= Identifier EnumVariantPayloadOpt`
 - `EnumVariantPayloadOpt ::= ('(' TypeListOpt ')')`

 Interface and impl declarations are part of the language design and are
 parsed by the front-end as the syntax is implemented:

 - `InterfaceDecl ::= ExportModifier 'interface' Identifier GenericParamListOpt InterfaceExtendsOpt '{' InterfaceItem* '}'`
 - `InterfaceExtendsOpt ::= ('extends' QualifiedName)`
 - `InterfaceMethodDecl ::= 'fn' NameToken FnSignature ';'`
 - `InterfaceItem ::= InterfaceMethodDecl | UsingDecl`

 - `ImplDecl ::= 'impl' QualifiedName GenericParamListOpt ImplAsOpt '{' ImplMemberDecl* '}'`
 - `ImplAsOpt ::= ('as' QualifiedName TypeArgListOpt)`
 - `ImplMemberDecl ::= FnDecl | UsingDecl` (within an `impl` block, `export` is
 reserved for static members with no `self` receiver; instance method
 visibility uses `public`/`private`)

 Note: const-parameter-style generics (`N: int` parameters and integer literal
 type arguments like `Foo(u8, 1024)`) remain tracked work; the front-end parses
 these surface forms but the compiler currently focuses on type parameters
 and monomorphization for type arguments.

 Exception: the nominal optional form `Option(T)` is recognized as sugar for
 `T?` and is accepted in the Supported forms.

- Types (Supported forms):

 - `Type ::= UnionType`
 - `UnionType ::= TypeNoPipe ('|' TypeNoPipe)*`
 - `TypeNoPipe ::= BaseType TypeSuffix`
 - `TypeSuffix ::= TypeSuffixElem TypeSuffix`
 - `TypeSuffixElem ::= OptionalTypeSuffix | ArrayTypeSuffix`
 - `OptionalTypeSuffix ::= '?' | '??'`
 - `ArrayTypeSuffix ::= '[' ']' | '[' ArrayLen ']'`
 - `ArrayLen ::= IntLiteral | Identifier`
 - `BaseType ::= ReferenceType | AttrFunctionType | FunctionType | CFunctionType | '(' Type ')' | SimpleType`
 - `ReferenceType ::= '&' BaseType`
 - `FunctionType ::= 'fn' '(' TypeListOpt ')' ResultTypeOpt`
 - `CFunctionType ::= 'c_fn' '(' TypeListOpt ')' ResultTypeOpt`
 - `AttrFunctionType ::= AttrAnnot FunctionType` (in the Supported forms, this is
 accepted as sugar for selecting ABI variants such as `attr(abi=c) fn (...) -> ...`)
 - `TypeListOpt ::= TypeList`
 - `TypeList ::= Type (',' Type)*`
 - `SimpleType ::= PrimitiveType | NamedType`
 - `PrimitiveType ::= 'bool' | 'i8' | 'u8' | 'i16' | 'u16' | 'i32' | 'u32' | 'i64' | 'u64' | 'i128' | 'u128' | 'int' | 'f32' | 'f64' | 'f128' | 'char' | 'string' | 'void' | 'Instant' | 'Duration'`
 - `NamedType ::= QualifiedName TypeArgListOpt`
 - `TypeArgListOpt ::= ('(' TypeArgListInnerOpt ')')`
 - `TypeArgListInnerOpt ::= TypeArgListInner`
 - `TypeArgListInner ::= TypeArg (',' TypeArg)* ','?`
 - `TypeArg ::= Type | IntLiteral`

 This means that type annotations such as `string?` or `int??` are parsed
 into nested optional types. For simple nominal optionals, the parser also
 recognizes `Option(T)` and desugars it to the same internal representation
 as `T?`. Borrowed reference types (`&T`) are now parsed in type annotations.
 Array/slice types (`T[]`, `T[N]`) are
 parsed and type-checked in the Supported forms (with element-type
 restrictions), and are part of the implemented expression grammar via array
 literals (`[a, b, c]`) and indexing (`xs[i]`). Function types
 (`fn (T, ...) -> R`) are parsed as part of the `Type` grammar, and function
 values are supported in the current lowering subset (including capturing
 closures as a restricted scalar-only subset; see `Types` and
 [`Memory Model (Stack, Heap, and Moves)`](/silk/docs/language/memory-model/)).

- Statements (Supported forms):

 - `Stmt ::= AttrAnnot* (LetStmt | LetElseStmt | SpecConstStmt | SpecAssertStmt | SpecTheoryDeclStmt | SpecTheoryStmt | AsyncBlockStmt | TaskBlockStmt | ExprStmt | IfStmt | LoopStmt | WhileStmt | ForStmt | MatchStmt | ReturnStmt | PanicStmt | AssertStmt | BreakStmt | ContinueStmt)`

 - `LetStmt ::= LetIntroducer LetBinder TypeAnnotationOpt InitializerOpt ';'`
 - `LetElseStmt ::= ('let' LetModifierOpt | 'var' LetModifierOpt) MatchExprPattern TypeAnnotationOpt '=' Expr 'else' Block ';'`
 - `LetBinder ::= Identifier | '_' | LetTupleBinder | LetStructBinder | LetArrayBinder | LetEnumBinder`
 - `LetTupleBinder ::= '(' LetTupleBinderItemsOpt ')'`
 - `LetTupleBinderItemsOpt ::= LetTupleBinderItem (',' LetTupleBinderItem)* ','?`
 - `LetTupleBinderItem ::= Identifier | '_'`
 - `LetStructBinder ::= '{' LetStructBinderItemsOpt '}'`
 - `LetStructBinderItemsOpt ::= LetStructBinderItem (',' LetStructBinderItem)* ','?`
 - `LetStructBinderItem ::= Identifier ('as' (Identifier | '_'))?`
 - `LetArrayBinder ::= '[' LetArrayBinderItemsOpt ']'`
 - `LetArrayBinderItemsOpt ::= LetArrayBinderItem (',' LetArrayBinderItem)* ','?`
 - `LetArrayBinderItem ::= Identifier | '_'`
 - `LetEnumBinder ::= QualifiedName '(' LetEnumBinderItemsOpt ')'`
 - `LetEnumBinderItemsOpt ::= LetEnumBinderItem (',' LetEnumBinderItem)* ','?`
 - `LetEnumBinderItem ::= Identifier | '_'`
 - `SpecConstStmt ::= '#const' Identifier '=' Expr ';'`
 - `SpecAssertStmt ::= '#assert' Expr ';'`
 - `SpecTheoryDeclStmt ::= '#theory' Identifier '(' TheoryParamsOpt ')' '{' TheoryBodyItem* '}'`
 - `SpecTheoryStmt ::= '#theory' QualifiedName '(' ArgListOpt ')' ';'`
 - `AsyncBlockStmt ::= 'async' Block`
 - `TaskBlockStmt ::= 'task' Block`
 - `MutOpt ::= 'mut'?`
 - `ExprStmt ::= Expr ';'`
 - `IfStmt ::= 'if' IfCondition Block ('else' (IfStmt | Block))?`
 - `IfCondition ::= Expr | IfLetCondition`
 - `IfLetCondition ::= 'let' LetModifierOpt MatchExprPattern '=' Expr LetChainOpt`
 - `LetChainOpt ::= ('&&' LetChainClause)*`
 - `LetChainClause ::= 'let' LetModifierOpt MatchExprPattern '=' Expr | Expr`
 - `LoopStmt ::= LoopPrefixOpt 'loop' Block`
 - `LoopPrefixOpt ::= 'async' | 'task'`
 - `WhileStmt ::= WhileSpecs 'while' WhileCondition Block`
 - `WhileCondition ::= Expr | WhileLetCondition`
 - `WhileLetCondition ::= 'let' LetModifierOpt MatchExprPattern '=' Expr LetChainOpt`
 - `WhileSpecs ::= (LoopInvariant | LoopVariant | LoopMonovariant)*`
 - `LoopInvariant ::= '#invariant' Expr ';'`
 - `LoopVariant ::= '#variant' Expr ';'`
 - `LoopMonovariant ::= '#monovariant' Expr ';'`
 - `ForStmt ::= ForInStmt | ForCStmt`
 - `ForInStmt ::= 'for' ForHead 'in' ExprNoRange (RangeOp ExprNoRange)? Block`
 - `ForCStmt ::= 'for' '(' ForInit ';' Expr ';' Expr ')' Block`
 - `ForInit ::= LetIntroducer Identifier TypeAnnotationOpt '=' Expr`
 - `ForHead ::= ForBinder | 'let' MutOpt MatchExprPattern`
 - `ForBinder ::= Identifier | '_'`
 - `RangeOp ::= '..' | '..='`
 - `BlockStmt ::= Block`
 - `Block ::= '{' Stmt* '}'`
 - `ReturnStmt ::= 'return' ExprOpt ';'`
 - `ExprOpt ::= Expr?`
 - `PanicStmt ::= 'panic' QualifiedName StructLiteralSuffixOpt ';'`
 - `AssertStmt ::= 'assert' Expr ';' | 'assert' '(' Expr (',' Expr)? ')' ';'`
 - `BreakStmt ::= 'break' ';'`
 - `ContinueStmt ::= 'continue' ';'`

 `LetModifierOpt` accepts at most one `mut` and at most one `move`, in either
 order. `mut` makes the introduced binders assignable. `move` consumes the
 initializer/scrutinee for ownership-tracked values. `var` bindings are always
 mutable; an explicit `mut` after `var` is accepted for symmetry, so
 `var move name = value;`, `var mut move name = value;`, and
 `var move mut name = value;` are the mutable-binding forms of
 initialization-time ownership transfer. In pattern forms the consuming
 modifier is written as `let move Some(value) = maybe`, `if let move ...`,
 `else if let move ...`, chained `&& let move ...`, or `while let move ...`.
 - `WithStmt ::= 'with' Identifier Block
 | 'with' WithBytes Block
 | 'with' WithBytes 'from' Identifier WithFromSliceOpt Block`
 - `WithBytes ::= IntLiteral | '(' IntLiteral ')'`
 - `WithFromSliceOpt ::= '[' IntLiteral '..' IntLiteralOpt ']'`
 - `IntLiteralOpt ::= IntLiteral`
 - `MatchStmt ::= 'match' Expr '{' MatchStmtArmListOpt '}'`
 - `MatchStmtArmListOpt ::= MatchStmtArmList`
 - `MatchStmtArmList ::= MatchStmtArm (',' MatchStmtArm)* ','?`
 - `MatchStmtArm ::= MatchStmtPattern '=>' Block`
 - `OptionalPattern ::= 'None'
 | 'Some' '(' (Identifier | '_') ')'`

 - `MatchStmtPattern ::= OptionalPattern
 | '_'
 | Identifier
 | (Identifier | '_') ':' QualifiedName`
 - `StructLiteralSuffixOpt ::= StructLiteralSuffix`

 Region declarations and `with` blocks are specified in [`Regions`](/silk/docs/language/regions/).

 [`match`](/silk/wiki/language/flow-match/) is implemented in two separate forms:

 - [`match`](/silk/wiki/language/flow-match/) as an expression (arms are expressions; see `MatchExpr` below),
 - [`match`](/silk/wiki/language/flow-match/) as a statement (arms are blocks), used for typed errors as
 specified in `Typed Errors (`error`, `panic`, and `T | ErrorType...`)`.

 In the Supported forms, the [`match`](/silk/wiki/language/flow-match/) statement form is restricted to a
 call-expression scrutinee and the patterns listed above.

- Expressions (Supported forms):

 Expressions follow a conventional precedence hierarchy, as implemented in
 `the implementation`:

 - `Expr ::= Assign`
 - `ExprNoRange ::= AssignNoRange`
 - `Assign ::= Range (AssignOp Assign)?`
 - `AssignNoRange ::= Coalesce (AssignOp AssignNoRange)?`
 - `Range ::= Coalesce (RangeOp CoalesceOpt)? | RangeOp CoalesceOpt`
 - `CoalesceOpt ::= Coalesce`
 - `AssignOp ::= '=' | '+=' | '-=' | '*=' | '/='`
 - `Coalesce ::= LogicalOr ('??' CoalesceRhs)?`
 - `CoalesceRhs ::= Coalesce | CoalesceTerminal`
 - `CoalesceTerminal ::= 'return' ExprOpt | 'break' | 'continue'`
 - `LogicalOr ::= LogicalAnd ('||' LogicalAnd)*`
 - `LogicalAnd ::= BitOr ('&&' BitOr)*`
 - `BitOr ::= BitXor ('|' BitXor)*`
 - `BitXor ::= BitAnd ('^' BitAnd)*`
 - `BitAnd ::= Equality ('&' Equality)*`
 - `Equality ::= TypeTest (('==' | '!=') TypeTest)*`
 - `TypeTest ::= Relational ('is' Type)?`
 - `Relational ::= Shift (('<' | '<=' | '>' | '>=') Shift)*`
 - `Shift ::= AddSub (('<<' | '>>') AddSub)*`
 - `AddSub ::= MulDiv (('+' | '-') MulDiv)*`
 - `MulDiv ::= Unary (('*' | '/' | '%') Unary)*`
 - `Unary ::= ('!' | '~' | '-' | 'mut' | 'move' | 'new' | 'await' | 'yield' | 'sizeof' | 'alignof' | 'offsetof' | 'typename' | '&' | '*' | '++' | '--') Unary | Postfix`
 - `Postfix ::= Primary PostfixSuffix*`
 - `PostfixSuffix ::= CallSuffix | FieldSuffix | OptionalFieldSuffix | StructLiteralSuffix | IndexSuffix | SliceSuffix | CastSuffix | TrySuffix | IncDecSuffix`
 - `CallSuffix ::= '(' CallArgsOpt ')'`
 - `FieldSuffix ::= '.' NameToken`

 `CoalesceTerminal` is deliberately narrow. `return`, `break`, and
 `continue` remain statements in the general language grammar and are only
 admitted here as the immediate right-hand side of `??`.
 - `OptionalFieldSuffix ::= '?.' NameToken`
 - `StructLiteralSuffix ::= '{' StructInitListOpt '}'`
 - `IndexSuffix ::= '[' ExprNoRange ']'`
 - `SliceSuffix ::= '[' SliceBoundOpt '..' SliceBoundOpt ']'`
 - `SliceBoundOpt ::= ExprNoRange`
 - `CastSuffix ::= 'as' RawOpt Type CastSliceLenOpt`
 - `CastSliceLenOpt ::= '(' Expr ')'`
 - `RawOpt ::= 'raw'`
 - `TrySuffix ::= '?'`
 - `IncDecSuffix ::= '++' | '--'`
 - `StructInitListOpt ::= StructInitList`
 - `StructInitList ::= StructInit (',' StructInit)* ','?`
 - `StructInit ::= NameToken (':' Expr)?`

 - `CallArgsOpt ::= CallArgs`
 - `CallArgs ::= GenericArgListOpt ';' ArgListOpt | ArgList`
 - `GenericArgListOpt ::= GenericArgList`
 - `GenericArgList ::= GenericArg (',' GenericArg)* ','?`
 - `GenericArg ::= Type | IntLiteral`

 - `ArgListOpt ::= ArgList`
 - `ArgList ::= Expr (',' Expr)*`

 Note: the parser treats `mut <expr>`, `new <expr>`, `await <expr>`, `await * <expr>`,
 `yield <expr>`, `yield * <expr>`, `sizeof <expr>`, `alignof <expr>`, `offsetof(Type, field_path)`,
 `typename <expr>`, and prefix `++<expr>` / `--<expr>` as unary expressions.

 Note: `CastSliceLenOpt` is permitted only when `Type` is a slice type (`T[]`)
 or `string`, and `RawOpt`. It is used by unsafe pointer view casts
 like `ptr as u8[](/silk/docs/len)` (slice view) and `ptr as string(len)` (string view).

 - The type checker currently permits `mut <expr>` only in call arguments (and
 method receivers) when the corresponding parameter is declared `mut` and is:
 - a borrowed reference type (`mut r: &T`), or
 - a slice type (`mut s: T[]`).
 - The `move <expr>` unary form is used for explicit ownership transfer; in the
 Supported forms it is restricted to `move <name>` where `<name>` is a local
 binding.
 - Binding declarations may also use `let move` / `var move` to request the
 same ownership transfer at initialization. The `mut` modifier may appear
 before or after `move`, including `let mut move value = source;` and the
 redundant-but-accepted `var mut move value = source;`. This is especially
 useful for destructuring: `let move (a, b) = pair;`,
 `let move Some(value) = maybe;`,
 `let move Some(value) = maybe else { ... };`,
 `if let move Some(value) = maybe { ... }`,
 `else if let move Some(value) = maybe { ... }`, and
 `while let move Some(value) = next() { ... }`.
 - The type checker currently permits `new <expr>` only when it can determine a
 concrete reference result type of the form `&Struct`. In the current
 implementation this happens either:
 - from an expected `&Struct` type context (for example `let x: &Frame = new
 Frame{ ... };` or as a call argument whose parameter type is `&Struct`)
 - from the operand itself when it names the struct type (for example `let x
 = new Frame{ ... };` or `let x = new Frame(...);`)
 `new` is supported only in function bodies (not in top-level `let`
 initializers).

 - `Primary ::= IntegerLiteral
 | DurationLiteral
 | FloatLiteral
 | StringLiteral
 | RegexpLiteral
 | CharLiteral
 | 'true'
 | 'false'
 | 'None'
 | 'null'
 | 'Some' '(' Expr ')'
 | ArrayLiteral
 | IfExpr
 | MatchExpr
 | FnExpr
 | AttrQueryExpr
 | AsmExpr
 | EmbedExpr
 | '(' Expr ')'
 | InferredStructLiteral
 | QualifiedName`

 - `AttrQueryExpr ::= 'attr' '(' AttrItemListOpt ')'`

 - `EmbedExpr ::= '#embed' '(' StringLiteral EmbedEncodingOpt ')'`
 - `EmbedEncodingOpt ::= ',' EmbedEncoding`
 - `EmbedEncoding ::= StringLiteral`

 `#embed` paths are resolved relative to the containing Silk source file.
 Absolute paths are accepted directly. If the parser has no source-file path
 because it is parsing an in-memory buffer, relative paths are resolved from
 the current working directory. The compiler rejects empty, unreachable, or
 unreadable paths. `#embed("file")` and `#embed("file", "utf8")` validate
 UTF-8 input and produce a `string`; omitting the encoding is equivalent to
 `"utf8"`. `#embed("file", "utf16")` decodes UTF-16 into a UTF-8 `string`;
 `#embed("file", "u8")`, `#embed("file", "u16")`, and
 `#embed("file", "u32")` produce compiler-owned array values for
 array-typed use sites such as
 `let bytes: u8[] = #embed("./data.bin", "u8");`. Multi-byte integer
 encodings read file bytes as little-endian element values; the compiler
 carries the file bytes as embed metadata instead of expanding them into
 source-level integer literal nodes. Without an expected array type, a raw
 integer embed infers a dynamic slice of the requested element type. The
 encoding literal value must be one of `"utf8"`, `"utf16"`, `"u8"`, `"u16"`,
 or `"u32"`.

 - `RegexpLiteral ::= '/' RegexpBody '/' RegexpFlagsOpt`
 - `RegexpFlagsOpt ::= Identifier`

 Notes:
 - `RegexpBody` is scanned by the parser (not the lexer): it is the byte span
 between the opening and closing `/`, where the closing delimiter is the
 first unescaped `/` that is not inside a character class (`[...]`).

 - `ArrayLiteral ::= '[' ExprListOpt ']'`
 - `ExprListOpt ::= ExprList`
 - `ExprList ::= Expr (',' Expr)* ','?`

 - `QualifiedName ::= GlobalPrefixOpt NameToken ('::' NameToken)*`
 - `GlobalPrefixOpt ::= '::'`

 - `InferredStructLiteral ::= '{' StructInitListOpt '}'`

 - `FnExpr ::= 'fn' '(' LambdaParamListOpt ')' ( '->' LambdaBody | Block )`
 - `LambdaParamListOpt ::= LambdaParamList`
 - `LambdaParamList ::= LambdaParam (',' LambdaParam)* ','?`
 - `LambdaParam ::= Identifier ':' Type`
 - `LambdaBody ::= Type Block | Expr`

 Disambiguation rule (current parser):

 - `fn (...) -> Type Block` is treated as the block-body form only when the
 return type is followed immediately by `{` (starting the block).
 - Otherwise, `fn (...) -> Expr` is treated as an expression-body function
 expression and its result type is inferred by the checker.
 - `fn (...) Block` is treated as the block-body form with an implicit `void`
 result type (shorthand for `fn (...) -> void Block`).

 Notes:

 - `InferredStructLiteral` has the same token-level shape as
 `StructLiteralSuffix` (used for `Type{ ... }`), but appears as a `Primary`
 expression with **no explicit type name**. The type checker requires an
 expected struct type context to resolve the literal’s target type.
 - To avoid ambiguity with statement blocks, the parser only recognizes
 `InferredStructLiteral` when the `{ ... }` contents look like a struct
 initializer list (or are `{}`): either the first token after `{` is `}` or
 it is an `Identifier` followed by `:` (explicit initializer) or followed by
 `,` / `}` (shorthand initializer).
 - Non-adjacent explicit struct literals (`Type { ... }`) are suppressed only
 at the immediate expression-before-block boundary used by block-bearing
 constructs. Delimited subexpressions restore ordinary expression parsing,
 so forms such as `[Type { field: value }]` and `call(Type { field: value })`
 remain `StructLiteralSuffix` expressions even when the surrounding
 expression is followed by a block.

 - `MatchExpr ::= 'match' Expr '{' MatchArmListOpt '}'`
 - `MatchArmListOpt ::= MatchArmList`
 - `MatchArmList ::= MatchArm (',' MatchArm)* ','?`
 - `MatchArm ::= MatchExprPattern '=>' Expr`
 - `MatchExprPattern ::= OptionalPattern | EnumVariantPattern | ResultPattern | TypedBinderPattern`

 - `IfExpr ::= 'if' Expr IfExprBlock 'else' (IfExpr | IfExprBlock)`
 - `IfExprBlock ::= '{' Expr '}'`

 - `AsmExpr ::= 'asm' StringLiteral`

 - `ResultPattern ::= ('Ok' | 'Err') '(' (Identifier | '_') ')'`

 - `EnumVariantPattern ::= QualifiedName EnumVariantBinderListOpt`
 - `EnumVariantBinderListOpt ::= ('(' EnumVariantBinderListInnerOpt ')')`
 - `EnumVariantBinderListInnerOpt ::= EnumVariantBinderList`
 - `EnumVariantBinderList ::= EnumVariantBinder (',' EnumVariantBinder)* ','?`
 - `EnumVariantBinder ::= Identifier | '_'`

 - `TypedBinderPattern ::= (Identifier | '_') ':' TypeNoPipe`

- Declarations (Supported forms additions):

 - `Decl ::= ... | ErrorDecl`
 - `ErrorDecl ::= ExportModifier 'error' Identifier '{' StructFieldListOpt '}'`

 This matches the current AST and checker:

 - `Primary` constructs `Literal` or `Name` expressions (or a parenthesized `Expr`),
 - unary expressions are represented as `UnaryExpr` with a token kind indicating the
 operator,
 - binary expressions are represented as `BinaryExpr` with a token kind indicating
 the operator,
 - identifiers and qualified names are stored as `NameExpr` with the full
 slice of source text (e.g. `util::answer`),
 - simple function calls such as `helper()` or `util::helper(1, 2)` are
 parsed as call expressions using the `Postfix`/`CallSuffix` productions;
 the compiler supports calls to named functions, but the type checker
 and back-end currently restrict which value types can appear at call boundaries;
 see ``silk` CLI` for the exact supported subset.

Further expression forms (ranges, etc.) are described in other
language concept documents and in `Operators`. The current
parser now accepts `?.` optional field access (`opt?.field`) and the initial
[`match`](/silk/wiki/language/flow-match/) expression form as part of the implemented optional subset, but other
expression forms will be added here as they are implemented.

### Role of This File

This document serves as the reference for:

- lexer implementation (token categories and reserved words),
- parser implementation (production rules and precedence),
- pretty-printer or formatter behavior.

As the parser and lexer are implemented, this file must be updated with:

- the exact grammar that the compiler accepts (including any temporary limitations),
- clarifications or corrections discovered during implementation (recorded here so this file remains canonical),
- notes about desugaring and how surface constructs map into the internal AST,
- clear indication of which productions are implemented today vs. planned
 future work, so that downstream users can see both the full language
 design and the currently supported subset.

## Types

This document specifies the Silk type system used by the compiler front-end and type checker.



- Supported end-to-end: primitives, nominal `struct` types, optionals (`T?`),
 `&Struct` references (in function parameter types and as local values
 produced by `new` / calls that return `&Struct`), and array/slice types
 (`T[N]`, `T[]`) over element types that lower to a fixed scalar-slot sequence
 in the backend (including array literals, indexing reads, and
 iterable `for` loops). Indexed assignment targets (`xs[i] = v`) are supported
 for these element types; compound index ops require numeric scalar element
 types in the Supported forms.
 - Parameterized nominal types (monomorphized): generic `struct` and
 `interface` declarations with **type parameters**, plus applied types in
 type positions (`Name(u8)`, `Name(string)`) for those declarations.
- Reserved intrinsics: the compiler currently exposes reserved, stdlib
 bring-up intrinsics for working with the `string` ABI (`__silk_string_ptr`,
 `__silk_string_len`, and `__silk_string_from_ptr_len`). User code should
 generally prefer the language sugar:
 - `s as raw u64` (extract the underlying byte pointer), and
 - `sizeof(s)` (extract the string byte length as `usize`), and
 - `p as raw u64` (extract the underlying address for `p: &T`), and
 - `ptr as string(len)` (construct a `string` view from a raw pointer plus an
 explicit byte length)
 over calling these helpers directly. The intrinsic names remain reserved and
 are not a stable user API.
- Special-case: the nominal optional form `Option(T)` is accepted and desugared
 to `T?` in type annotations (it is not a general generics feature).
- Parsed but rejected by the current checker: const parameters and integer
 literal type arguments (`Foo(N: int)`, `Foo(u8, 1024)`) ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2016`).
- Removed builtin map type form: `map(K, V)` (`E2017`; use
 `std::map::{HashMap, TreeMap}` instead).
- Defined in the native backend subset: 128-bit scalar primitives
 (`i128`, `u128`, `f128`).
 - In the current scalar-slot model ([`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)),
 these primitives lower to **two 8-byte slots** (`lo: u64`, `hi: u64`).
 - `f128` uses the IEEE‑754 binary128 bit pattern stored across those slots.
 - In the current backend implementation, `f128` arithmetic and some `as`
 casts lower to bundled runtime helper calls:
 - on `linux/x86_64`, the helpers use `__float128` and rely on libgcc
 symbols (for example `__addtf3`),
 - on targets where `long double` is an IEEE‑754 binary128 value (for
 example `linux/aarch64`), the helpers use `long double`,
 - on other targets, the helper calls are currently stubbed and will trap
 if executed.
 - Diagnostics rule: `u128` / `f128` are language features, so any compiler
 rejection is an implementation gap in a specific compiler path, not a
 language-contract rejection.
- Typed errors (`error`, `panic`, and `T | ErrorType...`) are specified in
 `Typed Errors (`error`, `panic`, and `T | ErrorType...`)`. The current compiler models typed error
 contracts as an effect on function return types and expressions.
 - Separately, type unions (`T1 | T2 | ...`) are supported in type annotations
 as described in `Type Unions (`T1 | T2 | ...`)`. In function declaration
 return types, union returns must be parenthesized (`-> (A | B)`) because
 unparenthesized `|` after `->` is reserved for typed-error contracts.

### Quick Reference

The core categories are:

- Booleans: `bool`
 - Examples: `true`, `false`.
 - Notes: logical values.
- Integers (fixed width): `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `u128`, `i128`
 - Examples: `let n: i32 = 42;`.
 - Notes: signed/unsigned bit-widths.
- Integer (platform): `int`
 - Examples: `let n: int = 1;`.
 - Notes: implementation/default integer.
- Pointer-width integers: `usize`, `size`
 - Examples: `let n: usize = 1;`, `let n: size = -1;`.
 - Notes: unsigned/signed integer types whose width matches the target
 architecture pointer width (for example 64-bit on `linux/x86_64`).
 - Compatibility: `isize` is accepted as an alias for `size`.
- Floats: `f32`, `f64`, `f128`
 - Examples: `let x: f64 = 3.14;`.
 - Notes: IEEE‑754.
- Char: `char`
 - Examples: `'A'`.
 - Notes: Unicode scalar.
- String: `string`
 - Examples: `"hello"`.
 - Notes: immutable text; multi‑line strings supported.
- Regexp: `regexp`
 - Examples: `/hello/i`.
 - Notes: compiled regular expression bytecode; a non-owning `{ ptr, len }`
 view analogous to `string`. Regex literals compile at compile time; runtime
 compilation and matching helpers live in [`std::regex`](/silk/docs/std/regex/) (see [`std::regex`](/silk/docs/std/regex/)).
- Region handle: `Region`
 - Examples: `fn f (r: Region) -> int { with r { ... } }`.
 - Notes: a first-class region allocation context handle; see [`Regions`](/silk/docs/language/regions/).
- Void / Unit: `void`
 - Examples: `fn foo () -> void {}`.
 - Notes: functions that return nothing.
- Time Types: `Instant`, `Duration`
 - Examples: `let i: Instant = std::now();`.
 - Notes: specialized `i64`-based types for time measurement.
- Optional: `T?`
 - Examples: `User?`, `i32?`.
 - Notes: `None` / `none` is the canonical empty value; `null` may also be
 used when an optional type is expected and coerces to `None`. Use [`match`](/silk/wiki/language/flow-match/),
 `?.`, `??`.
- None (value):
 - Examples: `None` / `none` (represented as `None` in code samples).
 - Notes: the distinguished empty value; typed as `T?`. The `null` literal is
 a distinct literal that can coerce to `None` when an optional type is
 expected (see [`Optional`](/silk/docs/language/optional/)).
- Reference (borrow): `&T`
 - Examples: `&User`.
 - Notes: reference type; in the Supported forms, `&Struct` may appear in
 parameter types and as local values when produced by `new` or by calls that
 return `&Struct`. Mutability follows the `mut` borrow contract and per-call
 aliasing rules described in [`Mutability`](/silk/docs/language/mutability/).
- Arrays / Slices: `T[]`, `T[N]`
 - Examples: `i32[]`, `byte[32]`, `u8[1024 * 1024]`.
 - Notes: dynamic slice vs fixed length (compile‑time `N`). In the current
 parser subset, `N` may be:
 - an integer literal,
 - a literal-only integer arithmetic expression using `+`, `-`, `*`, `/`,
 `%`, and parentheses,
 - or a single const parameter name in generic type positions.
 In the compiler, arrays/slices are supported only
 when the element type lowers to a fixed scalar slot sequence in the current
 scalar-slot memory model (for example primitive scalars, `string`, and
 supported `regexp`, supported non-opaque structs, and enums). See
 [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/) for the current scalar-slot memory
 model. In the Supported forms, fixed array lengths are limited to
 `N <= 4096`. Indexing `xs[i]` traps when `i` is out of bounds in the
 Supported forms.
- Range: `range`
 - Examples: `let r: range = 0..4;`, `let r2: range = (1..) + 2;`.
 - Notes: an `int`-indexed range value used for slicing and other index-based
 operations. Range literal bounds are `int` values; in range literals, bound
 expressions must have type `int` (integer literals may be coerced to
 `int`). Range literal expressions support:
 - `start..end` (end-exclusive) and `start..=end` (end-inclusive),
 - `start..` (open-ended),
 - `..end` (implicit start `0`),
 - `..` (full range).
 The inclusive (`..=`) form requires an explicit end bound.
 In index/slice contexts, open-ended ranges use the container’s length as
 the effective end bound.
 A `range` value can be used for slicing via `&xs[r]` / `mut &xs[r]`, enabling
 composed ranges (including inclusive ranges) to drive slicing.
- Function Types: `fn(params) -> R`
 - Examples: `fn(i32) -> i32`.
 - Notes: function types are part of the type grammar and function-typed
 values are supported as function values (including capturing closures) in
 Silk currently.
 Concurrency disciplines (`task` / `async`) are implemented on function
 *declarations* (see [`Concurrency`](/silk/docs/language/concurrency/)); function types in type
 positions do not currently include discipline modifiers.
- Capturing Closures:
 - Notes: capturing closures are supported as function values with an
 environment; see “Function Types and Closures” below for Supported forms
 restrictions.
- Structs (nominal):
 - Surface: `struct Name { ... }` then `Name(...)`.
 - Examples: `Point`, `Option(T)`.
 - Notes: user‑defined records; may be parameterized.
- Enums (sum types):
 - Surface: `enum Name { ... }`.
 - Notes: nominal tagged unions as described in the spec.
- Type unions:
 - Surface: `T1 | T2 | ...` (type annotations).
 - Notes: a tagged “one-of-these-types” type for a small, explicitly defined
 subset; see `Type Unions (`T1 | T2 | ...`)`.

The compiler must represent these types faithfully in its internal type system and in the C99 ABI mappings, and it must follow the exact surface syntaxes indicated above when parsing and printing types.

### Type Aliases (`type`)

Silk supports compile-time-only type aliases via `type` declarations.

Syntax examples:

```silk
type Int32 = i32;
type struct Bar = Foo;
type fn IntAdder = fn(int, int) -> int;
type pure fn PureIntAdder = fn(int, int) -> int;
type ResultOf(T) = std::result::Result(T, string);
export type struct PublicBar = Foo;
```

Semantics :

- A type alias introduces a new name for an existing type; it does **not**
 introduce a distinct nominal type.
- The type checker MUST treat uses of the alias name as equivalent to the alias
 target type (the alias is transparent).
- A type alias may declare generic parameters using the same parameter-list
 syntax as generic structs, enums, interfaces, impls, and functions. Applying
 the alias in a type position substitutes the supplied type arguments into the
 transparent target, for example `ResultOf(int)` is equivalent to
 [`std::result::Result(int, string)`](/silk/docs/std/result/).
- Type aliases may be used anywhere a type is expected (parameter/result types,
 local annotations, struct fields, `as` casts, etc.).
- Cycles in type aliases are rejected (`E2058`).

Kind tags:

- A `type` declaration may optionally specify a kind tag, for example
 `type struct Name = Foo;` or `type pure fn Name = fn(...) -> ...;`.
- When present, the compiler MUST validate that the resolved alias target
 matches the declared kind (`E2059`).

Import/export:

- `type` aliases may be exported (`export type ...;`) and imported as type names
 via named file imports (see [`Packages, Imports, and Exports`](/silk/docs/language/packages-imports-exports/)).

### Implicit Call-Argument Coercions

In Silk currently, Silk supports a small, **opt-in** implicit
coercion mechanism for function call arguments. This exists to keep the
current standard library ergonomic while generics and richer overload
systems are still evolving.

There are three related mechanisms:

1. **Coercion to a nominal `struct` value** `T` (by-value parameters and
 varargs elements) via exported static ctor-like methods.
2. **Coercion to a borrowed reference** `&T` (read-only `&T` parameters) via a
 `constructor` method that initializes a compiler-generated stack temporary.
3. **Contextual string compatibility** for values implementing
 [`std::interfaces::Serialize(string)`](/silk/docs/std/interfaces/).

#### 1) Coercion to `T` via exported static ctor-like methods

Rule (informal):

- When a function call argument type does not match a parameter type, and the
 parameter is a nominal `struct` type `T`, the compiler may rewrite the
 argument to a call of an exported, static ctor-like method on `T`.
- This also applies to varargs elements (`...args: T`).

Supported ctor-like method names (destination type opts in by defining these):

- `T.int(value: int) -> T`
- `T.i128(value: i128) -> T`
- `T.u64(value: u64) -> T`
- `T.u128(value: u128) -> T`
- `T.f64(value: f64) -> T`
- `T.f128(value: f128) -> T`
- `T.bool(value: bool) -> T`
- `T.char(value: char) -> T`
- `T.string(value: string) -> T`
- `T.regexp(value: regexp) -> T`
- `T.Region(value: Region) -> T`

Selection (source type → constructor):

- Signed integer primitives (`i8/i16/i32/i64/int/size/isize/Instant/Duration`) → `int`
- Signed wide integer primitive (`i128`) → `i128`
- Unsigned integer primitives (`u8/u16/u32/u64/usize`) → `u64`
- Unsigned wide integer primitive (`u128`) → `u128`
- Float primitives (`f32/f64`) → `f64`
- Wide float primitive (`f128`) → `f128`
- `bool` → `bool`
- `char` → `char`
- `string` → `string`
- `regexp` → `regexp`
- `Region` → `Region`

Integer width:

- When the source argument is a fixed-width integer, the compiler inserts an
 implicit integer cast to match the ctor parameter type before calling the
 ctor.
- When the source argument is `f32` and the selected ctor parameter type is
 `f64`, the compiler inserts an implicit float cast (`f32 -> f64`) before
 calling the ctor.

Example:

```silk
struct Counter {
  value: int,
}

impl Counter {
  public fn int (value: int) -> Counter {
    return Counter{ value: value };
  }
}

fn takes (c: Counter) -> int {
  return c.value;
}

fn main () -> int {
  let x: i32 = 7;
  return takes(x); // coerces via `Counter.int`
}
```

Notes:

- Coercions are only attempted when the destination type provides the matching
 exported static ctor method.
- Today this primarily exists to support [`std::fmt::Arg`](/silk/docs/std/fmt/) and ergonomic
 [`std::io::print/println`](/silk/docs/std/io/) calls without requiring explicit `Arg.*`
 wrappers everywhere.

#### 2) Coercion to `&T` via `constructor` (stack temporary)

Rule (informal):

- When a call argument does not match a parameter type, and the parameter is a
 **read-only borrowed reference** `&T` to a nominal `struct` type `T`, the
 compiler may create an implicit stack temporary `tmp: T`, initialize it by
 invoking `tmp.constructor(...)`, and pass `&tmp` to the callee.

This is intentionally a *stack* construction mechanism:

- it does **not** allocate on the heap,
- it is compatible with `silk build --noheap`,
- and the temporary’s lifetime is the duration of the call (similar to how C++
 binds temporaries to `const&` parameters).

Eligibility requirements (Supported forms):

- The parameter must be `&T` (not `mut &T`).
- The destination type `T` must provide a visible `constructor` overload with:
 - receiver `mut self: &T`,
 - exactly **one** non-receiver parameter (`value: U`),
 - return type `void`.
- The call argument type must match the selected overload’s `U` parameter type.
- If multiple overloads are viable for a given argument, the coercion is
 ambiguous and rejected (the call must be written with an explicit
 construction).

Example:

```silk
struct User {
  name: string,
}

impl User {
  fn constructor (mut self: &Self, name: string) -> void {
    self.name = name;
  }
}

fn print_user (user: &User) -> void {
  std::io::println("user.name = {}", user.name);
}

fn main () -> int {
  // Implicitly constructs a temporary `User` from a `string` for this call.
  print_user("alice");
  return 0;
}
```

Notes:

- This is an opt-in mechanism: types must provide the matching `constructor`.
- If the coercion is ambiguous (multiple viable conversion paths), the compiler
 rejects the call and requires an explicit construction.
- Because this coercion participates in ordinary call argument checking, it can
 make additional overloads applicable (for example a copy-constructor
 `constructor(mut self: &Self, other: &Self)` can accept a `string` argument by
 first constructing a temporary `Self` from `string`). Overload resolution
 prefers overloads that match without requiring such coercions.

#### 3) Contextual [`std::strings::String`](/silk/docs/std/strings/) compatibility for plain `string`

Rule (informal):

- When an expression is checked in a context that explicitly expects `string`,
 and the expression’s type is [`std::strings::String`](/silk/docs/std/strings/), the compiler may accept
 that expression as satisfying the expected `string`.

Supported contexts include:

- `let x: string = expr`
- `x = expr` when `x: string`
- ordinary function arguments where the parameter type is `string`

Notes:

- This is contextual; it does not change the expression’s nominal type outside
 a `string`-expecting site.
- This rule is currently specialized to [`std::strings::String`](/silk/docs/std/strings/).
- `String.serialize()` / `String.as_string()` still describe the borrowed view
 that is being exposed, so explicit `.as_string()` is no longer required
 solely to satisfy these ordinary borrowed `string` contexts.
- Returning a borrowed `string` view derived from a local owned `String`
 remains a lifetime-sensitive case and is rejected by the compiler; use
 `.as_string()` explicitly when you need to spell that borrow locally, or
 return an owned [`std::strings::String`](/silk/docs/std/strings/) instead.

### Explicit Casts (`as`)

Silk supports explicit casts using the postfix `as` operator:

```silk
let x: f64 = 3.14;
let n: int = x as int;
```

This operator is intended for explicit, potentially lossy primitive numeric
conversions. In the Supported forms it also supports explicit conversions via
[`std::interfaces::Serialize(T)`](/silk/docs/std/interfaces/) by lowering `expr as T` to `expr.serialize()`
when the operand type provides a matching `serialize` method.
For structured conversions, it also supports [`std::interfaces::Deserialize(S)`](/silk/docs/std/interfaces/)
by lowering `expr as T` to `T.deserialize(expr)` when the target type provides
a matching static `deserialize` method.

The supported conversions and semantics for Silk currently are
specified in `Operators` (“Casts (`as`)”).

Notes:

- `as` is explicit. It does not introduce new implicit coercions.
- For call-argument ergonomics, see the separate opt-in coercion mechanism
 described above (“Implicit Call-Argument Coercions”).

### Nominal & Parameterized Types

Nominal types are introduced by declarations (e.g. `struct`, `enum`, `interface`) and are equal only to themselves. Parameterized types are constructed by applying a type constructor to type arguments.

The compiler must:

- Treat nominal types as distinct even if their field layout is identical.
- In the full language design, support parameterized types in all contexts
 where the spec permits them. In Silk currently, **type-parameter**
 generics are supported for nominal declarations (`struct` / `interface`) and
 for applied types in type positions (`Name(u8)`).
 - Const parameters and integer-literal type arguments (`Name(N: int)`,
 `Name(u8, 1024)`) remain tracked work and are rejected (`E2016`).
 - The `Option(T)` optional sugar described above remains supported for
 the Supported forms.

#### Parameterized type syntax

The initial surface syntax for applying type arguments is:

- `TypeApply ::= TypeName '(' TypeArgListOpt ')'`
- `TypeName ::= Identifier ('::' Identifier)*`
- `TypeArgListOpt ::= TypeArgList`
- `TypeArgList ::= TypeArg (',' TypeArg)* ','?`
- `TypeArg ::= Type | IntLiteral`

Examples:

```silk
Foo(int, 1024)
Mutex(Account)
Result(int, string)
```

Notes:

- A `TypeArg` may be a type (e.g. `int`, `&Foo`, `Option(string)`) or a
 compile-time integer literal for const-parameter-style arguments.
- The full semantics (declaring generic parameters, constraint checking, and
 monomorphization) are still evolving; the key requirement is that the
 compiler preserves the argument structure in the AST/type system so later
 stages can enforce and lower it.

### Reference Types

Reference types describe access to values rather than owning them (e.g. references, borrowed views, or other non-owning handles as specified in this document and related language docs).

Key requirements:

- Distinguish owning vs. non-owning types in the type system.
- Preserve aliasing and lifetime constraints so that regions, buffers, and FFI safety rules can be enforced.

Current implementation notes:

- `&Struct` is supported in function parameter types and as local values when
 produced by heap allocation (`new`) or by calls that return `&Struct`.
- `&T` where `T` is a **single-slot scalar primitive** (for example `&bool`,
 `&int`, `&u64`, `&f64`) is supported in function parameter types and as local
 values when produced by the borrow operator `&expr`.
- Borrowed `&Struct` references may also be created from stack values:
 - via the borrow operator `&expr` on borrowable lvalues, and
 - via implicit borrow coercions in contexts that expect `&T`
 (for example `let r: &Pair = pair;`).
 These borrows are checked with conservative lexical lifetime rules (they may
 not escape the scope of the borrowed stack storage).
- Mutable reference parameters use the two-part `mut` contract and conservative
 per-call aliasing rules; see [`Mutability`](/silk/docs/language/mutability/).

### Function Types and Closures

Silk currently:

- Parses function types in type positions (most notably for `ext` declarations).
- Implements function expressions (lambdas) in expression positions:
 - expression body form: `fn (x: int, y: int) -> x + y`
 - block body form: `fn (x: int, y: int) -> int { return x + y; }`
 - block body `void` shorthand: `fn (x: int, y: int) { ... }` (implicit `void`)
- Function expressions may declare `&T` parameters only when `T` is a
 single-slot scalar primitive (for example `&int` / `&bool`).
- Function expression bodies are checked under the `pure` rules in the current
 subset. Non-capturing function expressions are inferred as `pure` function
 types and are permitted in `pure` code:
 - they may call only `pure` functions,
 - they may not mutate (`let mut`/`var`, assignment),
 - they may not allocate (`new`),
 - they may not use typed error contracts or `panic`.
- The checker also supports purity inference (“auto-pure”) for ordinary function
 declarations and `impl` methods. When inferred, these functions/methods are
 treated as `pure` for call checking and are callable from `pure` code.
- Capturing closures are implemented as a subset of function values:
 - a function expression body may reference **immutable** locals/parameters
 from an enclosing scope; those values are captured by value into a heap
 environment,
 - in the Supported forms, only **scalar** captures are supported (`int`, fixed
 width ints, `bool`, `char`, `f32`, `f64`, `Instant`, `Duration`),
 - forming captures inside `pure` code is rejected (capture environments
 allocate), but closure *values* are still checked under the `pure` rules and
 remain callable from `pure` code once constructed.
- Function values are supported end-to-end for this subset (non-capturing and
 capturing):
 - they may be passed as arguments, returned from functions, stored in
 structs/arrays, and called indirectly.
 - the runtime representation is a pair `{ func_ptr, env_ptr }` as specified
 in [`Memory Model (Stack, Heap, and Moves)`](/silk/docs/language/memory-model/).
- Discipline modifiers for function declarations (`pure` / `task` / `async`) are
 implemented. Function types in type positions do not currently include
 discipline modifiers.

#### C Function Pointers (`c_fn`)

Silk distinguishes between:

- `fn (...) -> R` *function values* (which may carry a closure environment), and
- `c_fn (...) -> R` *C callback pointers* (code pointers only; no environment).

`c_fn` is intended for FFI: it is a safe, storable representation for passing
callbacks to foreign code.

Rules (Supported forms):

- A `c_fn` value may be formed only from:
 - a top-level function name, or
 - a non-capturing `fn (...) -> ...` expression.
- Capturing closures are rejected when a `c_fn` is required.
- `c_fn` values are ABI-lowered as a single `u64` code pointer.

## Literals Overview

This document provides a high-level overview of literals in Silk, with details split into dedicated documents for each category.

For first-time readers, a good path is:

1. `Types` (primitive types like `int`, `f64`, `bool`, `string`),
2. this overview (what literal categories exist),
3. the specific literal docs below (syntax, semantics, and current implementation notes).

Returning readers typically want the notes near the top
of each literal concept doc, plus the “Tests” links for runnable
examples.

### Literal Categories

Silk includes the following literal categories:

- Numeric literals
- Duration literals
- Boolean literals
- Character literals
- String literals
- Regular expression literals
- Aggregate literals (arrays, structs)

Each literal form has well-defined syntax and type inference rules that the compiler must implement.

See:

- [`Numeric Literals`](/silk/docs/language/literals-numeric/)
- [`Duration Literals`](/silk/docs/language/literals-duration/)
- [`Boolean Literals`](/silk/docs/language/literals-boolean/)
- [`Character Literals`](/silk/docs/language/literals-character/)
- [`String Literals`](/silk/docs/language/literals-string/)
- [`Regular Expression Literals`](/silk/docs/language/literals-regexp/)
- [`Aggregate Literals`](/silk/docs/language/literals-aggregate/)

## Numeric Literals

Numeric literals produce integer (`int`, `u8`, `i128`, …) and floating-point
(`f32`, `f64`, `f128`) values.

In Silk, the sign is an operator: `-1` is a unary `-` expression applied to the
integer literal token `1`, not a distinct “negative literal” token.

### Notes

What works end-to-end today (lexer → parser → checker → lowering → codegen):

- Decimal integer literals: `0`, `42`, `255`.
- Digit separators (`_`) within numeric literal digits: `1_000_000`,
 `0b0000_1111_0000`, `0xFFFF_FFFF`, `1_000.25`, `1_000ms`.
- Integer base prefixes:
 - binary: `0b1010` / `0B1010`,
 - octal: `0o17` / `0O17`,
 - hex: `0xFF` / `0Xff`,
 - legacy octal: `017` (value 15).
- Decimal float literals with a fractional part: `0.0`, `1.5`, `10.25`.
- Unary `-` over numeric literals: `-1`, `-1.5`.
- Contextual typing:
 - integer literals default to `int`, but adopt an expected integer type
 (`u8`, `i128`, …) or time type (`Duration`, `Instant`) when a context
 provides one,
 - float literals default to `f64`, but adopt `f32`/`f64`/`f128` from context.
- Duration literal tokens of the form `<number><unit>` (no whitespace) such as
 `500ms` and `1.5s` (specified in [`Duration Literals`](/silk/docs/language/literals-duration/)).
- Lowering note (current IR backend subset): unannotated local `let` bindings
 participate only in the integer subset. Prefer explicit type annotations for
 `bool` and float locals when you intend to build an executable/library.

Not implemented yet:

- Exponent notation (`1e6`, `1.0e-3`).
- Numeric type suffixes (`42u8`, `1.5f32`).
- Numeric suffixes for 128-bit types (`1u128`, `1.0f128`) are not implemented;
 use annotations or `as` casts.

### Quick Reference

```silk
fn main () -> int {
  let a = 42;        // int
  let b: u8 = 42;    // u8 (typed by context)

  let x: f64 = 1.5;  // f64
  let y: f32 = 1.5;  // f32 (typed by context)

  let d: Duration = 5ms;
  let t0: Instant = 0;

  return 0;
}
```

### Surface Syntax

Numeric literal tokens are recognized as:

- **Integer literal**:
 - decimal digits (`[0-9]+`),
 - binary prefix: `0b` / `0B` followed by binary digits (`[01]+`),
 - octal prefix: `0o` / `0O` followed by octal digits (`[0-7]+`),
 - hex prefix: `0x` / `0X` followed by hex digits (`[0-9a-fA-F]+`),
 - legacy octal: `0[0-7]+` (for example `017`).
- **Float literal**: digits, `.`, digits (`[0-9]+ '.' [0-9]+`).

Notes:

- Integer and float literals may use `_` as a digit separator. Separators are
 ignored when parsing the numeric value, but must appear **between** digits.
 For example:
 - valid: `1_000`, `0xFFFF_FFFF`, `0b0000_1111_0000`, `1_000.2_5`,
 - invalid: `_1`, `1_`, `1__0`, `0x_FF`.
- A float literal must have digits on both sides of the `.`:
 - `1.0` is a float literal.
 - `1.` is not a float literal in the current lexer.
 - `.5` is not a float literal; write `0.5`.
- Numeric literals must start with a digit in the current lexer.
- The `-` sign is not part of the literal token:
 - `-1` parses as unary `-` applied to the integer literal `1`.
 - `-1.5` parses as unary `-` applied to the float literal `1.5`.
- A numeric token immediately followed by a duration unit suffix (e.g. `1s`,
 `500ms`, `1.5s`) is a single `Duration` literal token, not a number token
 followed by an identifier.
- A numeric literal token may not be immediately followed by an identifier
 start character or an ASCII digit (unless the identifier characters are part
 of a duration unit suffix). For example:
 - `3in` is a lexical error (write `3 in` or `3 * in` as intended),
 - `0b102` is a lexical error (invalid binary digit),
 - `08` is a lexical error in Silk because multi-digit literals starting with
 `0` are legacy octal (use `0o10` for octal 8, or write `8` for decimal).

### Type Rules

See `Types` for the primitive type names used below.

#### Integer literals

- Without an expected type, an integer literal has type `int`.
- When a context provides an expected type that is:
 - an integer type (`u8`, `i64`, `int`, …), or
 - a time type (`Duration`, `Instant`),
 then the integer literal adopts that expected type.

Example: parameter context and “adopt the expected type”

```silk
fn id_u8 (x: u8) -> u8 {
  return x;
}

fn main () -> int {
  // `255` is contextually typed as `u8` because `id_u8` expects `u8`.
  let v: u8 = id_u8(255);
  if v != 255 {
    return 1;
  }
  return 0;
}
```

Example: time types share an `i64`-based representation in the Supported forms
(``Duration` & `Instant``), so integer literals can be used as
`Instant`/`Duration` values via context:

```silk
fn main () -> int {
  let t0: Instant = 0;
  let d: Duration = 1s;
  let t1: Instant = t0 + d;

  let diff: Duration = t1 - t0;
  if diff != d {
    return 1;
  }
  return 0;
}
```

#### Float literals

- Without an expected type, a float literal has type `f64`.
- When a context provides an expected float type (`f32` or `f64`), the literal
 adopts that expected type.

```silk
fn id_f32 (x: f32) -> f32 {
  return x;
}

fn main () -> int {
  // `1.5` is contextually typed as `f32` because `id_f32` expects `f32`.
  let v: f32 = id_f32(1.5);
  if v != 1.5 {
    return 1;
  }
  return 0;
}
```

### Common Pitfalls

- **Trying to use suffixes**: `42u8` / `1.5f32` are not supported. Use type
 annotations (`let x: u8 = 42;`) or casts (`42 as u8`).
- **Using exponent notation**: `1e6` is not supported yet.
- **Writing incomplete floats**: write `1.0` (not `1.`) and `0.5` (not `.5`).
- **Mixing integers and floats implicitly**: use `as` casts (`Operators`)
 to convert explicitly when you need to combine integer and float values.

### Related Documents

- [`Duration Literals`](/silk/docs/language/literals-duration/) (duration literals like `5ms`, `1.5s`)
- ``Duration` & `Instant`` (time types and operators)
- `Operators` (unary `-`, arithmetic, and `as` casts)
- `Types` (primitive numeric type names)

## String Literals

String literals represent `string` values: immutable, length-tracked sequences of
bytes that are typically interpreted as UTF-8 text.

Use strings for:

- filenames and paths,
- user-visible messages,
- structured formats (JSON, CSV, etc),
- and general “text” data.

If you need a single Unicode scalar value, use `char` literals
([`Character Literals`](/silk/docs/language/literals-character/)).

### Notes

What works end-to-end today (lexer → parser → checker → lowering → codegen):

- Double-quote delimited string literals: `"hello"`.
- Backtick-delimited raw string literals: `` `hello` ``.
- Multi-line string literals: newlines may appear inside `"..."` and become part
 of the string value.
- Multi-line raw string literals: newlines may appear inside `` `...` `` and
 become part of the string value.
- Escape sequences:
 - `\\`, `\"`, `\'`
 - `\n`, `\r`, `\t`, `\0`
 - `\xNN` (exactly two hex digits, inserts a single byte)
 - `\u{...}` (1–6 hex digits, inserts UTF-8 bytes for a Unicode scalar)
- Line ending normalization:
 - embedded `\r\n` and `\r` in the literal source are normalized to `\n`,
 - `\r` escapes are normalized to `\n`.
- Equality and ordering comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`) over
 `string` values in the Supported forms.
- Compile-time file embedding with `#embed("path")`, text encodings
 `#embed("path", "utf8")` / `#embed("path", "utf16")`, and integer array
 encodings `#embed("path", "u8")` / `#embed("path", "u16")` /
 `#embed("path", "u32")`.

Not implemented yet (or not specified as stable):

- A stable, fully-specified string ABI story across the C boundary beyond what
 is documented in `C99 ABI and `libsilk.a``.

### Semantics

- The value of a string literal is a sequence of bytes.
- By convention and by intent, `string` values represent UTF-8 text, but some
 escape forms (notably `\xNN`) can construct byte sequences that are not valid
 UTF-8. Avoid this unless you are intentionally working with raw bytes.
- String literals are immutable.
- Unless otherwise specified for a particular FFI surface, string literals do
 not implicitly include a trailing `\0` byte; length is carried explicitly.

### Single-Line Strings

Single-line string literals:

- Use standard quote-delimited syntax.
- Support escape sequences as described below.

### Raw Strings (Backtick)

Raw string literals are delimited by backticks:

- `` `...` ``
- They may include newlines directly.
- They do **not** process escape sequences: `\n` is two bytes (`'\'` and `'n'`).
- They still normalize embedded `\r\n` / `\r` in the source text to `\n`.

Style guidance:

- Prefer raw multiline backtick strings for static multiline text that does not
 need escape processing. This keeps the source text visually aligned with the
 produced bytes and avoids dense `\n` escape runs.
- Use quoted multiline strings when the literal also needs escape processing.
- Use `\n` escapes for single newline bytes, compact generated fragments,
 escape-focused tests, or formats that require a literal backslash followed by
 `n`.

### Escape Sequences

Double-quoted string literals support the same escape spellings as character
literals:

- `\\` (backslash)
- `\"` (double quote)
- `\'` (single quote)
- `\n` (newline, U+000A)
- `\r` (carriage return, U+000D)
- `\t` (tab, U+0009)
- `\0` (NUL byte, U+0000)
- `\xNN` (byte escape, two hex digits)
- `\u{...}` (Unicode scalar value escape, 1–6 hex digits)

When decoding `\u{...}` escapes, the compiler must reject non-scalar Unicode
values (for example surrogate code points).

### Multi-Line Strings

Multi-line strings:

- Allow embedding newlines directly in the literal.
- Must be represented and encoded identically to `string` values produced at runtime.
- Should be written as raw multiline backtick strings when the text is static
 and does not need escape processing.

### Line Ending Normalization

When decoding string literals, the compiler must normalize:

- `\r\n` to `\n`
- `\r` to `\n`

This applies both to embedded newlines in multi-line literals and to escaped
forms such as `\r`.

Note: a sequence of two escapes like `"\r\n"` is still two escapes. In Silk,
`\r` escapes become `\n`, so `"\r\n"` produces two line
feed bytes (`"\n\n"`).

### Compile-Time File Embedding

`#embed(filepath[, encoding])` reads a file during parsing and embeds its
contents into the compiled program.

- `#embed("relative/path.txt")` resolves the path relative to the containing
 `.slk` source file, validates the file as UTF-8, and produces a `string`.
 This is equivalent to `#embed("relative/path.txt", "utf8")`.
- `#embed("path", "utf8")` validates the file as UTF-8 and produces a
 `string` containing those bytes.
- `#embed("path", "utf16")` decodes UTF-16LE/UTF-16BE input, using a BOM when
 present, and produces a UTF-8 `string`.
- `#embed("path", "u8")`, `#embed("path", "u16")`, and
 `#embed("path", "u32")` produce compiler-owned array values for array-typed
 bindings such as `let bytes: u8[] = #embed("./data.bin", "u8");`. Multi-byte
 integer encodings read little-endian element values; the frontend carries
 file bytes as embed metadata instead of expanding the payload into
 source-level integer literal nodes.
- If no expected array type is present, a raw integer embed infers a dynamic
 slice (`u8[]`, `u16[]`, or `u32[]`) backed by compiler-owned read-only data.

The compiler rejects empty paths, unreadable paths, invalid UTF-8 text embeds,
malformed UTF-16 text embeds, and integer encodings whose file byte length is
not divisible by the requested element width. Embedded strings do not receive
an implicit NUL terminator; `sizeof(value)` reports the embedded byte length.
Use the `u8` encoding for raw byte payloads that are not valid UTF-8 text.

### Examples

#### Basic string literal

```silk
fn main () -> int {
  let s: string = "hello";
  if s == "hello" {
    return 0;
  }
  return 1;
}
```

#### Escapes and byte escapes

```silk
fn main () -> int {
  // Quote and backslash escapes.
  if "\"" != "\x22" { return 1; }
  if "\\" != "\u{005C}" { return 2; }

  // Control escapes.
  if "\t" != "\x09" { return 3; }
  if "\n" != "\x0A" { return 4; }
  if "\r" != "\n" { return 5; } // `\r` is normalized to `\n` in the current subset.

  // NUL bytes are permitted; strings are length-tracked (not NUL-terminated).
  if "\0" != "\x00" { return 6; }

  // Unicode escapes insert UTF-8 bytes for that scalar.
  if "é" != "\u{00E9}" { return 7; }

  return 0;
}
```

#### Raw multiline string literal (preferred for static multiline text)

```silk
fn main () -> int {
  let multi: string = `a
b`;

  // Equivalent to using a `\n` escape.
  if multi != "a\nb" {
    return 1;
  }

  return 0;
}
```

#### Quoted multi-line string literal (when escapes are needed)

```silk
fn main () -> int {
  let multi: string = "a
b";
  if multi != "a\nb" {
    return 1;
  }

  // Backslashes are literal bytes in raw strings, so escape-focused code may
  // still need quoted strings for comparison.
  if `a\nb` != "a\\nb" { return 2; }

  return 0;
}
```

#### Embedded adjacent file

```silk
let shader_source: string = #embed("shader.metal");

fn main () -> int {
  if sizeof(shader_source) > 0 {
    return 0;
  }
  return 1;
}
```

### Common Pitfalls

- **Expecting NUL termination**: `"hi"` does not include an implicit `\0`.
 Use `\0` explicitly when you need it, and prefer APIs that are length-aware.
- **Using `\xNN` for non-ASCII characters**: `\xNN` inserts a raw byte, not a
 Unicode scalar. Use `\u{...}` for text.
- **Assuming multi-line indentation stripping**: multi-line strings include all
 bytes between the quotes, including indentation spaces.

### Related Documents

- `Types` (primitive `string` and `char`)
- [`Character Literals`](/silk/docs/language/literals-character/) (shared escape spellings)
- `C99 ABI and `libsilk.a`` (C ABI string representation)

## Regular Expression Literals

Regular expression literals represent `regexp` values: compiled regular
expression bytecode that can be used by [`std::regex`](/silk/docs/std/regex/) helpers.

The regex literal syntax is modeled after JavaScript:

- `/pattern/flags`

### Notes

What is intended to work end-to-end (lexer → parser → checker → lowering → codegen):

- Regex literal parsing in expression-start positions: `/pattern/flags`.
- Compile-time compilation during type checking:
 - invalid patterns are rejected during type checking,
 - invalid or duplicate flags are rejected during type checking,
 - overly deep regexp nesting is rejected within a conservative compile
 stack budget instead of recursing without a bound in the embedder,
 - successful literals embed compiled bytecode into the output.
- The literal’s type is `regexp`.

### Syntax

#### Delimiters and scanning

Regex literals are scanned by the parser (not the lexer):

- the opening delimiter is a single `/`,
- the closing delimiter is the first unescaped `/` that is **not** inside a
 character class (`[...]`),
- after the closing delimiter, the parser consumes ASCII letters as flags.

The parser does not interpret regex escapes: backslash sequences are preserved
as bytes for the regex engine.

#### Empty patterns and `//`

Because `//` introduces a line comment, an empty regex literal `//` is not a
valid token sequence. Use an explicit empty pattern, for example `/(?:)/`.

### Flags

The supported flag set is intentionally small in the Supported forms:

- `g` — global (recorded; does not change [`std::regex::matches`](/silk/docs/std/regex/) semantics)
- `i` — ignore case
- `m` — multiline
- `s` — dotAll
- `y` — sticky
- `d` — indices (recorded; not surfaced by [`std::regex`](/silk/docs/std/regex/) helpers)

The type checker rejects:

- unknown flags,
- duplicate flags (for example `/a/ii`).

### Semantics

- A regex literal’s value is a non-owning `{ ptr, len }` view (`regexp`) into
 compiled bytecode embedded in read-only data.
- The bytecode format is owned by the runtime regex engine; `regexp` values are
 opaque and must be consumed via [`std::regex`](/silk/docs/std/regex/).
- A literal `regexp` is borrowed data, not a heap-owned regex object:
 [`std::regex::RegExp.compile(...)`](/silk/docs/std/regex/) is the owning/runtime-allocated path, while
 wrapping a literal in [`std::regex::RegExp`](/silk/docs/std/regex/) does not transfer ownership.
- When a foreign ABI caller supplies a malformed `regexp` buffer to
 [`std::regex`](/silk/docs/std/regex/), the runtime rejects it as invalid input before entering the
 bundled engine.
- If a literal/borrowed/foreign `regexp` is later passed to the low-level
 regex free path, the runtime ignores it safely instead of freeing arbitrary
 pointers.
- In the Supported forms, matching is defined over the raw bytes of the input
 `string`, and match indices are byte offsets.
- Literal compilation uses the same conservative regexp compile stack budget as
 runtime [`std::regex::RegExp.compile(...)`](/silk/docs/std/regex/); excessively deep patterns are
 rejected as `E2104` (`invalid regexp literal`).

### Examples

#### Basic `test`

```silk
import std::regex;

fn main () -> int {
  if std::regex::matches(/hello/, "hello world") {
    return 0;
  }
  return 1;
}
```

### Related Documents

- `Types` (`regexp`)
- [`Formal Grammar Spec`](/silk/docs/language/grammar/) (regexp literal grammar)
- [`std::regex`](/silk/docs/std/regex/) (runtime regex API)

## Character Literals

Character literals represent Unicode scalar values (code points) and have type
`char` (`Types`).

Use `char` for:

- single-character markers and delimiters (e.g. `','`, `':'`),
- working with code points when interfacing with parsing/lexing logic,
- representing control characters (`'\n'`, `'\t'`, `'\0'`).

If you need multiple characters, use `string` literals ([`String Literals`](/silk/docs/language/literals-string/)).

### Notes

What works end-to-end today (lexer → parser → checker → lowering → codegen):

- UTF-8 character literals like `'x'`, `'é'`, and `'😀'` (exactly one Unicode
 scalar, encoded in UTF-8 in the source file).
- Escape sequences:
 - `\n`, `\r`, `\t`, `\0`
 - `\\`, `\'`, `\"`
 - `\xNN` (exactly two hex digits)
 - `\u{...}` (1–6 hex digits)
- Equality and inequality comparisons (`==`, `!=`) over `char` values.
- `char` values are lowered as a `u32` scalar in the current IR backend subset.

Not implemented yet (or not specified as stable):

- A dedicated diagnostic for invalid character literal spellings (most invalid
 forms currently surface as generic “unsupported expression” errors in the
 Supported forms).

### Surface Syntax

Character literals are delimited by single quotes:

```silk
let a: char = 'x';
```

Rules:

- The contents must represent **exactly one Unicode scalar value**.
- A character literal must not span multiple lines.
- The source file is interpreted as UTF-8.

### Escapes

Inside a character literal, `\` introduces an escape sequence.

Supported escapes:

- `\n` — U+000A (line feed)
- `\r` — U+000D (carriage return)
- `\t` — U+0009 (tab)
- `\0` — U+0000 (NUL)
- `\\` — backslash
- `\'` — single quote
- `\"` — double quote
- `\xNN` — a code point given as exactly two hex digits
- `\u{...}` — a code point given as 1–6 hex digits

Unicode rules:

- The decoded code point must be a Unicode scalar value:
 - range `0x0000..=0x10FFFF`, excluding the surrogate range
 `0xD800..=0xDFFF`.
- For `\u{...}`, values outside that range are rejected.

### Semantics

Evaluating a character literal produces a `char` value whose numeric value is
the decoded Unicode code point.

In the backend, that code point is carried as a `u32` scalar.
This is an implementation detail; the language-level rule is “a `char` is a
Unicode scalar value”.

### Examples

#### ASCII and punctuation

```silk
fn main () -> int {
  let comma: char = ',';
  if comma == ',' {
    return 0;
  }
  return 1;
}
```

#### Unicode: literal UTF-8 vs `\u{...}`

```silk
fn main () -> int {
  let a: char = 'é';
  let b: char = '\u{00E9}';
  if a == b {
    return 0;
  }
  return 1;
}
```

#### Escape sequences

```silk
fn main () -> int {
  if '\n' != '\x0A' { return 1; }
  if '\r' != '\x0D' { return 2; }
  if '\t' != '\x09' { return 3; }
  if '\0' != '\x00' { return 4; }
  if '\\' != '\u{005C}' { return 5; }
  if '\'' != '\x27' { return 6; }
  if '\"' != '"' { return 7; }
  return 0;
}
```

### Common Pitfalls

- **Using double quotes**: `"x"` is a `string`, not a `char`. Use `'x'`.
- **Writing more than one character**: `'ab'` is invalid; use `"ab"`.
- **Source encoding surprises**: prefer `\u{...}` for non-ASCII characters when
 you want the source spelling to be stable across editors/fonts.
- **Confusing `\xNN` between `char` and `string`**:
 - for `char`, `\xNN` denotes a code point value,
 - for `string`, `\xNN` denotes a raw byte ([`String Literals`](/silk/docs/language/literals-string/)).

### Related Documents

- `Types` (primitive `char` and `string`)
- [`String Literals`](/silk/docs/language/literals-string/) (string literals and escape sequences)
- `Operators` (`as` casts for int-like types, including `char`)

## Aggregate Literals

Aggregate literals cover arrays and structs.

### Array Literals

Array literals construct fixed-size array values from a list of elements.

#### Surface Syntax

An array literal is written using square brackets:

```silk
let xs = [1, 2, 3];
let ys = [1, 2, 3,]; // trailing comma allowed
```

Empty array literals are permitted only when an expected array type is
available from context (so the compiler knows the element type and, for
fixed-size arrays, the required length):

```silk
let empty: i32[0] = [];
let empty_slice: i32[] = [];
```

#### Typing

- A non-empty array literal has type `T[N]` where `N` is the number of
 elements and `T` is inferred from the elements (or from an expected type
 when present).
- When an expected type is present and it is `T[N]`, the literal must contain
 exactly `N` elements.
- When an expected type is present and it is `T[]`, the literal’s elements are
 type-checked against `T` and the resulting value has type `T[]`.
 - In Silk currently, this slice form is lowered as a non-owning
 view over a compiler-generated backing array.
 - Lifetime rules are not yet enforced for such
 slices; do not allow a slice derived from a stack-backed array literal to
 outlive the scope where it was created.

Compiler requirements:

- Infer element type when possible, or require explicit annotation where
 ambiguity exists.
- Validate that all elements are convertible to the target element type.
- Enforce current-subset restrictions on which element types are supported for
 array lowering/codegen (see `Types` and
 [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)).

### Struct Literals

Struct literals construct values of `struct` types by specifying field names and values.

#### Surface Syntax

A struct literal may be written in two forms:

- An **explicit** struct literal begins with a struct type name followed by a
 brace-enclosed field initializer list.
- A **contextual (inferred)** struct literal omits the type name and consists
 only of the brace-enclosed field initializer list. This form is only valid
 when an expected struct type is available from context (for example a
 function argument position or an explicit type annotation).

An explicit struct literal looks like:

```silk
struct Pair {
  a: int,
  b: int,
}

fn make () -> Pair {
  return Pair { a: 1, b: 2 };
}
```

Whitespace may appear between the type name and `{` in ordinary expression
contexts. The parser suppresses that non-adjacent form only at the immediate
boundary where an expression is followed by a statement block (for example the
condition or iterable expression before `if`, [`while`](/silk/wiki/language/flow-while/), `for`, or [`match`](/silk/wiki/language/flow-match/) block
braces). Inside delimited subexpressions such as array literals, call
arguments, indexes, parenthesized expressions, and accepted struct initializer
values, `Type { ... }` remains an explicit struct literal because the brace
cannot be the following statement block.

An inferred struct literal looks like:

```silk
struct User {
  name: string,
}

fn print_user (user: User) -> void {
  std::println("user.name = {}", user.name);
}

fn main () -> int {
  // Equivalent to: `print_user(User{ name: "user name" });`
  print_user({ name: "user name" });
  return 0;
}
```

Initializers are written as either:

- `field_name: <expr>` (explicit initializer), or
- `field_name` (shorthand initializer, equivalent to `field_name: field_name`).

Initializers are separated by commas and an optional trailing comma is
permitted.

Example (shorthand):

```silk
struct User {
  name: string,
}

fn main () -> int {
  let name: string = "alice";
  let user = User{ name }; // equivalent to `User{ name: name }`
  if (user.name != "alice") { return 1; }
  return 0;
}
```

#### Field defaults (struct declarations)

A `struct` field declaration may include a default value expression:

```silk
struct Beep {
  value: string = "boop",
}
```

When a struct literal omits a field, the compiler uses the field default
expression when present; otherwise it falls back to zero-initialization in the
current backend subset. This means the empty literal form is useful when all
fields have defaults:

```silk
let b = Beep {};
```

Important notes:

- Inferred struct literals are a **value** construction mechanism. They do not
 imply heap allocation. The compiler will not infer `&T` from `{ ... }`; use
 `new` for heap allocation explicitly.
- The parser only treats `{ ... }` as an inferred struct literal when it
 contains a struct-style initializer list (or is `{}`); blocks (`{ Stmt* }`)
 remain statement syntax (there is no general “block expression” in the current
 subset).

#### Default constructors (empty struct literals)

If a `struct` defines a **default constructor** method with the signature:

```silk
fn constructor (mut self: &Self) -> void { ... }
```

then an **empty** struct literal invokes it as part of value construction:

- `Type{}` (explicit empty literal)
- `{}` when a struct type is expected from context (inferred empty literal)

Construction order:

1. All struct slots are zero-initialized.
2. Field default expressions are evaluated for omitted fields (if present).
3. The default constructor is invoked, allowing it to mutate `self`.

Visibility rule:

- The default constructor is invoked only when it is visible from the current
 package (constructors are `public` by default; an explicitly `private`
 constructor is not invoked implicitly).

Supported forms note:

- Non-empty struct literals (for example `Type{ x: 1 }`) do **not** invoke
 constructors implicitly.

Compiler requirements:

- Enforce that field names are valid and that each field is initialized at most once.
- Define the behavior for omitted fields (in the Supported forms, omitted fields
 are default-initialized).
- Respect struct lowering/layout rules from [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/).

#### Notes

The current compiler implementation supports struct literals only for the
limited struct subset described in [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/):

- structs with 0+ fields of supported value types (scalar primitives, `string`,
 nested structs, and supported optionals),
- literals may omit fields:
 - omitted fields that have a field default (`field: T = <expr>`) use that
 default expression,
 - otherwise, omitted fields are **zero-initialized** in the current backend
 subset,
- no duplicate field initializers are permitted,
- field order is not semantically significant.

## Boolean Literals

Boolean literals are the two built-in logical values:

- `true`
- `false`

They have type `bool` (`Types`).

### Notes

What works end-to-end today (lexer → parser → checker → lowering → codegen):

- `true` / `false` literal tokens.
- `bool` variables, parameters, and return values.
- `if` / [`while`](/silk/wiki/language/flow-while/) conditions must have type `bool`.
- Boolean operators:
 - unary `!`,
 - short-circuit `&&` and `||` (left-to-right, skip evaluation of the right
 operand when the result is already determined).

### Examples

#### Basic control flow

```silk
fn main () -> int {
  let ready: bool = true;
  if ready {
    return 0;
  } else {
    return 1;
  }
}
```

#### Short-circuit evaluation

```silk
fn returns_false () -> bool {
  return false;
}

fn main () -> int {
  // Because the left operand is `true`, the right operand is evaluated.
  let a: bool = true && returns_false();
  if a {
    return 1;
  }

  // Because the left operand is `false`, the right operand is not evaluated.
  let b: bool = false && returns_false();
  if b {
    return 2;
  }

  return 0;
}
```

### Common Pitfalls

- **Assuming “truthy” values**: `bool` is a distinct type. Use comparisons to
 produce a `bool` (for example `x != 0`) rather than writing `if x { ... }`.
- **Forgetting short-circuiting**: `&&` and `||` may skip evaluating the right
 operand; do not rely on side effects in the skipped operand.

### Related Documents

- `Types` (the `bool` type)
- `Operators` (`!`, `&&`, `||`)
- ``if` / `else`` (`if` statement semantics)
- ``while` Loop` ([`while`](/silk/wiki/language/flow-while/) statement semantics)

## Duration Literals

The `Duration` and `Instant` types have specialized literal support.

### Syntax

Duration literals are written as a decimal integer or decimal float immediately
followed by a unit suffix:

- Examples:
 - `10ns`
 - `250us`
 - `5ms`
 - `1s`
 - `1.5s`
 - `2min`
 - `1h`

The unit suffix is part of the literal token; the lexer must not split it into
an integer token followed by an identifier.

### Units

Recognized suffixes:

- `ns` — nanoseconds
- `us` — microseconds
- `ms` — milliseconds
- `s` — seconds
- `min` — minutes
- `h` — hours
- `d` — days

### Semantics

Duration literals evaluate to a `Duration` value represented as an `i64`
nanosecond count.

- For integer forms (e.g. `5ms`), the value is scaled exactly.
- For floating-point forms (e.g. `1.5s`), the value is scaled and then rounded
 toward zero to an integral nanosecond count.

If the scaled value does not fit in `i64`, compilation fails.

Compiler requirements:

- Implement lexing rules that distinguish unit suffixes from identifiers.
- Map duration literals to the `Duration` type with correct unit scaling.
- Ensure constant-evaluation behavior (rounding, overflow) matches the spec.

## Flow Control Overview

Flow control describes how Silk programs sequence work, branch, loop, and exit.
This concept spans several surface constructs and their static rules (typing,
scoping, and diagnostics).

### Core Constructs

- `if` / `else`
- [`loop`](/silk/wiki/language/flow-loop/) loops
- [`while`](/silk/wiki/language/flow-while/) loops
- `for` loops
- [`match`](/silk/wiki/language/flow-match/) expressions
- `return`
- `break`
- `continue`
- blocks and statement composition
- expression statements

Each construct has defined syntax, typing, and evaluation semantics which the
compiler must implement.

### Notes

Implemented end-to-end in the current compiler:

- `if` / `else` as statement forms (``if` / `else``)
- [`loop`](/silk/wiki/language/flow-loop/) loops (``loop` Loop`)
- [`while`](/silk/wiki/language/flow-while/) loops (``while` Loop`)
- `for` loops (ranges, builtin arrays/slices, and C-style `for (init; cond; step)`; ``for` Loop`)
- `break` / `continue` inside loops (``break``,
 ``continue``)
- `return` statements, including “all paths must return” checking for non-`void`
 functions (``return``)
- [`match`](/silk/wiki/language/flow-match/) as an expression for optionals and enums (``match` Expression (and Statement)`)
- [`match`](/silk/wiki/language/flow-match/) as a statement for typed errors (`Typed Errors (`error`, `panic`, and `T | ErrorType...`)`)
- Expression statements for calls and assignments only
 ([`Expression Statements`](/silk/docs/language/flow-expression-statements/))

Not implemented yet (design exists, but the current parser/checker do not
accept these end-to-end):

- `if` as a value-producing expression form

When in doubt, consult:

- `the implementation status` (implementation snapshot)
- [`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/) (error codes)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/) (working examples)

### Principles

These rules help keep control flow explicit and statically checkable:

- Conditions are boolean: `if` and [`while`](/silk/wiki/language/flow-while/) require a `bool` condition (no
 integer “truthiness”).
- Bodies are blocks: flow constructs use `{ ... }` blocks as their bodies.
- Statements are terminated: most statement forms end with `;` (for example
 `let`, `return`, `break`, `continue`, `panic`, `assert`, and expression
 statements).

### Quick Examples

Branching:

```silk
fn main () -> int {
  let x: int = 1;
  if x == 0 {
    return 0;
  } else {
    return 1;
  }
}
```

Looping:

```silk
fn main () -> int {
  let mut i: int = 0;
  while i < 3 {
    i += 1;
  }
  return 0;
}
```

Matching:

```silk
fn main () -> int {
  let x: int? = Some(7);
  let y: int = match x {
    None => 0,
    Some(v) => v,
  };
  return y;
}
```

See the dedicated documents:

- ``if` / `else``
- ``loop` Loop`
- ``while` Loop`
- ``for` Loop`
- ``match` Expression (and Statement)`
- ``return``
- ``break``
- ``continue``
- [`Blocks and Statement Composition`](/silk/docs/language/flow-blocks-statements/)
- [`Expression Statements`](/silk/docs/language/flow-expression-statements/)

## `if` / `else`

The `if` / `else` construct provides branching based on a boolean condition.

In Silk currently, `if` is a **statement** that selects which
block of statements executes. The broader language design also includes
expression-oriented forms; those are documented as planned where relevant.

### Surface Syntax

Minimal form:

```silk
if <condition> {
  ...
}
```

With an `else`:

```silk
if <condition> {
  ...
} else {
  ...
}
```

### `if let` (Pattern-Destructuring Statement Form)

Silk also supports an `if let` statement form for refutable pattern matching
without introducing a separate [`match`](/silk/wiki/language/flow-match/) expression:

```silk
if let <pattern> = <scrutinee> {
  ...
} else {
  ...
}

if let mut <pattern> = <scrutinee> {
  ...
}
```

Notes:

- The scrutinee expression is evaluated exactly once.
- The pattern binders (for example `Some(v)` binds `v`) are in scope only in
 the `then` block.
- `if let mut` marks binders introduced by the pattern as mutable in that
 scope, so they may be reassigned like ordinary `let mut` locals.
- `else` is optional (when omitted, a non-matching scrutinee executes no block).
- `else if let ...` chains are supported and parse as nesting in the same way
 as `else if ...`.
- `else let ...` is supported as shorthand for `else if let ...`; `else let mut`
 has the same binder mutability as `else if let mut`.
- `if let move ...`, `else if let move ...`, and `else let move ...` consume the
 scrutinee for ownership-tracked values. The consumed source binding is not
 available in the `then` block, the `else` block, or after the `if`.

#### `if let` chains (`&& let`)

The `if let` statement form supports a short-circuiting `&&` chain that mixes
refutable `let` clauses and ordinary boolean clauses:

```silk
if let Some(x) = get_x() &&
   x > 0 &&
   let mut Ok(v) = get_value(x) {
  // `x` and `v` are in scope here.
  v = v + 1;
  return v;
} else {
  // `x` and `v` are NOT in scope here.
  return 0;
}
```

Semantics:

- Clauses are evaluated left-to-right and short-circuit like `&&`.
- A `let <pattern> = <expr>` clause evaluates `<expr>` exactly once:
 - if the pattern matches, its binders are introduced and evaluation continues,
 - otherwise the entire condition is `false`.
- A `let mut <pattern> = <expr>` clause introduces mutable binders for the
 remaining clauses and the `then` block.
- A `let move <pattern> = <expr>` clause consumes the clause scrutinee for
 ownership-tracked values. The moved source binding is unavailable in later
 clauses, the `then` block, and the `else` block.
- A non-`let` clause must have type `bool`; `false` short-circuits.
- Binders introduced by `let` clauses are in scope for:
 - subsequent clauses in the chain, and
 - the `then` block.
 They are not in scope in the `else` block, and they do not escape the `if`.

Parsing note (Supported forms):

- `&&` at the top level is parsed as a clause separator. Use parentheses if a
 clause needs its own `&&` / `||` / `??` expression at the top level.

Example (`else let` shorthand):

```silk
fn main () -> int {
  let a: int? = None;
  let b: int? = Some(3);

  if let Some(v) = a {
    return v;
  } else let Some(v) = b {
    return v;
  } else {
    return 0;
  }
}
```

Supported patterns in the Supported forms (same as [`match`](/silk/wiki/language/flow-match/) expressions; see
``match` Expression (and Statement)`):

- optionals: `None`, `Some(name)`, `Some(_)`
- recoverable results: `Ok(name)`, `Err(name)` (and `_` binders)
- enums: `Variant(...)` / `E::Variant(...)` / qualified variants
- type unions: `name: Type` / `_: Type`

Example (optional):

```silk
fn main () -> int {
  let maybe: int? = Some(7);

  if let Some(v) = maybe {
    return v;
  }
  return 0;
}
```

Example (recoverable `Result`):

```silk
import std::result;

fn main () -> int {
  let r: std::result::Result(int, string) = Ok(42);
  if let Ok(v) = r {
    return v;
  }
  return 0;
}
```

Notes:

- `<condition>` is an expression; parentheses are optional because the normal
 expression grammar already includes parenthesized expressions.
- Bodies are blocks. `else` may be followed by either:
 - a block (`else { ... }`), or
 - another `if` (`else if ... { ... }`) to form an “else-if” chain.

### Surface Syntax (Expression Form)

Silk also supports `if` / `else` as an **expression** form that yields a value:

```silk
let v: int = if cond { 123 } else { 456 };
```

Notes:

- `if` expressions require an `else` branch so the expression yields a value on
 all paths.
- The `else if ...` chain form is supported in expression position:

  ```silk
  let v: int = if a { 1 } else if b { 2 } else { 3 };
  ```

- Restriction: the `{ ... }` bodies of `if` expressions
 contain a single expression (not a full statement block).

### Semantics

- The condition expression is evaluated exactly once.
- If the condition is `true`, the `if` block executes and the `else` block (if
 present) does not execute.
- If the condition is `false`, the `else` block executes if present; otherwise
 the `if` statement does nothing.

Blocks create scopes:

- Declarations inside the `if` body are not visible outside that body.
- Declarations inside the `else` body are not visible outside that body.

### Type Checking Rules

- The condition must have type `bool`. If it does not, the checker reports a
 type mismatch ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2001`).

For `if` expressions:

- The `then` and `else` branches must produce compatible value types.
- The expression’s result type is the shared branch type (or the expected type
 when the expression is type-directed).

### `else if` Chains

The language supports chained conditions (“else-if chains”). The compiler
parses `else if` as sugar for nesting an `if` inside the `else` block:

```silk
fn main () -> int {
  let x: int = 1;

  if x == 0 {
    return 0;
  } else {
    if x == 1 {
      return 1;
    } else {
      return 2;
    }
  }
}
```

The equivalent direct surface form is:

```silk
fn main () -> int {
  let x: int = 1;
  if x == 0 {
    return 0;
  } else if x == 1 {
    return 1;
  } else {
    return 2;
  }
}
```

### Examples

#### Minimal `if` / `else`

```silk
fn main () -> int {
  if true {
    return 0;
  } else {
    return 1;
  }
}
```

#### Boolean expressions in conditions

```silk
fn main () -> int {
  let x: int = 1;
  let y: int = 2;

  if x < y && y < 10 {
    return 3;
  } else {
    return 4;
  }
}
```

#### Control flow inside branches

```silk
fn main () -> int {
  let x: int = 1;
  let y: int = 2;

  if x < y {
    while false {
      continue;
    }
    return 3;
  } else {
    return 4;
  }
}
```

### Notes

Implemented end-to-end:

- `if <expr> { ... }` and `if <expr> { ... } else { ... }` statement forms.
- `if let <pattern> = <expr> { ... }` statement form:
 - `else if let` / `else let` chains, and
 - `&&` let-chains in the `if let` condition.
- Boolean type-checking for conditions.
- `if` expressions of the form `if <cond> { <expr> } else { <expr> }`.

Not implemented yet:

- General block expressions (`{ stmt* <expr> }`) outside the specific `if`
 expression form.

examples:

- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)

## [`while`](/silk/wiki/language/flow-while/) Loop

The [`while`](/silk/wiki/language/flow-while/) loop repeatedly executes a block while a boolean condition holds.

### Surface Syntax

Minimal form:

```silk
while <condition> {
  // body
}
```

`<condition>` is an expression. Parentheses are optional because the condition
is parsed using the normal expression grammar:

```silk
while (x < y && y < 10) {
  ...
}
```

### `while let` (Pattern-Destructuring Loop Form)

Silk supports a `while let` loop form for iterating while a refutable pattern
matches:

```silk
while let <pattern> = <scrutinee> {
  ...
}

while let mut <pattern> = <scrutinee> {
  ...
}
```

Notes:

- The scrutinee expression is evaluated once per iteration.
- The pattern binders (for example `Some(v)` binds `v`) are in scope only in
 the loop body.
- `while let mut` marks binders introduced by the pattern as mutable for that
 iteration's loop body.
- The loop exits when the scrutinee does not match the pattern.
- Supported patterns are the same as `if let` (see ``if` / `else``).

#### `while let` chains (`&& let`)

The `while let` loop form supports the same short-circuiting `&&` chain syntax
as `if let`, mixing refutable `let` clauses and ordinary boolean clauses:

```silk
fn main () -> int {
  var x: int? = Some(3);
  var sum: int = 0;

  while let mut Some(v) = x && v > 0 {
    let original = v;
    v = v + 1;
    sum = sum + v;
    x = if original <= 1 { None } else { Some(original - 1) };
  }

  return sum;
}
```

Semantics:

- Clauses are evaluated left-to-right and short-circuit like `&&`.
- `let` clause binders are in scope for subsequent clauses and for the loop
 body, but they do not escape the loop.
- `let mut` clauses introduce mutable binders for subsequent clauses and for
 that iteration's loop body.
- `let move` clauses consume their scrutinee for ownership-tracked values. This
 is most useful when the scrutinee is a fresh expression each iteration, such
 as `while let move Some(value) = next() { ... }`; a moved local source binding
 is unavailable to later clauses, the loop body, and code after the loop.
- The loop exits when any clause fails (pattern mismatch or boolean `false`).

Parsing note (Supported forms):

- `&&` at the top level is parsed as a clause separator. Use parentheses if a
 clause needs its own `&&` / `||` / `??` expression at the top level.

Example (optional countdown):

```silk
fn main () -> int {
  var x: int? = Some(3);
  var sum: int = 0;

  while let Some(v) = x {
    sum = sum + v;
    if v <= 1 {
      x = None;
    } else {
      x = Some(v - 1);
    }
  }

  // 3 + 2 + 1 = 6
  return sum;
}
```

#### Loop Specifications (`#invariant` / `#variant` / `#monovariant`)

The language supports attaching loop specifications immediately before a
[`while`](/silk/wiki/language/flow-while/). This is part of Formal Silk (see `Formal Silk`).
When Formal Silk syntax is present, the compiler proves these obligations with
Z3 at compile time.

```silk
#invariant <expr>;
#variant <expr>;
#monovariant <expr>;
while <condition> {
  ...
}
```

### Semantics

Evaluation rules:

- The condition is evaluated before each iteration.
- If the condition evaluates to `true`, the body block executes.
- After the body completes normally, control returns to the condition.
- If the condition evaluates to `false`, the loop terminates and execution
 continues after the loop statement.

Control-flow statements inside the body follow their own definitions:

- `break` exits the nearest enclosing loop (``break``).
- `continue` skips to the next iteration (``continue``).
- `return` exits the function (``return``).
- `panic` exits the function via the typed error system (`Typed Errors (`error`, `panic`, and `T | ErrorType...`)`).

Blocks create scopes. A `let` declared inside the body is not visible outside
the loop’s body block.

### Type Checking Rules

The checker enforces:

- The loop condition must have type `bool` (otherwise `E2001`).
- Each `#invariant` expression must have type `bool` (otherwise `E2001`).
- If present, the `#variant` expression must have an integer type (`int` or a
 fixed-width integer; otherwise `E2001`).
- Each `#monovariant` expression must have an integer type (`int` or a
 fixed-width integer; otherwise `E2001`).

`#invariant`, `#variant`, and `#monovariant` expressions are compile-time-only
(erased from runtime code). When Formal Silk verification is enabled by syntax,
they are proved with Z3 during compilation.

### Examples

#### Minimal loop with `break`

```silk
fn main () -> int {
  while true {
    break;
  }
  return 0;
}
```

#### Loop with invariants and a variant

```silk
fn main () -> int {
  let limit: int = 3;
  #const original_limit = limit;

  let mut i: int = 0;
  #invariant i >= 0;
  #invariant i <= original_limit;
  #variant original_limit - i;
  while i < limit {
    i = i + 1;
  }

  return 0;
}
```

### Notes

Implemented end-to-end:

- [`while`](/silk/wiki/language/flow-while/) loops with boolean conditions.
- `while let <pattern> = <expr> { ... }` pattern-destructuring loops.
- `&&` let-chains in `while let` loop conditions.
- `break` / `continue` inside [`while`](/silk/wiki/language/flow-while/) bodies.
- `#invariant` (type-checked as `bool`), `#variant` (type-checked as an
 integer), and `#monovariant` (type-checked as an integer) attached to [`while`](/silk/wiki/language/flow-while/).

examples:

- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)

## `break`

`break` exits the nearest enclosing loop.

### Surface Syntax

```silk
break;
```

Notes:

- `break` is a statement, terminated by a semicolon.
- `break` does not carry a value in the current language design; there is no
 `break <expr>` form.

### Semantics

When executed, `break;`:

- terminates the innermost enclosing loop ([`loop`](/silk/wiki/language/flow-loop/), [`while`](/silk/wiki/language/flow-while/), or `for`), and
- continues execution at the statement immediately following that loop.

In nested loops, `break` only exits the nearest loop:

```silk
fn main () -> int {
  while true {
    while true {
      break; // exits the inner loop only
    }
    break; // exits the outer loop
  }
  return 0;
}
```

`break` does not exit the current function. Use `return` for that.

### Type Checking Rules

- `break` is only permitted inside a loop body.
- A `break` outside a loop is a type-check error ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/),
 `E2007`).

### Notes



- `break;` is accepted inside loops ([`loop`](/silk/wiki/language/flow-loop/), [`while`](/silk/wiki/language/flow-while/), and `for`) and lowered
 end-to-end.
- `break;` outside of a loop is rejected (`E2007`).

examples:

- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/) (shows `break` in a loop with specs)

### Common Pitfalls

- Forgetting the semicolon (`break` is a statement).
- Expecting `break` to return a value (not supported).
- Using `break` outside a loop (rejected, `E2007`).

## `continue`

`continue` skips the remainder of the current loop iteration and jumps to the
next iteration of the nearest enclosing loop.

### Surface Syntax

```silk
continue;
```

Notes:

- `continue` is a statement, terminated by a semicolon.

### Semantics

When executed inside a loop body, `continue;`:

- stops executing the remainder of the current iteration’s body, and
- transfers control to the loop’s “next iteration” point:
 - for [`loop`](/silk/wiki/language/flow-loop/), this means jumping to the start of the loop body.
 - for [`while`](/silk/wiki/language/flow-while/), this means re-evaluating the loop condition.
 - for `for`, this means advancing to the next iteration (and for C-style `for`
 loops, executing the loop step before re-checking the loop condition).

Example:

```silk
fn main () -> int {
  let mut i: int = 0;
  while i < 10 {
    i += 1;
    if i == 5 {
      continue; // skips the return below for i == 5
    }
    // More work could happen here.
  }
  return 0;
}
```

In nested loops, `continue` applies to the nearest loop:

```silk
fn main () -> int {
  while true {
    while true {
      continue; // continues the inner loop
    }
  }
  return 0;
}
```

### Type Checking Rules

- `continue` is only permitted inside a loop body.
- A `continue` outside a loop is a type-check error ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/),
 `E2008`).

### Notes



- `continue;` is accepted inside loops ([`loop`](/silk/wiki/language/flow-loop/), [`while`](/silk/wiki/language/flow-while/), and `for`) and lowered
 end-to-end.
- `continue;` outside a loop is rejected (`E2008`).

Example that uses `continue` in the Supported forms:

- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)

### Common Pitfalls

- Forgetting the semicolon (`continue` is a statement).
- Expecting `continue` to exit the loop (it does not; use `break`).
- Using `continue` outside a loop (rejected, `E2008`).

## `return`

The `return` statement exits a function, optionally with a value.

### Surface Syntax

Return a value:

```silk
return <expr>;
```

Return from a `void` function:

```silk
return;
```

### Semantics

When a `return` statement executes:

- the current function terminates immediately, and
- control transfers back to the caller,
- carrying a return value if the function’s result type is non-`void`.

No statements after a `return` in the same control-flow path are executed.

### Type Checking Rules

The checker enforces:

- `return` is only valid inside a function body (otherwise `E2009`).
- In a function with non-`void` result type `R`, `return` must provide an
 expression whose type is `R` (otherwise `E2009`).
- In a `void` function, `return;` is permitted and `return <expr>;` is rejected
 (`E2009`).
- In a function with non-`void` result, falling off the end of the function
 body is a compile-time error ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2010`).

### Examples

#### Returning from `main`

```silk
fn main () -> int {
  return 0;
}
```

#### Early return

```silk
fn main () -> int {
  let x: int = 1;
  if x == 0 {
    return 0;
  }
  return 1;
}
```

#### `return;` in a `void` function

```silk
struct Counter {
  value: int,
}

impl Counter {
  fn inc (mut self: &Counter) -> void {
    self.value += 1;
    return;
  }
}
```

### Notes

Implemented end-to-end:

- `return <expr>;` from non-`void` functions, with type checking.
- `return;` from `void` functions.
- Missing return in a non-`void` function is rejected (`E2010`).

examples:

- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/) (wrong type, rejected)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/) (missing return, rejected)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/) (uses `return;` in a `-> void` method)

## [`match`](/silk/wiki/language/flow-match/) Expression (and Statement)

The [`match`](/silk/wiki/language/flow-match/) expression provides structured pattern matching.

Key ideas:

- A [`match`](/silk/wiki/language/flow-match/) selects one of several branches based on a scrutinee expression.
- The full language design includes richer patterns and arm guards, but the
 current shipped subset documented here does not implement `if` guards in any
 [`match`](/silk/wiki/language/flow-match/) form yet.
- [`match`](/silk/wiki/language/flow-match/) is an expression; all arms must be compatible in type.

The compiler must:

- Enforce exhaustiveness rules (where specified).
- Type check each arm and compute a consistent result type.

### Surface Syntax

The full language design includes rich pattern matching, guards, and matching
over many scrutinee types. The current compiler implementation supports only a
narrow, explicitly documented subset so we can validate end-to-end lowering and
code generation.

In the initial subset, [`match`](/silk/wiki/language/flow-match/) is accepted as an *expression* of the form:

```silk
match <scrutinee> {
  <pattern> => <expr>,
  <pattern> => <expr>,
}
```

Notes:

- Arms are separated by commas; a trailing comma is permitted.
- In the initial subset, arm bodies are expressions (not blocks).
- In the current compiler, expression-form [`match`](/silk/wiki/language/flow-match/) is implemented for:
 - optionals,
 - primitive integers,
 - primitive booleans,
 - primitive strings,
 - enums,
 - type unions,
 - and recoverable `Result`-style values.
- Guard clauses of the form `pattern if cond => ...` are currently unsupported
 across all of those subsets.

#### Optional Matching (`T?`)

For optionals, the Supported forms is:

- The scrutinee expression must have optional type `T?` (`Option(T)`), where `T`
 is a payload type supported by the backend.
- Patterns are restricted to:
 - `None`
 - `Some(<name>)`
 - `Some(_)`
- No guards (`if ...`) are implemented yet.
- Matches must be exhaustive for the optional scrutinee: there must be exactly
 one `None` arm and exactly one `Some(...)` arm (order is not significant).

Example:

```silk
fn main () -> int {
  let x: int? = Some(7);
  let y: int = match x {
    None => 5,
    Some(v) => v,
  };
  return y;
}
```

#### Integer Matching (Primitive Integers)

The compiler also supports a small [`match`](/silk/wiki/language/flow-match/) subset for integer-like primitive
scrutinees.

Notes: 

- The scrutinee expression must have a primitive integer type in the current
 backend subset (`int`, `i64`, `u64`, and the fixed-width integer primitives).
- Patterns are restricted to:
 - integer literals (`0`, `1`, `123`, `0xFF`, ...), and
 - a wildcard `_` arm.
- The match must be exhaustive:
 - when the scrutinee is not a known integer literal, there must be exactly one
 wildcard `_` arm, and that arm must be final,
 - when the scrutinee is a known integer literal, the wildcard may be omitted
 only if one integer-literal arm covers that exact value, and
 - literal arms must not repeat the same value.
- No guards (`if ...`) are implemented yet.

Example:

```silk
fn main () -> int {
  let x: int = 0;
  let y: int = match (x) {
    0 => 1,
    _ => 2,
  };
  return y;
}
```

Literal scrutinees may be exhaustively matched by the literal arms alone:

```silk
fn main () -> int {
  let y: int = match 2 {
    1 => 10,
    2 => 20,
  };
  return y;
}
```

#### Boolean Matching (Primitive `bool`)

The compiler also supports [`match`](/silk/wiki/language/flow-match/) over primitive `bool` scrutinees.

Supported forms:

- The scrutinee expression must have type `bool`.
- Patterns are restricted to:
 - boolean literals (`false` and `true`), and
 - a wildcard `_` arm.
- The match must be exhaustive:
 - when the scrutinee is not a known boolean literal, it must cover both
 `false` and `true`, or have exactly one final wildcard `_` arm,
 - when the scrutinee is a known boolean literal, the wildcard may be omitted
 only if one boolean-literal arm covers that exact value, and
 - literal arms must not repeat the same boolean value.
- No guards (`if ...`) are implemented yet.

Example:

```silk
fn main () -> int {
  let b: bool = false;
  let y: int = match b {
    false => 1,
    true => 2,
  };
  return y;
}
```

#### String Matching (Primitive `string`)

The compiler also supports [`match`](/silk/wiki/language/flow-match/) over primitive `string` scrutinees.

Supported forms:

- The scrutinee expression must have type `string`.
- Patterns are restricted to:
 - string literals (`"..."` and raw backtick strings), and
 - a wildcard `_` arm.
- Literal comparison uses the decoded byte sequence of each string literal.
- The match must be exhaustive:
 - when the scrutinee is not a known string literal, it must have exactly one
 final wildcard `_` arm,
 - when the scrutinee is a known string literal, the wildcard may be omitted
 only if one string-literal arm covers that exact decoded byte sequence, and
 - literal arms must not repeat the same decoded byte sequence.
- No guards (`if ...`) are implemented yet.

Example:

```silk
fn main () -> int {
  let s: string = "foo";
  let y: int = match s {
    "foo" => 1,
    _ => 2,
  };
  return y;
}
```

Literal scrutinees may be exhaustively matched by the literal arms alone:

```silk
fn main () -> int {
  let y: int = match "foo" {
    "foo" => 1,
    "bar" => 2,
  };
  return y;
}
```

#### Enum Matching (`enum`)

The language design supports matching over user-defined `enum` types
(``enum` Types`).

Notes: 

- The scrutinee expression must have an enum type `E` (including an
 instantiated generic enum in module-set builds).
- Patterns are restricted to enum variants:
 - unit variants: `E::Cancelled` or `Cancelled`
 - tuple variants: `E::Msg(x)` / `Msg(x)` / `E::Pair(a, b)` / `Pair(a, b)` (binders may be identifiers or `_`)
- For instantiated generic enums, the qualifier `E` in patterns may be a type
 alias for the instantiation (for example `type R = Result(int, string);` then
 `R::Ok(v)` / `R::Err(e)`), or patterns may omit the qualifier and use the
 variant name directly.
- No guards (`if ...`) are implemented yet.
- In expression form, enum matches must still be exhaustive:
 - either there is exactly one explicit arm for each enum variant,
 - or a final wildcard `_` arm covers every remaining unmatched variant,
 - and if `_` is used, it may appear at most once and must be the final arm.

#### Type Union and Concrete Typed-Binder Matching

The language supports matching over **type unions** (`Type Unions (`T1 | T2 | ...`)`).
The same typed-binder arm syntax is also accepted for concrete struct values in
the backend when the checker can select exactly one applicable
arm statically.

Notes: 

- For a union scrutinee, the scrutinee expression must have a union type
 `T1 | ... | Tn`.
- For a concrete struct scrutinee, the scrutinee expression must have a known
 concrete struct type, and exactly one typed-binder arm must accept that type.
- Patterns are restricted to typed binders:
 - `name: Ti` (binds the payload as `Ti`), or
 - `_: Ti` (matches and ignores the payload),
 where `Ti` is one of the union member types for union scrutinees, or an
 accepting nominal type for concrete scrutinees.
- For concrete struct scrutinees, an arm type accepts the scrutinee when it is:
 - the exact concrete type or an alias of it,
 - a valid base type in the struct `extends` chain, or
 - an interface implemented by the concrete type.
- No guards (`if ...`) are implemented yet.
- Union matches must be exhaustive: there must be exactly one arm per union
 member type (order is not significant).
- Concrete typed-binder matches are statically selected: there must be exactly
 one accepting arm. Zero accepting arms are rejected as a missing arm, and more
 than one accepting arm is rejected as an ambiguous typed-binder match.

Example:

```silk
interface Object {}
struct A { x: int }
struct B { x: int }
impl A as Object {}

fn main () -> int {
  let a = A { x: 1 };
  let y = match a {
    v: A => v.x,
    _: B => 0,
  };
  let z = match a {
    _: Object => 2,
  };
  return y + z;
}
```

### Semantics

- The scrutinee expression is evaluated exactly once.
- The selected arm is chosen based on the scrutinee value; non-selected arms
 are not evaluated.
- For `Some(v) => ...`, the binder `v` is in scope only within that arm and has
 type `T` (the inner payload type of the scrutinee `T?`).
- For typed-binder arms `v: T => ...`, the binder `v` is in scope only within
 that arm and has the annotated type `T`; `_: T` checks the same type contract
 without introducing a binder.
- The result type of a [`match`](/silk/wiki/language/flow-match/) expression is the common type of its arms; all
 arms must type-check to the same result type in the initial subset.

### [`match`](/silk/wiki/language/flow-match/) Statement (Block Arms)

Silk also supports a *statement* form of [`match`](/silk/wiki/language/flow-match/) whose arms are blocks. This is
the ergonomic counterpart to the expression form when an arm must perform
multiple statements (printing, early returns, mutation, etc).

Surface form:

```silk
match (<scrutinee>) {
  <pattern> => { ... },
  <pattern> => { ... },
}
```

The statement form may also use a single-expression arm body without braces:

```silk
match (x) {
  _ => do_work(),
}
```

An optional trailing semicolon is permitted after the closing brace:

```silk
match (x) { _ => { } };
```

In Silk currently, the statement form is supported for:

- ordinary value matching (no typed-error contract), and
- typed error handling (`Typed Errors (`error`, `panic`, and `T | ErrorType...`)`).

#### Ordinary value matching

When the scrutinee expression is an ordinary value (it does *not* have a typed
error contract), the statement form supports the same scrutinee + pattern
subsets as the [`match`](/silk/wiki/language/flow-match/) expression form in this document:

- optionals (`T?`): `None` / `Some(name)` / `Some(_)`
- primitive integers: integer literals and `_`
- primitive booleans: `false` / `true` and `_`
- primitive strings: string literals and `_`
- enums (`enum`): enum variants (see note below)
- type unions (`T1 | ... | Tn`): typed binders `name: Ti` / `_: Ti`
- recoverable results: `Ok(name)` / `Ok(_)` and `Err(name)` / `Err(_)`

Exhaustiveness rules:

- Expression [`match`](/silk/wiki/language/flow-match/) remains exhaustive.
- Statement [`match`](/silk/wiki/language/flow-match/) is also exhaustive by default.
- For `Option(T)` and recoverable `Result`-like values only, the statement form
 may omit one side of the split:
 - `match (opt) { Some(v) => { ... } }`
 - `match (opt) { None => { ... } }`
 - `match (res) { Ok(v) => { ... } }`
 - `match (res) { Err(e) => { ... } }`
- In that one-arm statement form, the unhandled case is an implicit no-op.
- This partial form does not apply to expression [`match`](/silk/wiki/language/flow-match/), integer matches,
 general enums, type unions, or typed-error matches.

Notes:

- The preferred single-branch control-flow forms remain `if let`, `let ... else`,
 and `while let` when they fit naturally.
- For ordinary enum matches, both expression and statement form now support one
 final wildcard `_` arm end to end.
- That wildcard arm:
 - may appear at most once,
 - must be the final arm,
 - and must cover at least one still-unmatched variant.

Enum variant pattern note (statement form):

- In the statement form, a bare identifier pattern `name` is reserved for a
 catch-all binder arm (used by typed error matches), so enum variant patterns
 must be written in qualified form: `E::Variant(...)` (including
 `::pkg::E::Variant(...)`).
- `match (stream) { Error => { ... } }` therefore treats `Error` as a binder
 arm, while `match (stream) { IOStream::Error => { ... } }` matches the unit
 enum variant.
- Ordinary enum statement matches now also allow one final `_` catch-all arm,
 with the same final-arm and non-redundancy rules as expression-form enum
 matches.

#### Typed error matching (Terminal Arm Rule)

The language design also includes a statement form of [`match`](/silk/wiki/language/flow-match/) used for
*typed errors* (`Typed Errors (`error`, `panic`, and `T | ErrorType...`)`).

Surface form:

```silk
match (expr) {
  pattern => { ... },
  err: SomeError => { std::abort(); }
}
```

Key semantic rule (Terminal Arm Rule):

- If `expr` is an error-producing expression (its signature includes `T | ErrorType...`),
 then any arm that matches an `error` type must end in a terminal statement.

Implementation

- The compiler currently implements [`match`](/silk/wiki/language/flow-match/) as an expression for the documented
 Supported forms:
 - optionals (`T?`),
 - primitive integers,
 - type unions,
 - recoverable `Result`-style values,
 - and exhaustive enum matches.
- No match arm guards (`pattern if cond => ...`) are implemented yet in either
 expression or statement form.
- The statement form is implemented for:
 - ordinary values in the supported subset (block arms), and
 - typed errors as part of the typed errors feature work (`Typed Errors (`error`, `panic`, and `T | ErrorType...`)`).

Note: the compiler also allows the [`match`](/silk/wiki/language/flow-match/) statement form to destructure
recoverable `Result`-style values. This form does not trigger the Terminal Arm
Rule because it is not a `T | ...` typed-error expression.

#### Result Matching (`Ok(...)` / `Err(...)`)

The [`match`](/silk/wiki/language/flow-match/) expression also supports a small subset for
recoverable “success or error” values. In the initial subset, this includes:

- [`std::result::Result(T, E)`](/silk/docs/std/result/) (an `enum` with `Ok(T)` and `Err(E)` variants), and
- “Result-like” structs of the form `{ value: T?, err: E? }`.

For the struct form, the runtime invariant is: exactly one of `value` and `err`
is `Some(...)`. If this invariant is broken at runtime, execution traps.

Patterns:

- `Ok(name)` / `Ok(_)`
- `Err(name)` / `Err(_)`

Rules (Supported forms):

- Enum form:
 - The scrutinee expression must have an enum type with variants `Ok` and `Err`.
 - `Ok(...)` / `Err(...)` patterns are shorthand for `R::Ok(...)` / `R::Err(...)` where `R`
 is the scrutinee enum type, and may appear alongside other enum variant patterns.
 - In expression form, exhaustiveness follows the enum rules: there must be
 exactly one arm per enum variant.
- Struct form:
 - The scrutinee expression must have a nominal struct type that contains
 `value: T?` and `err: E?`.
- Matches must be exhaustive:
 - for enum scrutinees in expression form, follow the enum rules (one arm per
 variant, or a single final wildcard arm covers the remaining unmatched
 variant),
 - for struct scrutinees, there must be exactly one `Ok(...)` arm and exactly
 one `Err(...)` arm.
- In `Ok(v) => ...`, the binder `v` has type `T`.
- In `Err(e) => ...`, the binder `e` has type `E`.

Example:

```silk
import std::result;
import std::strings::String;

fn main () -> int {
  let s: String = match String.from_string("hello") {
    Ok(v) => v,
    Err(_) => String.empty(),
  };
  return s.len as int;
}
```

One-arm statement examples:

```silk
match (parse_port(input)) {
  Ok(port) => {
    use_port(port);
  },
}
```

```silk
match (std::env::get("HOME")) {
  None => {
    std::io::println("HOME is not set");
  },
}
```

## Structs, Impl Blocks, and Memory Layout

Structs and impl blocks are separated:

- `struct` declarations define pure data layout.
- `impl` blocks attach behavior to types without changing their layout.

### `struct` Declarations

Structs define a composite data type made of named fields:

```silk
struct Frame {
  sequence: u32,
  size: u16,
  flag: u8,
}
```

Key rules:

- Structs contain only data members.
- Memory layout and padding are well-defined so that FFI and ABI rules can rely on them.
- Stack vs heap allocation is specified in [`Memory Model (Stack, Heap, and Moves)`](/silk/docs/language/memory-model/).

#### Generic structs

Structs may declare type parameters:

```silk
struct Data(T) {
  value: T,
}
```

Rules:

- A generic `struct Name(T, ...)` introduces a **type constructor** `Name`.
- Outside a generic context, uses of the type must be fully applied (for
 example `Data(u8)`), not bare `Data`.
- A declaration name may not be reused across different generic arities (for
 example `struct Foo { ... }` and `struct Foo(T) { ... }` cannot both exist in
 the same namespace).

#### Field Default Initializers

Struct fields may include an optional default initializer expression:

```silk
struct Point {
  x: int = 0,
  y: int = 0,
}
```

When a struct literal omits a field, the compiler initializes the field from its
default expression.

In Silk currently, a field default must be a compile-time
evaluable expression. This is broader than the current default-function-argument
rule.

Supported forms currently include:

- literals,
- `const` bindings,
- calls to `const fn`,
- struct literals, and
- field access over compile-time values.

Current limits:

- `new` is still rejected,
- ordinary runtime helper calls are rejected, and
- the expression still has to type-check against the field type.

Example:

```silk
const DEFAULT_Y: int = 0;

struct Point {
  x: int = 0,
  y: int = DEFAULT_Y,
}

fn main () -> int {
  let p = Point{ x: 5 };
  return p.y; // defaults to 0
}
```

#### Single Inheritance (`extends`)

Silk supports **single inheritance** for `struct` declarations via `extends`.

Surface syntax:

```silk
struct Base {
  x: int,
  y: int = 0,
}

struct Derived extends Base {
  z: int,
}
```

Semantics (Supported forms):

- A derived struct inherits all fields of its base struct.
- The derived struct’s field sequence is:
 1) all base fields (in declaration order), then
 2) all derived fields (in declaration order).
- Field access on the derived struct can refer to inherited base fields
 directly (`d.x`, `d.y`).
- Default field initializers are inherited:
 - a `Derived{ ... }` literal may omit inherited fields that have defaults in
 the base struct.

Type checking rules (Supported forms):

- `extends` is permitted only on non-opaque `struct` declarations.
- The base name must resolve to a `struct` type in the compiled module set.
- Cycles in `extends` chains are rejected.
- A derived struct may not declare a field whose name conflicts with an
 inherited field name.

Notes:

- `extends` does not imply implicit subtyping in Silk currently:
 there is no implicit coercion from `Derived` to `Base` (or `&Derived` to
 `&Base`) yet.

#### Opaque Structs (FFI Handles)

Opaque structs are a special form of `struct` declaration intended for safely
representing foreign pointers/handles from C APIs.

Syntax:

```silk
// Declares an opaque handle type.
struct MyFFIHandle;
```

An opaque struct has **no fields** and **no Silk-defined layout**. It exists
only as a nominal handle type that can be passed around safely.

Rules:

- Opaque structs **cannot be instantiated** (no struct literals).
- Opaque structs **do not support field/member access** (`.` / `?.`).
- Opaque structs **must not be used by value** in type positions (locals,
 parameters, results). Only the reference form `&MyFFIHandle` is allowed.

These rules increase safety at the language boundary:

- **Eliminates type confusion**: distinct handle types such as `&DatabaseHandle`
 and `&FileHandle` are not interchangeable.
- **Prevents invalid operations in Silk**: Silk code cannot read/write fields or
 assume a size/layout for the foreign type.

##### Safety and Undefined Behavior (UB)

Opaque handles do not carry lifetime information. You are responsible for
calling the corresponding destruction/free function provided by the foreign
library.

Using an opaque handle after it has been destroyed is **undefined behavior**.
The compiler does not currently enforce this at compile time.

##### ABI and Lowering

In the backend, an `&Opaque` value is lowered as a single pointer
scalar (`u64` on the current `linux/x86_64` target), rather than as a
struct-of-pointers like `&struct` borrows.

#### Memory Layout (Intended Contract)

The long-term Silk design is for `struct` layout to match conventional C layout
rules for the corresponding field types on the target:

- **Sequential layout**: fields appear in memory in the exact order they are
 declared in the `struct` definition.
- **Alignment and padding**: each field is placed at an offset that is a
 multiple of the field type’s required alignment. The compiler inserts padding
 bytes where necessary.
- **Final padding**: the overall struct size is padded to a multiple of the
 struct’s alignment (typically the maximum alignment of its fields), so arrays
 of the struct keep each element correctly aligned.

Example (typical C layout on `linux/x86_64`):

```silk
struct Frame {
  sequence: u32, // 4 bytes
  size: u16,     // 2 bytes
  flag: u8,      // 1 byte
}
```

Conceptually, this layout would be:

- `sequence` at offset `0` (4 bytes)
- `size` at offset `4` (2 bytes)
- `flag` at offset `6` (1 byte)
- 1 byte of tail padding at offset `7` to make the total size a multiple of 4

Total size: 8 bytes (alignment 4).

#### Memory Layout

the compiler does **not** implement packed C-like struct
layout yet. Instead, it uses a *scalar slot* model:

- A `struct` value is lowered into a sequence of scalar “slots” in source order,
 after recursively expanding certain composite field types:
 - `string` contributes two slots: `(u64 ptr, i64 len)`.
 - nested non-opaque structs contribute their slot sequence.
 - optionals contribute `(bool tag, payload slots...)`, where payload slots
 follow the lowering of the underlying non-optional type.
- When a `struct` is stored in memory (stack locals and heap boxes), each slot
 is stored in a separate **8-byte cell**.
 - This means sub-64-bit fields (`bool`, `i8`/`u8`, `i32`/`u32`, `f32`, `char`,
 etc.) are not packed yet.
 - Values are still *typed* as their declared scalar kinds (the checker and IR
 track widths/sign), but the physical in-memory representation is widened to
 one 8-byte slot per scalar.

This design keeps lowering/codegen simple and lets the compiler support nested
aggregates without committing to a final packed layout. The trade-off is that
the in-memory representation is not ABI-compatible with a C struct unless the
struct is restricted to ABI-safe 64-bit slots.

Example : the `Frame` above is lowered as 3 scalar
slots and occupies 24 bytes when stored in memory (3 × 8-byte cells), even
though the intended C-like packed layout would be 8 bytes.

#### ABI and Code Generation

The Silk language design includes full support for user-defined structs, nested
aggregates, and FFI-safe ABI mapping. The current compiler/backend
implementation supports only a narrow, explicitly documented subset:

- Only "plain" structs with **0+ fields** are supported by codegen.
 - Empty structs (`struct Empty {}`) are currently represented as a single
 placeholder `u64` slot in the scalar-slot model.
- Fields may be:
 - scalar primitive types (`bool`, fixed-width integers, `int`, `char`,
 `f32`/`f64`, `Instant`, `Duration`),
 - `string` (lowered as `{ ptr: u64, len: i64 }`),
 - nested (non-opaque) structs,
 - and optionals (`T?`) of supported payload types.
- At ABI boundaries (exported functions and `ext` declarations), structs must be
 ABI-safe: after slot-flattening, all slots must be `i64`/`u64`/`f64` (for
 example `string` fields are ABI-safe because they lower to `(u64, i64)`, but
 `bool`, `char`, and `f32` fields are not).
- Such structs are passed and returned by value by lowering them to their
 scalar slots in order and following the System V AMD64 ABI rules for
 those scalar slots:
 - integer-like slots consume general-purpose argument slots (`rdi`, `rsi`,
 `rdx`, `rcx`, `r8`, `r9`, then the stack),
 - `f32`/`f64` slots consume XMM argument slots (`xmm0`..`xmm7`, then the stack),
 - 1–2 slot results use `rax`/`rdx` for integer-like slots and `xmm0`/`xmm1`
 for float slots, with mixed aggregates using both,
 - 3+ slot results return indirectly via a hidden sret pointer passed in `rdi`
 (caller-allocated return buffer), with the callee storing each scalar slot
 sequentially and returning the pointer in `rax`.

Note: at the C ABI surface, exported functions accept ABI-safe structs by
flattening parameters to their scalar slots in order. For 1–2 slot structs this
is ABI-compatible with passing an equivalent by-value C struct parameter, while
for 3+ slot structs downstream C callers should declare separate scalar
parameters for the slots. Struct returns with 3+ slots use sret and are
ABI-compatible with returning an equivalent C struct by value.

This subset is intentionally small so that we can validate the end-to-end type
pipeline (parsing → checking → lowering → IR→ELF codegen) while keeping ABI
behavior consistent with C for the supported cases.

### `impl` Blocks

`impl` blocks attach functions and methods to existing types without affecting
memory layout.

The intent is to provide “high-level” APIs without baking behavior into `struct`
layout. In the implementation, `impl` blocks are *syntax and
type-checking structure*; code generation treats methods as ordinary functions
that follow the same calling conventions as other Silk functions.

#### Generic impl blocks

If a type is declared with type parameters (struct or enum), its impl blocks
must provide a full type-argument list of the same arity.

Each position in the `impl Name(...)` argument list may be:

- a **type parameter name** (a generic impl), or
- a **concrete primitive type name** (an impl specialization for that argument).

```silk
struct Data(T) { value: T }

// Generic impl (applies to all specializations of Data(T)).
impl Data(T) {
  fn get(self: &Self) -> T { return self.value; }
}

// Specialized impl (applies only to Data(u8)).
impl Data(u8) {
  fn is_zero(self: &Self) -> bool { return self.value == 0; }
}
```

Specialized impl blocks are merged with any other applicable impl blocks for
the same type specialization, subject to the usual duplicate method-name rules.

Supported forms limitation:

- Only **primitive type names** (for example `u64`, `string`, `bool`) are
 recognized as concrete specialization arguments in `impl Name(...)`. Any
 other identifier in an `impl` argument position is treated as a type
 parameter name.

#### Syntax

```silk
impl List {
  // Ordinary static method (no receiver).
  fn init (cap: i64) -> List { ... }

  // Heap constructor used by `new List(...)` (special name, receiver + `void`).
  fn constructor (mut self: &Self, cap: i64) -> void { ... }

  // Instance method (receiver as first parameter).
  public fn len (self: &List) -> i64 { ... }

  // Mutating instance method (mutable receiver).
  public fn push (mut self: &List, value: u8) -> void { ... }
}
```

Rules:

- An `impl` block attaches methods to exactly one nominal type name (a `struct`
 or an `enum`).
- Multiple `impl` blocks may exist for the same type name; the compiler merges
 their methods (subject to duplicate-name rules).
- Methods inside an `impl` block are `fn` declarations (with bodies).
- The receiver, when present, is the first parameter named `self` and must be
 either:
 - a borrowed reference to the `impl` type (`self: &Type` / `mut self: &Type`),
 or
 - an owned value of the `impl` type (`self: Type` / `mut self: Type`).
- Within an `impl` block, the special type name `Self` may be used anywhere a
 type name is accepted, and is treated as an alias for the `impl` type.
 For example, `self: &Self` is equivalent to `self: &Type`, and `-> Self` is
 equivalent to `-> Type`.
- Static methods omit the receiver parameter.
- Method visibility:
 - Methods are **private by default**: a method declared without an explicit
 visibility modifier is callable only within the **defining `impl { ... }`
 block**.
 - `public fn` marks a method as callable from outside the defining `impl`
 block.
 - `private fn` is permitted to make intent explicit.
 - `export` is reserved for static members (no `self` receiver) and is not
 permitted on instance methods; use `public fn` instead.
 - When an `impl` block declares conformance to an interface (`impl T as I`),
 the interface’s required methods are **public by definition**:
 - the corresponding impl methods may omit `public`, but
 - they may not be explicitly marked `private`.
 See `Interfaces`.
- The method named `constructor` is treated specially:
 - it is only meaningful for `struct` types (it backs `new Type(...)`); enums
 do not support `constructor` methods in the Supported forms,
 - it is `public` by default,
 - when explicitly marked `private`, it is callable only within the defining
 `impl { ... }` block,
 - it may be declared multiple times in a single `impl` block (an overload set),
 - its overload set includes `constructor` declarations across all merged
 `impl` blocks for the type,
 - it is invoked by:
 - heap allocation (`new Type(...)`),
 - empty struct literals (`Type{}` and contextual `{}`) when a visible
 default constructor exists (see [`Aggregate Literals`](/silk/docs/language/literals-aggregate/)),
 - and certain call-argument coercions (see `Types`),
 - `new Type(args...)` invokes the unique overload whose receiver is
 `mut self: &Type`, whose return type is `void`, and whose non-receiver
 parameter list matches `args...` after applying the normal call-argument
 type-checking rules,
 - if multiple overloads are applicable, the compiler prefers overloads that do
 **not** rely on implicit call-argument coercions (notably the `U -> &T`
 constructor coercion for `&T` parameters); if multiple overloads remain tied,
 the call is rejected as ambiguous.

#### Call syntax

The surface call syntax uses field-access + call:

- Instance method call: `value.method(arg0, arg1, ...)`
- Static method call: `Type.method(arg0, arg1, ...)`

Semantically, method calls behave like ordinary function calls where the
receiver is passed as an explicit first argument.

Static-method receiver sugar (Supported forms):

- If `value.method(...)` does not resolve to an instance method (a method whose
 first parameter is a receiver `self: &Type` / `mut self: &Type`), the
 compiler may resolve it as a call to a visible static method of the receiver
 type by inserting the receiver as the first argument: `Type.method(value, ...)`.
- This supports fluent chaining for value-consuming helper APIs like
 [`std::result::Result.unwrap_or`](/silk/docs/std/result/):

  ```silk
  let r: R = /* ... */;
  let x: int = r.unwrap_or(0); // sugar for `R.unwrap_or(r, 0)`
  ```

Mutability rule (Supported forms):

- If the method receiver is `self: &Type`, the call site passes a read-only
 borrow of the receiver (for example `value.method(...)`).
- If the method receiver is `mut self: &Type`, the call site must pass a
 mutable borrow of the receiver.
 - When the receiver is a **name binding** that is mutable (`let mut value = ...`)
 or a mutable reference binding (for example a `mut self: &Type` receiver),
 the compiler treats `value.method(...)` as a mutable receiver call (no
 `(mut value)` wrapper required).
 - The explicit `value.method(...)` form is permitted but is no longer
 required for name receivers.
- If the method receiver is `self: Type` or `mut self: Type`, the call site
 passes the receiver **by value**. For ownership-tracked values (for example
 types with `Drop`), this consumes the receiver binding (use after move is
 rejected); for plain scalars and POD structs it behaves like a copy.

Supported forms limitations:

- Mutable **borrow** receiver calls (`mut self: &Type`) must use a name receiver;
 mutable borrows from non-name receiver expressions (for example `make().push(1)`)
 are rejected.
- Non-`mut` receivers may be arbitrary expressions (including calls), so
 chaining like `url.href().as_string()` is permitted.

Compiler requirements:

- Keep data layout and behavior separate in the IR.
- Preserve struct layout exactly for ABI and FFI.
- Enforce rules for opaque structs and UB as described in this document and the ABI spec.

## `enum` Types

An `enum` defines a *tagged union* type: a value that is exactly one of several
named variants, optionally carrying a payload.

Use enums to model:

- finite state machines (connection state, parser state),
- protocol messages and events,
- and any API where “exactly one of these cases” is the core invariant.

If your goal is “a function can fail with one of several error shapes”, prefer
typed errors (`Typed Errors (`error`, `panic`, and `T | ErrorType...`)`) over enums.

### Notes

What works end-to-end today (parser → checker → lowering → codegen):

- **Enum declarations** with:
 - unit variants (`A`),
 - tuple variants (`Data(int)` and `Pair(int, int)`),
 - and an optional trailing comma after the last variant.
- **Construction**:
 - unit variants as values: `E::A`,
 - tuple variants as calls: `E::Data(7)`.
 - type-directed shorthand:
 - when the expected type is an enum `E`, unit variants may be written as `A`
 (sugar for `E::A`),
 - when the expected type is an enum `E`, tuple variants may be written as
 `Data(7)` (sugar for `E::Data(7)`).
- **Function signatures**:
 - enums may be used in parameter lists and return types,
 - error-producing functions may return enums (`-> E | ErrorType...`), and `call()?` works when the success type is an enum.
- **[`match`](/silk/wiki/language/flow-match/) expression over enums**:
 - patterns are restricted to enum variants (`E::A`, `E::Data(x)`) and may
 use shorthand (`A`, `Data(x)`) when the scrutinee type is the enum,
 - binders may be names or `_`,
 - no guards (`if ...`) yet,
 - and expression-form matches must still be *exhaustive* in the current
 subset:
 - either by explicit arm coverage for every variant,
 - or by a single final wildcard `_` arm that covers all remaining
 unmatched variants.
- **`??` over ordinary two-variant enums**:
 - for a named enum with exactly two declared variants, `value ?? fallback`
 treats the first declared variant as the “success” arm,
 - if that first variant is unit, the expression yields that enum value,
 - if that first variant carries exactly one payload, the expression yields
 that payload,
 - and if the enum value is the second declared variant, the fallback
 expression is evaluated.
- **[`match`](/silk/wiki/language/flow-match/) statement over ordinary enum values**:
 - enum variant arms must use the qualified form `E::Variant(...)`,
 - bare identifiers remain reserved for binder-style arms,
 - and both expression-form and statement-form ordinary enum matches now allow
 one final `_` catch-all arm, which must cover at least one remaining
 variant.
- **Generic enums (monomorphized)**:
 - `enum Name(T, ...) { ... }` declarations are supported in module-set builds
 that run monomorphization,
 - instantiated enums behave like ordinary enums once referenced (including
 construction and [`match`](/silk/wiki/language/flow-match/)),
 - callers typically introduce a local type alias for the instantiated enum
 (for example `type R = Result(int, string);`) and then use `R::Ok(...)` /
 `R::Err(...)` as constructors and patterns.
- **`impl` blocks on enums**:
 - enums may have `impl` blocks (including generic `impl EnumName(T, ...)`),
 - static methods are callable as `EnumName.method(...)` (including through
 type aliases for instantiated generic enums),
 - instance methods are callable as `value.method(...)` when the first
 parameter is a receiver (`self: &EnumName` / `mut self: &EnumName`),
 - the special `constructor` method used by `new Type(...)` is for `struct`
 types; enums do not support `constructor` methods in the Supported forms.

Not implemented yet (or not yet stable/documented):

- Guards in enum match arms (`E::A if cond => ...`).
- A stable ABI story for passing/returning enums across the C99 boundary (do
 not assume an enum layout until it is specified in `C99 ABI and `libsilk.a``).

When the compiler rejects an enum construct in the Supported forms, the most
common error is `E2002` (“unsupported expression in the Supported forms”). Type
mismatches inside enum constructors or match arms are `E2001` (“type mismatch”).

### Surface Syntax

Enum declarations introduce a nominal type and its variants:

```silk
enum RecvJob {
  Msg(Job),
  Cancelled,
  Timeout,
}
```

Rules:

- Variant names are identifiers and must be unique within the enum.
- Variant names may not be the reserved optional constructors `Some` / `None`.
- An enum must declare at least one variant.
- A variant is either:
 - a **unit** variant (no payload): `Cancelled`,
 - or a **tuple** variant with one or more payload element types: `Msg(Job)`,
 `Pair(int, int)`.
- A trailing comma after the last variant is permitted.

### Construction

#### Unit variants

Unit variants are constructed as values using `Enum::Variant` (or, in
type-directed contexts, just `Variant`):

```silk
enum E {
  A,
  B,
}

fn main () -> int {
  let x: E = E::A;
  let y: E = A;
  return 0;
}
```

Notes:

- `E::A()` and `A()` are invalid in the Supported forms (unit variants are not callable).

### Two-Variant `??` Shortcut

When a named enum declares exactly two variants, the `??` operator may be used
as a compact success/fallback form.

Rules in the Supported forms:

- declaration order matters: the first declared variant is the “success” case,
 and the second declared variant triggers the fallback,
- if the first variant is unit, `value ?? fallback` yields that enum value,
- if the first variant carries exactly one payload, `value ?? fallback` yields
 that payload,
- if the first variant carries more than one payload element, use [`match`](/silk/wiki/language/flow-match/)
 instead,
- the second variant may be unit or may carry payloads; its payload is ignored
 by the `??` form and the fallback expression runs.
- the right-hand side is still an expression in the current grammar; terminal
 statements such as `return`, `break`, and `continue` are not accepted there
 yet.

Examples:

```silk
enum Ready {
  Go,
  Stop,
}

fn require_ready (r: Ready) -> Ready {
  return r ?? Ready::Go;
}

enum ParsedPort {
  Port(int),
  Invalid,
}

fn port_or_default (p: ParsedPort) -> int {
  return p ?? 80;
}
```

#### Tuple variants

Tuple variants are constructed using `Enum::Variant(<args...>)` where the
argument count and types match the variant’s declared payload element types:

```silk
enum E {
  Data(int),
  Pair(int, int),
  Empty,
}

fn main () -> int {
  let a: E = E::Data(7);
  let b: E = Data(7);
  let b: E = E::Pair(1, 2);
  let c: E = E::Empty;
  return 0;
}
```

Notes:

- `E::Data` by itself is not a value in the Supported forms (tuple variants must
 be constructed with `(...)`).
- If a tuple-variant constructor argument has the wrong type, you get `E2001`.
- If the argument count does not match the variant definition, the compiler
 currently rejects the construct with `E2002`.

#### Generic enums (instantiation via alias)

When an enum is generic, callers typically alias an instantiation and then use
that alias as the qualifier for constructors:

```silk
enum Result(T, E) {
  Ok(T),
  Err(E),
}

type R = Result(int, int);

fn main () -> int {
  let x: R = R::Ok(123);
  return match x {
    R::Ok(v) => v,
    R::Err(_) => 0,
  };
}
```

#### Namespaced enums (packages)

Across packages, enums and variants may be referenced with `::` qualification.
For example, if `util` defines `enum Mode { Inc, Dec }`, an importer can write:

- `util::Mode` as the type name, and
- `util::Mode::Inc` / `util::Mode::Dec` as the constructors and patterns.

See [`Packages, Imports, and Exports`](/silk/docs/language/packages-imports-exports/) for module-set rules and for how
package imports seed qualified type names.

### Matching

Enum values are typically consumed via [`match`](/silk/wiki/language/flow-match/) expressions. The [`match`](/silk/wiki/language/flow-match/)
expression rules are defined in ``match` Expression (and Statement)`; this section
focuses on the enum-specific subset.

#### Patterns

Enum patterns are variant patterns:

- Unit variant: `E::A`
- Tuple variant: `E::Data(x)`, `E::Pair(a, b)`
- Tuple binder omission: `E::Pair(_, b)` (underscore binder ignores that element)

Shorthand:

- When the scrutinee type is the enum `E`, the qualifier may be omitted:
 - Unit variant: `A`
 - Tuple variant: `Data(x)`, `Pair(a, b)`

For instantiated generic enums, the qualifier `E` may be a type alias (for
example `type R = Result(int, string);` then `R::Ok(v)` / `R::Err(e)`).

Binders:

- introduce a name scoped to that arm only, and
- shadow outer bindings of the same name (because they create a new binding in
 the arm’s environment).

#### Exhaustiveness

In the Supported forms, enum exhaustiveness is split by [`match`](/silk/wiki/language/flow-match/) form:

- Expression form:
 - either there is exactly one arm per enum variant,
 - or a single final wildcard arm (`_ => ...`) covers the remaining unmatched
 variants,
 - explicit variant arms must not repeat the same variant,
 - and if `_` is used, it may appear at most once and must be the final arm.
- Statement form over ordinary enum values:
 - either there is exactly one arm per enum variant,
 - or a single final wildcard arm (`_ => ...`) covers the remaining unmatched
 variants,
 - explicit variant arms must not repeat the same variant,
 - and if `_` is used, it may appear at most once and must be the final arm.

If a match is not exhaustive:

- expression form currently reports `E2002`, and
- statement form without full coverage uses the checker’s missing-arm path when
 it reaches that analysis.

#### Example: unit enum match

```silk
enum E {
  A,
  B,
}

fn main () -> int {
  let v: E = E::A;

  let x: int = match v {
    E::A => 10,
    E::B => 20,
  };

  if x != 10 {
    return 1;
  }
  return 0;
}
```

#### Example: unit enum match with final wildcard

```silk
enum State {
  Ready,
  Busy,
  Closed,
}

fn score (s: State) -> int {
  return match s {
    Ready => 10,
    _ => 20,
  };
}
```

#### Example: tuple enum match (payload binders)

```silk
enum E {
  Pair(int, int),
  Empty,
}

fn main () -> int {
  let v: E = E::Pair(1, 2);

  let x: int = match v {
    E::Pair(a, _) => a,
    E::Empty => 0,
  };

  if x != 1 {
    return 1;
  }
  return 0;
}
```

#### Example: struct payload enum match

```silk
struct Job {
  id: int,
}

enum RecvJob {
  Msg(Job),
  Cancelled,
}

fn main () -> int {
  let j: Job = Job{ id: 5 };
  let evt: RecvJob = RecvJob::Msg(j);

  let rc: int = match evt {
    RecvJob::Msg(job) => job.id,
    RecvJob::Cancelled => 0,
  };

  if rc != 5 {
    return 1;
  }
  return 0;
}
```

### Representation

Enums are values. In the current IR-backed lowering, an enum value is lowered to
scalar slots as:

1. a `u64` **tag** (variant index in declaration order, starting at `0`), and
2. a **payload region** that includes a distinct slot range for each variant’s
 payload elements, in variant declaration order.

Conceptually:

```text
(u64 tag,
 payload slots for variant 0,
 payload slots for variant 1,
 ...)
```

Only the active variant’s payload region is meaningful for a given value; other
payload regions are unspecified.

This representation is an implementation detail and is expected to evolve (for
example, toward a tag + max-payload “union-style” layout) as the compiler and
ABI mature.

### Common Pitfalls

- **Forgetting parentheses**: `E::Data(7)` is valid, but `E::Data` is not a value
 in the Supported forms (error `E2002`).
- **Calling a unit variant**: `E::A` is a value; `E::A()` is rejected (`E2002`).
- **Wrong binder count**: `E::Pair(a)` does not match `Pair(int, int)` (`E2002`).
- **Non-exhaustive matches**: you must list every variant (error `E2002` in the
 Supported forms).
- **Assuming enum equality is defined**: use [`match`](/silk/wiki/language/flow-match/) to inspect the tag/payload;
 the backend does not define `==`/`!=` over enums yet.

### Related Documents

- ``match` Expression (and Statement)` (match expression rules)
- [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/) (struct payloads)
- `Types` (nominal types and type annotations)
- [`Packages, Imports, and Exports`](/silk/docs/language/packages-imports-exports/) (namespaces and imports)
- `Typed Errors (`error`, `panic`, and `T | ErrorType...`)` (typed errors, not enums)

## Interfaces

Interfaces allow types to declare that they implement a particular contract.
They are the foundation for standard-library “protocols” such as readers,
writers, iterators, and allocators.

When the standard library is enabled (the default), the compiler provides a
small implicit std prelude (see [`Packages, Imports, and Exports`](/silk/docs/language/packages-imports-exports/)).
In particular, the interface names from [`std::interfaces`](/silk/docs/std/interfaces/) are available without
an explicit `import std::interfaces;`. This prelude is specified by the stdlib
module [`std::runtime::globals`](/silk/docs/std/runtime-globals/).

Key components:

- The `interface` declaration.
- The `struct` that implements the interface.
- The `impl ... as ...` declaration that ties them together.
- A `module ... as ...` declaration for module-level conformance.

### Interface declarations

An interface declares a set of required method *signatures*.

Syntax:

```silk
interface Element {
  fn onclick(event: &Event) -> void;
}
```

Rules:

- Interface members are method declarations introduced with `fn`.
- Interface methods have **no body** and end with `;`.
- Parameter types in interface methods should be explicitly annotated (the
 compiler should not rely on type inference for interface contracts).
- Interface method parameter lists use the same trailing-varargs marker as
 ordinary functions, so a required method may end with `...args: T`.
- Interface methods are part of a **public contract**:
 - interfaces do not have private members, and
 - interface method declarations do not accept visibility modifiers.

### Generic interfaces

Interfaces may declare type parameters:

```silk
interface Channel(T) {
  fn send(value: T) -> bool;
  fn recv() -> T?;
}
```

Rules:

- Generic parameter lists use the same syntax as structs (`(T, ...)`).
- Type parameters may provide default type arguments (`T = Type`). When defaults
 are present, use sites may omit trailing arguments that have defaults.
- The interface name is a **type constructor** and must be applied with the
 correct number of type arguments where a concrete interface type is required
 (for example in `impl ... as ...` declarations).

### `Self` in interface signatures

Within an interface method signature, the special type name `Self` refers to
the concrete implementing type when checking `impl Type as Interface { ... }`
conformance.

### Interface inheritance (`extends`)

Interfaces may use `extends` for **single inheritance**:

```silk
interface BaseLogger {
  fn log(msg: string) -> void;
}

interface FancyLogger extends BaseLogger {
  fn warn(msg: string) -> void;
}
```

Semantics (Supported forms):

- An interface that `extends` another interface inherits all of the base
 interface’s method signatures.
- A conformance declaration (`impl T as I` or `module ... as I`) must satisfy
 the full inherited interface surface.

Rules (Supported forms):

- `extends` is permitted only on `interface` declarations.
- Only single inheritance is permitted (at most one `extends` base).
- Cycles in `extends` chains are rejected.
- A derived interface may not redeclare a method with the same name as an
 inherited base method.

### Implementations (`impl ... as ...`)

An implementation block declares that a concrete type implements an interface
and provides method bodies.

Example:

```silk
interface Element {
  fn onclick(event: &Event) -> void;
}

struct Button {
  handle: i64;
}

impl Button as Element {
  fn constructor(...) -> Button { ... }
  fn onclick(self: &Button, event: &Event) -> void { ... }
}
```

Applied interface types:

```silk
interface Read(T) {
  fn read() -> T;
}

struct ByteSource { /* ... */ }

impl ByteSource as Read(u8) {
  fn read(self: &ByteSource) -> u8 { /* ... */ }
}
```

Compiler requirements:

- Represent interface types and `impl ... as ...` relationships.
- Enforce that all required interface methods are implemented with compatible
 signatures.
- Treat required interface methods as **public by definition**:
 - impl methods that satisfy an interface requirement may omit `public`, but
 - they may not be explicitly marked `private`.

Conformance rules (implementation):

- For an `interface I { fn m(p0: T0, ...) -> R; }`, the corresponding impl must
 provide a method `m` whose signature matches after accounting for the
 receiver:
 - the interface method signature itself must omit any explicit `self`
 parameter; ordinary interface methods always use an implicit receiver,
 - the impl method’s first parameter is the receiver `self: &Type` (or
 `mut self: &Type`), and
 - the remaining parameters, including whether the final parameter is
 varargs, and the result type must match the interface method.
- Exception (static protocol, Supported forms):
 - [`std::interfaces::Deserialize(S)`](/silk/docs/std/interfaces/) and [`std::interfaces::Parse(E, S)`](/silk/docs/std/interfaces/) are
 receiverless static protocols. Their conformance does **not** use a
 receiver parameter:
 - `impl T as std::interfaces::Deserialize(S)` provides
 `fn deserialize(value: S) -> Self` (no `self` parameter),
 - `impl T as std::interfaces::Parse(E, S)` provides
 `fn parse(value: S) -> std::result::Result(Self, E)` (no `self`
 parameter),
 - calls use `T.deserialize(value)`.
 - and `Parse` calls use `T.parse(value)`.
 - Only `Deserialize` participates in `as` casts today. `Parse` remains an
 explicit method call so fallible construction stays visible in source.
 - The implemented conformance check substitutes `Self` recursively through
 nested generic result shapes, so interfaces such as
 `Parse(E) { fn parse(value: string) -> Result(Self, E); }` can be
 satisfied by impl methods whose concrete result type is a monomorphized
 specialization of that generic result.

Invalid ordinary interface declaration example:

```silk
interface Object {
  fn as_string(self: &Self) -> string; // invalid
}
```

Correct form:

```silk
interface Object {
  fn as_string() -> string;
}
```

Generic interface conformance rule:

- When the `as` clause names an applied generic interface type (for example
 `Read(u8)`), all type arguments must be fully known at the conformance site,
 unless the conformance itself is generic and binds those type parameters (for
 example `impl Data(T) as DataInterface(T)`).

### Module conformance (`module ... as ...`)

A module declaration may declare conformance to an interface:

```silk
interface Logger {
  fn log(msg: string) -> void;
}

module my_app::logger as Logger;

export fn log (msg: string) -> void {
  // ...
}
```

Name resolution:

- The interface name in `module ... as Interface;` is resolved after the module’s
 import block is processed, so it may refer to an interface imported later in
 the file’s import section.
 - This allows an unqualified, ergonomic module header form like:

    ```silk
    module hello::build as Builder;

    // Optional: only needed with `--nostd` or when the active stdlib prelude
    // does not include `Builder`.
    import { Builder } from "std/interfaces";
    ```

Conformance rules:

- For an `interface I { fn m(p0: T0, ...) -> R; }`, the corresponding module must
 provide a function `m` whose signature matches exactly:
 - there is no receiver parameter for module conformance, and
 - the parameter list, including whether the final parameter is varargs, and
 the result type must match the interface method.
- Conformance compares the **call result type** of the exported function:
 - `export async fn m (...) -> R` is treated as `m(...) -> Promise(R)`,
 - `export task fn m (...) -> R` is treated as `m(...) -> Task(R)`,
 - `export async task fn m (...) -> R` is treated as `m(...) -> Promise(Task(R))`.
 This allows module interfaces to express async/task entrypoints by writing the
 appropriate handle type in the interface method result.
- In Silk currently, module conformance is checked against the
 module’s **exported** functions (written as `export fn ...`), since those are
 the module members that are visible across module boundaries.

Generic module conformance:

- A module may declare conformance to an applied generic interface type
 (for example `module my_app::bytes as Read(u8);`).
- All interface type arguments must be fully specified (modules do not bind
 their own type parameters).

### Dispatch model (status)



The current compiler now supports interface-typed runtime values without a
separate boxed/vtable runtime. Instead, the native compiler resolves an
interface value type to a closed-world union of all known concrete conformers
in the current compilation set.

Example:

```silk
interface Object {
  fn as_string () -> string;
}

let xs: Object[] = [
  Foo {},
  Bar {},
];
```

In the current implementation, `Object[]` is lowered as an array whose element
type is the union of the known `Object` conformers (`Foo | Bar` in this
example). A method call like `value.as_string()` on an interface-typed runtime
value is rewritten by the checker into an ordinary [`match`](/silk/wiki/language/flow-match/) dispatch over that
union.

Typed-binder [`match`](/silk/wiki/language/flow-match/) patterns may also name an interface. When the scrutinee has
a concrete struct type and that type implements the named interface, an arm such
as `value: Object => ...` accepts the scrutinee. This concrete form is selected
statically and therefore also covers single-conformer cases without requiring a
preexisting interface-typed union value.

This means the current runtime interface subset supports:

- interface-typed local bindings, function parameters, borrowed function
 parameters (`&Interface`), and array elements,
- interfaces with empty method sets (`interface I {}`) in those same runtime
 positions when the conformer set is known,
- heterogeneous arrays/slices whose declared element type is an interface and
 whose values come from known conforming concrete types,
- method calls on those interface-typed values when the selected method is part
 of the shared conforming surface,
- typed-binder [`match`](/silk/wiki/language/flow-match/) arms whose pattern type is an implemented interface for
 a concrete struct scrutinee,
- special-case compiler hooks for specific interfaces (currently
 [`std::interfaces::Drop`](/silk/docs/std/interfaces/) for deterministic cleanup; see
 [`std::interfaces`](/silk/docs/std/interfaces/) and [`Memory Model (Stack, Heap, and Moves)`](/silk/docs/language/memory-model/)).

Current limits:

- This is a closed-world compilation strategy, not an open-world trait-object
 ABI. The conformer set is computed from the known program/package being
 compiled.
- The runtime representation of an interface-typed value therefore depends on
 that conformer set. If the set of known conformers changes, the lowered
 runtime shape can change too.
- The current implementation does not introduce a general boxed interface
 object or vtable layout.
- There is no language-level guarantee today that an ordinary interface type
 such as `Object` has one stable binary layout that can be passed unchanged
 across separately compiled libraries, plugin boundaries, or the C embedding
 surface.
- In practical terms, a separately compiled artifact cannot add a new
 conformer to an already-built interface-typed runtime boundary without
 recompiling the consumer too, because the consumer’s lowered union shape was
 chosen from the closed world it already knew about.
- Runtime interface values currently depend on the existing union-value backend,
 so they are supported where the resulting conformer union is representable by
 Silk currently.

#### Closed-world compilation versus open-world ABI

The current compiler strategy is:

- source-level interface declarations and `impl ... as ...` conformance,
- compile-time discovery of the conformers that are present in the current
 build,
- lowering runtime interface values to a concrete union over those conformers,
- and lowering interface method calls to ordinary [`match`](/silk/wiki/language/flow-match/) dispatch over that
 union.

The current compiler does **not** promise:

- a heap-boxed trait object,
- a stable `(data pointer, vtable pointer)` object model,
- a public binary layout for arbitrary interface values,
- or open-world dynamic dispatch where unknown future conformers can be linked
 in later without recompiling code that stores or passes `Interface` values.

This distinction matters at package and ABI boundaries:

- Within one closed-world native build, interface-typed locals, parameters,
 borrows, and arrays can work because the compiler can see the conformers.
- Across separately compiled binary boundaries, you must not assume that
 `Interface` itself is a stable interchange type.
- For C ABI boundaries, exported library interfaces, plugins, or other
 open-world extension points, use an explicit concrete ABI:
 - an enum/union chosen by the API author,
 - a concrete struct carrying tagged data,
 - or an explicit function-table struct if you need manually designed dynamic
 dispatch.

Treat ordinary Silk interfaces today as a language-level conformance and
closed-world compilation feature, not as a general-purpose open-world trait
object ABI.

## Packages, Imports, and Exports

This document specifies the initial surface syntax for packages, imports, and
exports in Silk. The semantics are intentionally minimal for now and will be
extended as the compiler’s resolver and linker mature.

### Notes



- `package <path>;` declarations (with the module ordering rules below).
- `module <path>;` declarations (mutually exclusive with `package`) including
 `module ... as <Interface>;` conformance checking.
- Inline module declarations (`module Name { ... }` / `export module Name { ... }`)
 for nested namespaces.
- A contiguous top-level `import` block (package imports and `from "..."` module
 specifier imports).
- Named re-exports: `export { Name, Other as Alias };` (exports an in-scope value
 name so other modules may import it).
- Package imports (`import std::strings;`) that make a package’s exported values
 available for use in the importing source file.
- Package-import aliasing (`import std::window;`, `import std::strings as str;`)
 that binds a namespace alias for qualified access (`window::is_supported`, `str::eq`).
- Qualified symbol imports (`import std::strings::Builder;`, `import std::io::println;`,
 `import ::malloc;`) that bring a single symbol into scope without importing the
 entire package namespace.
- Module-specifier imports (`import { Name } from "...";`, `import ns from "...";`)
 including:
 - relative file imports (`from "./file.slk"`),
 - std package specifier imports (`from "std/strings"`; a trailing `.slk` is
 accepted for compatibility and stripped before package lookup),
 - dependency-rooted POSIX module specifiers (`from "logger/lib"`) resolved
 through `[dependencies]` in the importing package manifest; dotted
 dependency keys such as `my.dep.b` match quoted paths such as
 `"my/dep/b"` by longest prefix,
 - and unquoted package-path specifiers (`from ns_pkg::subpath`).
- Default exports (`export default fn ...` and `export default Name;`) and default
 imports that bind either:
 - the default-exported symbol, or
 - the module namespace when no default export exists.
- Declaration-only exported function prototypes (`export fn name(...) -> T;`)
 for header-style “prototype modules” that describe an exported surface without
 providing a body (satisfied by link-time definitions from other Silk sources
 and/or `.o`/`.a` inputs).

Not implemented yet:

- Bulk re-exports (“export from ...”) and forwarding of export surfaces.
- A stable, fully specified “package build” system outside the current CLI/module-set
 model (see `Package Manifests (`silk.toml`)` for current manifest support).

Working examples (recommended to read alongside this doc):

- Package imports: [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/), [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/), [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- File imports (named + default): [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/),
 [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/),
 [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- Package-path imports (`from pkg::name`): [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/),
 [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)

When an import fails, the relevant error codes live in [`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/)
(notably `E1001`–`E1006`, plus `E2003`/`E2004` for invalid imported names).

### Terminology

- **Source file**: a single `.slk` source file.
- **Package**: a named collection of source files that share a namespace (declared via
 `package ...;`).
- **Module declaration**: a `module ...;` header that declares a namespace and a
 **compile-time-only** module value, and may declare interface conformance via `as`.
- **Module set**: the set of source files the compiler is compiling together for a
 given command. Package imports can only resolve to packages that exist in this
 module set.
- **Named import**: `import { A, B as C } from "...";` (introduces unqualified names).
- **Default import**: `import X from "...";` (binds either a default export symbol or
 a namespace, depending on what is imported).
- **Namespace import**: a default import that binds a module or package namespace; you
 access its members as `X::Name`.

### Packages

A Silk program is organized into packages and source files.

Each source file may declare the package it belongs to using a `package`
declaration at the top of the file:

```silk
package my_app::core;
```

Rules:

- Each module MAY declare at most one `package` declaration.
- When present, the `package` declaration MUST appear before all other
 top-level declarations in the module; it is the first declaration in
 the file.
- Package names are sequences of identifiers separated by `::`.
 - As a special case, the keyword `task` is permitted as a `::`-qualified
 segment so [`std::task`](/silk/docs/std/task/) is a valid package name.
 - [`std::strings`](/silk/docs/std/strings/)
 - [`std::task`](/silk/docs/std/task/)
 - `my_app::core`
 - `example`
- The standard library lives under the reserved `std::` namespace, for
 example [`std::strings`](/silk/docs/std/strings/), [`std::memory`](/silk/docs/std/memory/), etc.

If a source file omits a `package` declaration, it is treated as belonging to an
implementation-defined default package (for example, the “main” package for
an executable). The exact rules for default packages will be specified as
multi-module builds are implemented.

In the current `silk` CLI implementation, when building a package via a package
manifest (`silk.toml`), source files that omit `package` default to the
manifest’s `package.name`. See `Package Manifests (`silk.toml`)`.

### Modules (`module`)

`module` declares a named module namespace and a **compile-time-only** module
value.

Syntax:

```silk
module my_app::core;
module my_app::core as SomeInterface;
```

Rules:

- A source file MAY declare at most one `module` declaration.
- A source file MAY declare at most one of:
 - a `package` declaration, or
 - a `module` declaration.
- When present, the `module` declaration MUST appear before all other top-level
 declarations in the source file; it is the first declaration in the file.
- Module names follow the same `::`-qualified naming rules as packages.
- Modules are **compile-time-only** values: there is no runtime representation
 for a module value.
- If a module declares `as <Interface>`, the compiler MUST validate that the
 module satisfies the interface surface as specified in
 `Interfaces`.

#### Inline modules (`module Name { ... }`)

In addition to the source file header form (`module ...;`), Silk supports
**inline modules** as a nested-namespace mechanism inside a file:

```silk
package my_package;

export module inner_module {
  export fn hello () -> string {
    return "hello world";
  }
}
```

Rules :

- Inline modules MUST appear at top level (not inside function blocks).
- The inline module name is a single identifier.
- The body is a brace-delimited list of top-level declarations; inline modules
 may be nested.
- `package`, header-form `module ...;`, and `import` declarations are not
 permitted inside an inline module body.
- Declarations inside an inline module are referenced from outside using `::`
 qualification (`inner_module::hello()`).
- Within an inline module body, direct nominal declarations in that inline
 module (`type`, `struct`, `enum`, `error`, `interface`) are available through
 unqualified type lookup:
 - `impl User { ... }`, `new User()`, `User{ ... }`, and `type Alias = User`
 resolve `User` to the current inline module before falling back outward.
 - other inline-module declarations still require explicit `::` qualification
 in Silk currently.
- `export module Name { ... }` exports the namespace:
 - exported declarations inside it become part of the containing package’s
 export surface with their names prefixed by `Name::` (for example
 `inner_module::hello`),
 - exported type aliases keep the same prefixed type name surface (for
 example `users::UserId`) and may appear in exported function parameter and
 result types,
 - nested `export module` declarations extend the prefix (for example
 `outer::inner::name`).
- Imported namespace qualifiers preserve exported inline-module prefixes. For
 example, after `import app as a;`, exported members of
 `export module users { ... }` in package `app` are available as
 `a::users::Name`; after `import lib from "./lib.slk";` binds a file
 namespace, the same exported inline-module member is available as
 `lib::users::Name`.

### Source File Header Ordering (Mandatory)

In each source file, top-level declarations must appear in this order:

1. Optional `package` or `module` declaration (`package ...;` or `module ...;`).
2. Zero or more `import` declarations, as a contiguous block.
3. All other top-level declarations.

This ordering is enforced by the parser/resolver and keeps dependency structure
easy to understand and tooling-friendly.

### Imports

Source files may refer to other packages or modules via `import` declarations:

```silk
package my_app::core;

import std::strings;

fn main () -> int {
  return 0;
}
```

Rules:

- `import` declarations MUST appear at top level (not inside functions or
 blocks).
- All `import` declarations in a module, if any, MUST appear after the
 optional `package` declaration (if present) and before any other kind of
 top-level declaration. In other words, imports form a contiguous block at
 the beginning of the module immediately following the optional package.
- An `import` path is a sequence of identifiers separated by `::`, matching
 the package naming rules above (including the [`std::task`](/silk/docs/std/task/) special case).
 - As with expression/type qualified names, an import path MAY start with `::`
 to explicitly name the global namespace (the unnamed package).
- `import` declarations identify dependencies and bring exported symbols
 from the imported package into scope in the importing module, subject to
 the visibility rules below.
- Currently:
 - importing a package makes its exported `let` bindings with explicit
 type annotations visible as ordinary, unqualified names in the
 importing module (for example, `import util;` followed by `answer`
 refers to `util::answer` when `util` exports `let answer: int = 42;`),
 - imported exported `let` bindings are also reachable via qualified
 names of the form `pkg::name` (for example, `util::answer` after
 `import util;`); both unqualified (`answer`) and qualified
 (`util::answer`) forms are accepted for now, but the qualified form
 reflects the intended package-namespaced style,
 - exported functions (`export fn`) are callable across packages for the
 compiler’s current backend subset:
 - within a package, top-level functions form a shared namespace across
 all modules in that package (so functions in one module may call
 functions defined in another module of the same package),
 - when a module imports a package, that package’s `export fn`
 declarations become callable from the importing module,
 - both unqualified (`foo()`) and qualified (`util::foo()`) call forms
 are accepted initially for imported exports, matching the current
 constant-import behavior, though the qualified form reflects the
 intended package-namespaced style,
 - this callable subset is limited to the compiler’s current
 code generation subset (supported parameters/results, direct calls, and
 structured control flow supported by the IR→ELF backend on
 `linux/x86_64`),
 - struct type names (`struct` declarations) from imported packages are visible in the importing module for the supported `struct` subset:
 - the qualified form `pkg::Struct` is always accepted when `pkg` is imported,
 - the unqualified form `Struct` is accepted when it is unambiguous across the module’s imports and does not conflict with a locally defined struct name,
 - when multiple imported packages define the same struct name, the unqualified form is rejected as ambiguous and the qualified form must be used,
 - enum type names (`enum` declarations) from imported packages are visible in the importing module for the supported enum subset:
 - the qualified form `pkg::Enum` is always accepted when `pkg` is imported,
 - the unqualified form `Enum` is accepted when it is unambiguous across the module’s imports and does not conflict with a locally defined type name,
 - enum variants are referenced relative to the enum name (`Enum::Variant` or `pkg::Enum::Variant`),
 - unqualified type names are introduced only by:
 - local declarations in the current package, and
 - explicit imports (package imports and module-specifier named imports).
 The checker does not resolve an unqualified type name by scanning every
 package in the module set for a “unique match”.
 - if an imported package does not exist, resolution fails before
 type-checking (see the resolver).
 - a leading `::` on a qualified name forces lookup in the **global namespace**
 (the unnamed package), bypassing any same-named declarations in the current
 package or imported packages. This is intended as an explicit escape hatch
 for shadowing (for example, calling `::malloc(...)` when the current module
 also defines or exports `malloc`). The prefix is valid in both expression
 and type positions, including:
 - values: `::malloc(...)`,
 - types and struct literals: `::Foo` and `::Foo{...}`,
 - enum variant paths/patterns: `::E::Variant`.

#### Package import aliasing (`import pkg;` / `import pkg as alias;`)

In addition to importing exported symbols, a package import also binds a
**namespace alias** that can be used for qualified access:

```silk
import std::window;

fn main () -> int {
  if window::is_supported() {
    return 0;
  }

  return 1;
}
```

Rules :

- The default alias is the **last segment** of the imported package path:
 - `import std::strings;` binds `strings`
 - `import std::runtime::mem;` binds `mem`
- You may override the alias with `as`:

  ```silk
  import std::strings as str;
  ```

- If the alias would conflict with an existing in-scope name, the import is a
 compile-time error; use `as` to choose a different alias or use the fully
 qualified package path (for example [`std::strings::eq`](/silk/docs/std/strings/)).

#### Qualified Symbol Imports

In addition to importing whole packages, a module may import a single symbol by
fully qualifying it:

```silk
import std::io::println;
import std::url::URL;
import ::malloc;
```

Semantics:

- If the import path matches a package name present in the module set, it is a
 **package import** (`import std::io;`).
- Otherwise, it is treated as a **qualified symbol import**:
 - the compiler finds the longest package-name prefix of the path,
 - the remaining suffix is the symbol name within that package (it may contain
 `::` due to exported inline modules),
 - the symbol is introduced into the importing module under its final path
 segment (for example, `println` for `import std::io::println;`).
- When the import path begins with `::`, the symbol is resolved from the global
 namespace (the unnamed package) and is not subject to package export gating.

`import { Name } from "...";` remains the preferred form when you need to rename
imports (`as`) or import from a file path.

Global namespace (`::name`) rules :

- The global namespace is the package formed by modules that have **no**
 `package ...;` or header-form `module ...;` declaration (their package name is
 empty).
- `::Name` resolves `Name` from that global namespace, if a matching declaration
 exists in the current module set.
- `::Outer::Inner::Name` resolves `Outer::Inner::Name` from that same global
 namespace (for example, names nested under inline modules in a global module).
- Global names are only accessible via the explicit `::` prefix; there is no
 implicit prelude import of the unnamed package.
- Separately, when the standard library is enabled (the default), the compiler
 provides a small implicit std prelude of selected standard symbols (for
 example `Result` and the [`std::interfaces`](/silk/docs/std/interfaces/) interface names) as specified by
 [`std::runtime::globals`](/silk/docs/std/runtime-globals/). Use `--nostd` to disable this behavior.

Future extensions may introduce bulk re-exports and more fine-grained import
forms beyond the current package/file-path import surface. Such features will
be documented here before they are implemented.

#### Example: a two-module package program

Two modules can share a package name and export symbols for other packages to
use.

```silk
// util.slk
package util;

export let answer: int = 41;

export fn add1 (x: int) -> int {
  return x + 1;
}
```

```silk
// app.slk
package app;

import util;

fn main () -> int {
  // Currently, both unqualified and qualified access are
  // accepted after importing a package. Prefer the qualified form to make the
  // origin explicit.
  if util::add1(util::answer) != 42 {
    return 1;
  }
  return 0;
}
```

#### Package imports resolve against the module set

A **package import** resolves only if the package exists in the current module
set.

This matters most when you use package-path specifiers (`from ns_pkg`) or when
you expect a package import to find a package that is not otherwise present.

Tooling note (the `silk` CLI):

- The language semantics are still “imports resolve against the module set”.
 The CLI grows the module set by loading additional source files.
- In addition to auto-loading `std::...` packages from the stdlib root, the CLI
 MAY load non-`std::` packages from a **package search path** when an unquoted
 package path is imported (for example `import api from my_api;`).
- The package search path is configured via `SILK_PACKAGE_PATH` (PATH-like:
 roots separated by `:` on POSIX).
- A package name like `my_api::core` maps to the filesystem candidate
 `<root>/my_api/core/silk.toml`. The first matching manifest in search order is
 used.
- Qualified imports that include extra `::` segments (e.g. `my_api::core::Thing`)
 are treated as qualified symbol imports: the CLI resolves the **longest**
 package prefix that exists (`my_api::core`, then `my_api`) and loads that
 package into the module set.

Example: bringing a package into the module set via a file import, then importing
the package namespace:

```silk
// main.slk
import { answer as ignored } from "./support_pkg_ns_pkg.slk"; // declares `package ns_pkg;`
import pkg from ns_pkg; // now resolves because `ns_pkg` exists in the module set

fn main () -> int {
  return pkg::add1(pkg::answer);
}
```

If you omit the file import (or otherwise fail to include a module that declares
`package ns_pkg;`), the package import fails with `E1001` (“unknown imported package”).

From the CLI, the usual fix is to ensure the missing package’s module(s) are
part of the command’s module set (for example by passing their `.slk` files to
`silk check` / `silk build`, or by adding a file import). See
``silk` CLI` and `CLI Usage Examples`.

### Import Specifier Imports (JS-style)

In addition to `import pkg::name;` package imports, Silk supports JS-style
import forms that use `from` with either a quoted module path or an unquoted
package path.

The current JS-style forms are:

- Named imports: `import { Name } from "<specifier>";` or
 `import { Name } from package::path;`
- Default imports / namespace imports: `import Name from "<specifier>";` or
 `import Name from package::path;`
- Ambient imports: `import "<specifier>";`

An import specifier string is interpreted in one of three ways:

- **File specifier**: the string begins with `./` or `../`. These imports
 resolve to a module by file path.
- **Std package specifier**: the string begins with [`std/`](https://github.com/oro-computer/silk/tree/master/std/). These imports
 normalize `/` to `::` and resolve against the linked std package surface,
 not by direct file path lookup.
- **Dependency module specifier**: any other string is a POSIX-style module path
 rooted at a dependency key in the importing package's `silk.toml`. For
 example, with `[dependencies] logger = { path = "../logger" }`,
 `from "logger/lib"` resolves to that dependency's `lib.slk` source module.
 If the extension is omitted, `.slk` is appended. A bare dependency key such as
 `from "logger"` resolves to the dependency's default `lib.slk` source module.
 Dotted dependency keys map to slash prefixes, so
 `[dependencies] my.dep.b = { path = "../dep-b" }` makes `from "my/dep/b"`
 resolve to the `lib.slk` source module in `../dep-b`.

When a dependency entry omits `path`, lookup is contextual to the importing
package. Relative `SILK_PACKAGE_PATH` entries, and the default `packages/`
directory when `SILK_PACKAGE_PATH` is unset, are resolved from the importing
package root and then from parent package roots up to the root package of the
current graph. This allows dependency manifests to declare their own nested
dependencies without depending on the command's current working directory.

Quoted import specifiers MUST NOT contain `::`. Use an unquoted package path
such as `from oro::logger` when importing by package namespace.

This mirrors the common JS convention that relative file imports must start
with `./` or `../`. Silk additionally reserves the [`std/`](https://github.com/oro-computer/silk/tree/master/std/) prefix for stdlib
package imports backed by the linked stdlib.

Examples (namespace-style imports):

```silk
import ui from oro::ui;              // package namespace
import helpers from "./helpers.slk"; // file module namespace (if no default export)
import logger from "logger";         // dependency-key root: dependency the implementation

fn main () -> void {
  let opts: &ui::WindowOptions = new ui::WindowOptions();
  helpers::do_something();
  logger::info();
}
```

Example with dependency-key mapping:

```toml
[dependencies]
logger = { path = "../libs/logger", version = "^0.1.0" }
local.math = { path = "../libs/local-math", version = "0.3.0" }
my.dep.a = { version = "^1.2.0" }
my.dep.b = { version = "^2.0.0" }
```

```silk
import logger_file from "logger";            // dependency default source module
import logger_pkg from oro::logger;          // package.name from logger's manifest
import { add } from "local/math";            // local.math -> ../libs/local-math
import my::dep::a::transform;                // package-search package namespace
import my::dep::b::some_function;            // binary package definition + artifact
import { some_function as some_from_defs } from "my/dep/b";

fn main () -> int {
  return logger_file::info()
    + logger_pkg::info()
    + add(20, 1)
    + transform(10)
    + some_function()
    + some_from_defs();
}
```

In this example:

- `logger` is an explicit path dependency whose package may be named
 `oro::logger`; quoted imports use the local dependency key (`"logger"`),
 while unquoted package-path imports use `oro::logger`.
- `local.math` is an explicit path dependency whose quoted import root is
 `"local/math"`.
- `my.dep.a` is found from the package search path as `my/dep/a`.
- `my.dep.b` is found from the package search path as `my/dep/b`; if it ships
 one definition file and a compatible native `[[artifact]]`, the unquoted
 symbol import and exact quoted named import can both be satisfied by that
 package.

#### Ambient imports

An ambient import loads a module into the module set without introducing any
imported names into local scope:

```silk
import "./my_api.slk";
import "std/io";
```

Notes:

- Ambient imports use the same specifier interpretation rules as other
 specifier-based imports:
 - `./` / `../` paths are resolved as file imports,
 - [`std/<path>`](https://github.com/oro-computer/silk/tree/master/std/<path>) is normalized to a std package name,
 - other strings are dependency-rooted POSIX module paths matched against
 `[dependencies]` keys. Dotted keys match slash-separated path prefixes, and
 the longest matching key wins.
- Ambient imports do not bind a namespace or import any symbols. If you need to
 call a function or reference a type from the imported module, use a named
 import, a default import (namespace import), or a package import.
- Ambient imports are useful for declaring dependencies that exist only to:
 - satisfy prototype/definition conformance rules (see below), or
 - ensure a module is present in the module set so its types and methods are
 available for type checking and monomorphization.

#### Named imports

Named imports import selected exported names directly into the importing
module:

```silk
import { StringBuilder, write_u8 as writeByte } from "./runtime.slk";
```

Notes:

- There is no combined `import foo, { bar } from "...";` form in the current grammar.
 Use separate `import` declarations.
- File specifiers should include the `.slk` extension explicitly. `std/...`
 strings are package specifiers rather than file paths.

Rules:

- File imports MUST appear in the same import-declaration block as package
 imports: after the optional `package` declaration and before any other
 top-level declaration.
- The `from` keyword is part of the import syntax.
- The `from` specifier may be either:
 - a string literal (`from "./file.slk"`, `from "std/io"`, `from "logger/lib"`), or
 - a package path (`from std::io;`, `from ns_pkg::sub;`).
- String literal specifiers MUST NOT contain `::`; `::` belongs to package path
 syntax.
- If the specifier is a **file specifier**, it is resolved relative to the
 importing file’s directory. `./` and `../` path segments are permitted.
 Absolute paths and backslash-separated paths are rejected in source imports.
- If the specifier starts with **[`std/`](https://github.com/oro-computer/silk/tree/master/std/)** (`"std/<path>"` or
 `"std/<path>.slk"`), it is a std package specifier. `/` is normalized to
 `::`, and a trailing `.slk` is accepted for compatibility and stripped before
 package lookup. For example, `from "std/io"` resolves package [`std::io`](/silk/docs/std/io/).
- If the specifier is an unquoted **package path**, it is interpreted as a
 package name (using the same `::`-separated syntax as `package` declarations)
 and is resolved via the package graph.
- If the specifier is a quoted **dependency module specifier**, it MUST match a
 dependency key from the importing package manifest. Dot-separated dependency
 keys match slash-separated import prefixes (`my.dep.b` matches
 `"my/dep/b"`), and the longest matching key selects the dependency root. The
 remaining path names a module under that dependency's source-module
 directory; when there is no remaining path, `lib.slk` is used. The dependency
 key is independent of the dependency manifest's `package.name`; quoted
 dependency imports use the key, and unquoted package-path imports use
 `package.name`.
- Binary-only dependency packages may omit implementation sources and ship
 `[package].definitions` plus a compatible native `[[artifact]]`. In that
 case, an exact root specifier such as `from "my/dep/b"` can bind named imports
 from the package's single definition file when the default source module is
 absent; the
 package artifact is then linked automatically by package builds for supported
 targets.
- The imported module MAY declare a `package` or omit it. File specifiers refer
 to the target module *by file path*, not by package name.

Exported names for named imports:

- Named imports can import:
 - exported values: `export fn`, named `export default fn`, `export let`, and
 exported `ext` bindings, and
 - type names: `struct`, `enum`, `error`, and `interface` declarations.
 In Silk currently, type exports are recorded but not fully
 enforced for all type declarations; loading a module into the module set
 makes its type declarations available for type checking when that module’s
 package is imported (and for some fully-qualified uses in monomorphized
 declarations).
 - exported type aliases: `export type ...;`, and
 - exported Formal Silk theories: `export theory` declarations (importable so
 they can be applied via `#theory Name(args);`).
- `impl` blocks do not introduce importable names directly, but loading the
 imported module makes its methods available for method-call checking on the
 corresponding types.

Name binding rules:

- Each entry in the `{ ... }` list names one imported symbol.
- `as` can be used to rename an imported symbol (`Name as Alias`).
 - For values (`fn` / `let` / `ext`), this introduces a value alias.
 - For type names (`struct` / `enum` / `error` / `interface`) and exported
 type aliases (`export type`), this introduces a local `type` alias
 (transparent: it does not create a new type identity).
 - For Formal Silk theories (`export theory`), this introduces a theory alias.
- Imported names are introduced into the importing module as unqualified names
 (matching the existing behavior for package imports).
- Importing an unknown name from a file is an error.
- Importing the same value name from multiple file imports without aliasing is
 an error.
- Importing a value name that is already visible in the module (for example
 via same-package scope or a package import) is treated as a no-op **unless**
 it conflicts with a local declaration in the importing module.
- Importing a type name that is already visible in the module is treated as a
 no-op.

#### Default imports and namespace imports

A module may declare a single *default export* and importing modules may bind
that default export with a JS-style default import:

```silk
// module.slk
package module;

export default fn () -> int {
  return 1 + 2;
}
```

```silk
// main.slk
import foo from "./module.slk";

fn main () -> int {
  let value = foo();
  if (value != 3) {
    return 1;
  }
  return 0;
}
```

Rules:

- Default exports are module-level and are consumed by default imports
 (`import Name from "<specifier>";`).
- A default export may be declared in either of two ways:
 - a default-exported function declaration:
 - `export default fn ...` (the function name is optional only in this form),
 - or a default-export statement:
 - `export default Name;` (names an in-scope symbol in the current module).
- Default exports may target any top-level symbol kind that can be referenced
 by name:
 - functions (`fn`),
 - top-level bindings (`let` / `const` / `var`),
 - external bindings (`ext`),
 - type aliases (`type`),
 - nominal types (`struct`, `enum`, `error`, `interface`),
 - Formal Silk theories (`theory`).
- Each module MAY declare **at most one** default export.
- A default export is distinct from named exports:
 - `export default fn add () -> int { ... }` declares a default export whose
 internal name is `add` within the module,
 - but it does **not** implicitly create a named export of `add` for other
 modules. To export it as a named export, write `export fn add ...` (or add
 an explicit named export form once one exists in the language).
- The function name after `fn` is optional only for default exports. When the
 name is omitted (`export default fn () -> ...`), the function is anonymous in
 the surface language and can only be referenced by importing it via a default
 file import.
- Default imports have two behaviors depending on whether a default export
 exists:

 - If the imported module declares `export default`, the local name binds to
 that default-exported symbol.
 - If the imported module does **not** declare a default export, the default
 import becomes a **namespace import**: the local name refers to the
 imported module’s namespace and its exported names are accessed via
 `foo::Name`.

 In other words: *if there is no explicit default export, the module’s
 namespace is treated as the default export.*

- When a default import binds a default export, it introduces a single
 unqualified name into the importing module:
 - if the default export is callable (a `fn` or an `ext` function), it binds a
 callable value name (`foo()`),
 - if the default export is a type (`struct`/`enum`/`error`/`interface`/`type`),
 it binds a type name usable in type positions (and as the head of struct
 literals),
 - if the default export is a Formal Silk theory, it binds a theory name that
 may be applied via `#theory foo(args...);`,
 - if the default export is a non-callable value (`let`/`const`/`var` or a
 non-function `ext`), it binds a value name.
 When a default import binds a namespace, it does not introduce any unqualified
 imported names; you must use `foo::Name` to access exported names.
- Namespace imports also expose exported inline-module members by keeping the
 inline-module prefix after the namespace name, such as
 `foo::users::make()` and `foo::users::User`.
- Using a namespace import name as a callable (e.g. `foo()`) is an error; add an
 explicit `export default` to the imported module or use a named import.

Package namespace imports:

- For an unquoted **package path** (for example `import ui from ui;`), the default
 import binds the package’s default export when the package declares one.
 Otherwise, it binds a namespace and exported names are accessed via `ui::Name`.

### Exports

Top-level declarations can be marked as exported using the `export`
modifier:

```silk
package my_app::core;

export fn main () -> int {
  return 0;
}

export let answer: int = 42;
```

Rules:

- `export` is not allowed inside blocks; it applies only to module-level
 declarations. Inside `impl` blocks, `public` controls method visibility and
 `export` is reserved for static members.
- The implementation supports `export` on:
 - functions (`export fn ...`), including a declaration-only prototype form
 (`export fn name(...) -> T;`) used for header-style interface modules,
 - `let` and `const` bindings (`export let ...`, `export const ...`).
 - `ext` declarations (`export ext name = ...;`),
 - Formal Silk theories (`export theory Name(...) { ... }`),
 - `type` aliases (`export type Name = ...;`),
 - `struct` declarations (`export struct Name { ... }`),
 - `enum` declarations (`export enum Name { ... }`),
 - `error` declarations (`export error Name { ... }`),
 - `interface` declarations (`export interface Name { ... }`),
 - static members inside `impl` blocks (`impl T { export fn ... }` with no
 `self` receiver).
- The `export` modifier marks a declaration as part of the package’s
 externally visible surface. The exact visibility rules across packages
 (including how exports appear in the resolver and back-end symbol tables)
 will be specified and implemented alongside the package graph in
 [`Compiler Architecture`](/silk/docs/compiler/architecture/).

Currently, most type names are treated as visible across
module boundaries once the relevant module(s) are loaded into the module set.
The `export` modifier is still recorded on type declarations so the
package/export model can be tightened later without changing source.

The checker does not treat “globally unique” unqualified type names as
implicitly imported: if a type name is not introduced by a local declaration or
an explicit import, it is unknown (even if some other package in the module set
defines a type with that base name).

#### Prototype exports (`export fn ...;`)

In addition to ordinary function definitions (`export fn ... { ... }`), a module
may declare a **prototype** (a declaration without a body) by terminating the
signature with `;`:

```silk
module bar;

export fn foo (value: string) -> int;
```

This is the Silk analogue of a C/C++ header prototype or a TypeScript `*.d.ts`
declaration file:

- Other modules may import the prototype (named import or namespace import) and
 type-check calls against its signature.
- The prototype itself does **not** provide an implementation. The symbol must
 be provided at link time by:
 - another Silk source file in the same package that defines `export fn foo ... { ... }`, and/or
 - an object/archive input that defines the symbol (for example a `.o`/`.a`
 produced by a C compiler).
- Prototype declarations may include Formal Silk contract annotations (`#require`
 / `#assure` / contract `#theory` uses). This is the visible contract surface
 for callers; when the implementation is precompiled and the function body is
 not available in the module set, callers still type-check and may verify call
 sites against the prototype’s contract surface.

When both a prototype declaration and a source-level implementation are present
in the same build/module set, the compiler enforces:

- the signatures match, and
- the implementation package explicitly imports the prototype module (via a
 file import) so the relationship is declared in source.

Example (consumer imports the prototype):

```silk
import { foo } from "./ibar.slk";

export fn main () -> int {
  return foo("hello");
}
```

Example (implementation imports the prototype and provides the body):

```silk
module bar;

import "./ibar.slk"; // ambient import; used for conformance only

export fn foo (value: string) -> int {
  return 0;
}
```

This pattern is equivalent in intent to describing the export surface as an
`interface` and declaring module conformance (`module ... as ...`), but it is
file-based and designed to support separate compilation + link-style workflows.

#### Re-export declarations (`export { ... };`)

In addition to `export fn ...` and `export let ...`, Silk supports exporting an
*already in-scope name* via a re-export declaration:

```silk
import { my_function } from "./module.slk";
export { my_function };
```

This is the idiomatic way to build “barrel” modules that forward selected
exports from other modules.

Rules :

- A re-export declaration must appear at top level and ends with `;`.
- Each entry in the `{ ... }` list names a **local** in-scope symbol.
 - The entry may rename the exported name: `export { localName as ExportedName };`.
- Re-exported names are part of the module/package export surface, so other
 modules may import them via `import { Name } from "./barrel.slk";`.
- Currently, `export { ... }` supports values and exported
 Formal Silk theories (`theory` declarations). It does not export type names.

### Notes

The current compiler front-end:

- parses `package` declarations into the AST,
- parses `import` declarations into the AST,
- records whether top-level declarations are marked `export`:
 - values (`fn`, `let`, `ext`),
 - Formal Silk theories (`theory`),
 - type aliases (`type`),
 - type declarations where supported (`error`, `interface`),
 - and similarly tracks `export` for static `impl` members and `export default`
 for top-level functions.

The type checker partially respects `package`, `import`, and `export` today:

- a multi-module helper (`the module-set import helper`) seeds each
 module’s top-level environment with exported `let` bindings (with
 explicit type annotations) from any packages it imports, making those
 constants visible as unqualified names in the importing module,
- within a module set, function calls are type-checked against:
 - all top-level functions in the current package (across all modules of
 that package), and
 - `export fn` declarations from any imported packages,
 while still rejecting calls to non-exported functions across package
 boundaries.

A package-level resolver now exists in `the implementation` and is used by the
ABI build path (`silk_compiler_build`) to:

- group modules into packages (including an implementation-defined default
 package for modules that omit `package`),
- ensure that every `import` refers to a package that exists in the current
 module set,
- reject cyclic package graphs (e.g. `package a` importing `b` while `b`
 imports `a`).

Resolver errors are surfaced through `libsilk.a` as human-readable
errors (for example, `"unknown imported package"` or
`"cyclic package imports"`), and are covered by both Zig tests and C99
tests under `the C ABI test harness`.

In addition to the package graph, the resolver also builds per-package
export tables:

- for each package, all `export fn` and `export let` declarations are
 collected into a symbol list,
- duplicate exported names within the same package are rejected, except for the
 prototype/definition pairing described above (`export fn name(...) -> T;` +
 `export fn name(...) -> T { ... }`), which is accepted only when the
 signatures match,
- these export tables are currently used only for consistency checks; the
 type checker does not yet use them for cross-package name resolution.

Future work (tracked in `the implementation plan`) will:

- extend the resolver and checker to:
 - map imports to concrete modules and exported symbols,
 - ensure only exported symbols are visible across package boundaries,
- propagate package and export information into the IR and back-end so that
 symbol visibility and linkage match these rules.

### Common Pitfalls

- **Forgetting semicolons**: `package` and `import` declarations end with `;` (parse error, `E0001`).
- **Imports not at the top**: imports must come immediately after the optional
 `package` declaration and before any other top-level declaration (`E0001`).
- **Assuming package imports find code automatically**: a package import can only
 resolve if the package exists in the module set (fix by adding the relevant `.slk`
 files to the build, or by file-importing them; missing packages are `E1001`).
- **Calling a namespace import**: if `import foo from "./mod.slk";` binds a namespace
 (because there is no default export), then `foo()` is invalid; use `foo::Name` or
 add `export default` (`E2018`).
- **Name collisions with named imports**: when importing from multiple modules, use
 `as` to rename one binding (`E2004`).

## Optional

The [`Optional`](/silk/docs/language/optional/) type provides a safe way to represent values that may or may not
be present, instead of relying on sentinel values such as `null`.

- The nominal type constructor is `Option(T)`.
- The shorthand `T?` is sugar for `Option(T)` and is the recommended form.
- Optional values are constructed using `Some(...)` and `None` (the compiler
 also accepts `none` as an alias of `None`).
- The `null` literal is distinct from `None`, but may coerce to `None` when an
 optional type is expected.
- Use [`match`](/silk/wiki/language/flow-match/), `?.` (optional chaining), and `??` (coalescing) to consume optionals.

### Declaring Optional Types

You can declare variables or fields as optional using either:

- `T?` (idiomatic suffix form),
- `Option(T)` (nominal form).

The language design treats these as equivalent.

Implementation

- The type system (`the implementation`) models optional types, and the parser
 now accepts both:
 - the suffix form `T?` in type annotations, and
 - the nominal form `Option(T)` for simple cases (a single type argument),
 which is desugared into the same internal optional representation as
 `T?`.
 - For example, the following is valid today and type-checks successfully
 (note that the current compiler requires `let` initializers; see
 [`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2015`):

    ```silk
    fn main () -> int {
      let a: string? = None;
      let b: Option(string) = None;
      return 0;
    }
    ```

- The current `linux/x86_64` IR→ELF backend subset now supports a first slice
 of optional *values* for a subset of payload types:
 - construct optionals via `None` and `Some(value)`,
 - access fields of optional structs via optional chaining
 (`opt?.field`, producing a `FieldType?` value),
 - call methods on optional structs via optional chaining
 (`opt?.method(args)`, producing a `ResultType?` value),
 - use nested optionals (`T??`) for a subset of payloads in the current backend
 (see below),
 - compare supported optionals via `==` / `!=` (tag + payload equality; nested
 optionals compare recursively),
 - unwrap optionals via `??` (coalescing) with short-circuit evaluation of
 the fallback expression,
 - explicitly branch on optionals via the [`match`](/silk/wiki/language/flow-match/) expression (see
 ``match` Expression (and Statement)`),
 - and pass/return such optionals between helpers in the supported IR
 subset.

 Supported optional payloads in this backend subset include:

 - scalars (`bool`, `char`, `f32`, `f64`, `int`, and fixed-width integers),
 - `string` (lowered as `{ ptr: u64, len: i64 }`),
 - enums (tagged unions) in the current enum backend subset (lowered as `(u64 tag, payload_0, payload_1, ...)`),
 - and the supported `struct` subset (0+ fields of supported value types,
 including nested structs and optionals; see [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)).

 In this subset, optionals are represented at IR boundaries as a `Bool` tag
 followed by the payload scalars: `(Bool tag, payload0, payload1, ...)` where
 `tag=0` means `None` and `tag=1` means `Some(...)`. The payload scalar slots
 follow the same lowering rules as the underlying non-optional type (1 scalar
 for scalar payloads, 2 scalars for `string`, N scalars for the current `struct`
 subset, and N scalars for enums (including the enum’s own `u64` tag slot).

 Nested optionals (`T??`) are supported in this backend subset for the same
 payload subset (scalars, `string`, enums, and the supported `struct` subset).

 In this subset, `T??` is represented as an outer optional whose payload is
 the full inner optional representation: for example `int??` lowers as
 `(Bool tag0, Bool tag1, i64 payload)`.

- Not yet implemented:
 - optional chaining beyond the current optional-struct field access and
 optional method call subsets (for example chaining through optional fields
 and optional indexing),
 - [`match`](/silk/wiki/language/flow-match/) over non-optional scrutinee types (and richer pattern forms beyond
 `None`/`Some(...)`),
 - and richer optional forms beyond the backend.

Note: optional payload equality (`==` / `!=`) is still limited in the current
backend subset; comparisons against `None` are supported broadly, but full
payload equality for all optional payload kinds (notably optional-of-enum) is
still evolving.

For the current C ABI mapping of optionals in exported function signatures
within the supported backend subset, see `C99 ABI and `libsilk.a`` and
`External Declarations (`ext`)`.

### Creating Optional Values

An optional can be:

- `None` — the empty state.
- `Some(value)` — the value‑holding state.

Examples from the spec:

- `let age: u32? = None;`
- `let age: u32? = Some(30);`
- `struct User = { profile: None };`
- `profile: Some({ email: "some@example.com", age: Some(30) })`

The compiler infers the optional’s element type from context when possible.

Equality comparisons provide optional type
context for `None` / `Some(...)` operands, so forms like `opt == None` and
`opt == Some(value)` type-check when `opt` has type `T?`.

### `None`: The Empty State

`None` represents the absence of a value.

Spelling note: `None` may also be written as `none` (alias). The `null` literal
is a distinct literal that can coerce to `None` in optional contexts.

Key points:

- `None` can be assigned to any `T?`; its concrete `T` is inferred.
- In pattern matching and control flow, `None` corresponds to the empty branch.

### `Some(value)`: The Value-Holding State

`Some(value)` wraps a concrete value in an `Option(T)`.

Key points:

- The type of `Some(value)` is `T?` (or `Option(T)`).
- Nested optionals are allowed (e.g. a struct containing fields that are `T?`).

### Optional-Coalescing Operator `??`

The `??` operator unwraps an optional by providing a fallback value if it is `None`.

From the spec:

- It “coalesces” the optional’s value and the default into a single, non‑optional result.
- The expression `opt ?? default_value` has type `T` when `opt` has type `T?`.
- When `opt` has type `T??`, the expression `opt ?? default_value` has type
 `T?` (it unwraps one optional layer).
- It composes naturally with optional chaining.

Example:

- `let email_address: string = user2.profile?.email ?? "no-email-provided@domain.com";`

Scope note:

- `??` is primarily the optional-coalescing operator.
- The same token is also used for recoverable `Result`-like values:
 `result ?? fallback` yields the `Ok(...)` payload or the fallback for
 `Err(...)`.
- The same token is also used for ordinary named enums with exactly two
 declared variants:
 - if the first declared variant is unit, `value ?? fallback` yields that enum
 value,
 - if the first declared variant carries exactly one payload, it yields that
 payload,
 - and if the value is the second declared variant, `fallback` is evaluated.
- The right-hand side may also be one of the narrow terminal control-flow
 forms accepted only after `??`:
 - `value ?? return expr`
 - `value ?? break`
 - `value ?? continue`
- These forms keep the same validity rules as their statement counterparts:
 - `return` must be valid in the enclosing function and type-check against its
 result type,
 - `break` and `continue` are only valid inside loops.
- This is still a narrow rule for coalescing. It does not make `return`,
 `break`, or `continue` general expressions elsewhere in the language.
- The optional and recoverable-result forms are distinguished by the left-hand
 operand type; expression [`match`](/silk/wiki/language/flow-match/) remains the more general payload-aware tool
 when you need explicit names, multiple payload elements, or more than two
 states.

Examples:

```silk
fn read_port () -> int {
  let port: int = maybe_port() ?? return 80;
  return port;
}

fn drain () -> int {
  let mut seen: int = 0;
  loop {
    let value: int = next_value() ?? break;
    seen = value;
  }
  return seen;
}

fn scan (values: int?[]) -> int {
  let mut found: int = 0;
  for item in values {
    let value: int = item ?? continue;
    found = value;
  }
  return found;
}
```

### Using Optional Values

The spec provides several mechanisms for working with optionals:

- Optional chaining `?.`:
 - `user.profile?.email` yields `string?`.
 - If any link in the chain is `None`, the result is `None`.
 - Optional method calls are also supported:
 - `user.profile?.email_len()` yields `int?`.
 - When the receiver is `Some(v)`, the call evaluates as `Some(v.email_len())`.
 - When the receiver is `None`, the call evaluates as `None`.
- Coalescing `??`:
 - Converts an optional into a non‑optional by supplying a default.
- Explicit checking via [`match`](/silk/wiki/language/flow-match/):
 - Pattern‑matching on `Some(...)` / `None` to handle both cases explicitly.

### Optional combinators (methods)

In addition to [`match`](/silk/wiki/language/flow-match/), `?.`, and `??`, the compiler provides a small set of
combinator methods on optional values (`T?`). These are designed to feel
familiar to Rust developers while preserving Silk’s explicit move/cleanup rules
([`Memory Model (Stack, Heap, and Moves)`](/silk/docs/language/memory-model/)).

Supported methods (Supported forms):

- `opt.is_some() -> bool`
- `opt.is_none() -> bool`
- `opt.map(f) -> U?` where `f: fn(T) -> U`
- `opt.and_then(f) -> U?` where `f: fn(T) -> U?`
- `opt.or_else(f) -> T?` where `f: fn() -> T?`
- `opt.unwrap_or(fallback) -> T` (eager; `fallback` is evaluated before the call)
- `opt.unwrap_or_else(f) -> T` where `f: fn() -> T` (lazy; called only for `None`)

Notes:

- `??` remains the idiomatic lazy fallback operator because the fallback is an
 ordinary expression and is evaluated only for `None`.
- `unwrap_or` is eager by design; use `unwrap_or_else` (or `??`) when the
 fallback is expensive.
- `map`/`and_then` call the callback only for `Some(...)`.

Example (`map` + `and_then`):

```silk
import std::result;

fn parse_port (s: string) -> std::result::Result(int, int) {
  return Ok(123);
}

fn main () -> int {
  let maybe: string? = Some("8080");
  let port_opt: int? = maybe.and_then(fn (s: string) {
    return parse_port(s).ok_value();
  });
  return port_opt.unwrap_or(0);
}
```

### Compiler Requirements

The compiler must:

- Support `T?` and `Option(T)` as equivalent surface forms.
- Ensure that `Some` / `None` usage is type‑correct.
- Track optionality in the type system and enforce checks when unwrapping.
- Implement `?.` and `??` with the short‑circuit semantics described above.
- Support [`match`](/silk/wiki/language/flow-match/) on `Option(T)` and integrate optionals with flow control and error reporting.

## Errors

This document summarizes the Silk error-handling model at a level suitable for compiler implementation. It is based on the language design captured in this specification (optionals, verification, `ext`, ABI).

For unrecoverable logic bugs and contract violations, Silk uses **typed
errors** (`error`, `panic`, and `T | ErrorType...`), specified in
`Typed Errors (`error`, `panic`, and `T | ErrorType...`)`.

### Notes

- Typed errors are implemented end-to-end for the current front-end and the
 `linux/x86_64` backends (see `Typed Errors (`error`, `panic`, and `T | ErrorType...`)`).
- `assert` is implemented:
 - in release builds, a failed assertion traps immediately,
 - in debug builds on `linux/x86_64` (`silk build --debug` / `-g`), a failed
 assertion prints a panic header, an optional message, and a stack trace
 before aborting.
- In `silk test` builds, failed assertions record a test failure and execution
 continues (the test process exits non-zero when failures were recorded). See
 `Testing`.

### Design Goals

- Error signaling is explicit and typed (no hidden global error state).
- Error paths are part of normal control flow, not out-of-band exceptions.
- The verifier can reason about both success and error paths symmetrically.
- The C99 ABI must be able to represent error outcomes in a stable, documented way.

### Recoverable Errors (Recommended Pattern)

Silk distinguishes between:

- **Recoverable errors** (invalid user input, I/O failures, parse failures): model
 these as normal values, typically using [`std::result::Result(T, E)`](/silk/docs/std/result/) or an
 optional (`T?`).
- **Typed errors** (`T | ErrorType...` + `panic`): reserved for unrecoverable
 contract violations and logic bugs that should not be silently ignored (see
 `Typed Errors (`error`, `panic`, and `T | ErrorType...`)`).

#### Example: Recovering from URL parse errors

[`std::url`](/silk/docs/std/url/) exposes a recoverable parsing API ([`std::url::parse`](/silk/docs/std/url/)) that returns a
tagged result ([`std::url::URLResult`](/silk/docs/std/url/)), so callers can report an error and keep
going without aborting.

A runnable example that wraps `URLResult` into [`std::result::Result`](/silk/docs/std/result/) and parses
all command-line arguments is in:

- `an example program`

### Error Representation

From the overall language design:

- Silk favors explicit types such as:
 - optionals (`T?` / `Option(T)`) for “may be present / may be absent” values.
 - domain-specific error types (enums or structs) for richer error reporting.
- Functions that can fail should surface that in their type signatures:
 - either by returning a value that encodes both success and error (e.g. an optional or a nominal error-aware type),
 - or by returning an error-only type where success is absence of error.

The naming and shapes of error-carrying types are defined by this language spec and by standard library APIs, but the compiler must:

- treat them as regular, first-class types,
- enforce that callers handle them appropriately (e.g. via pattern matching, explicit checks).

### Interaction with Control Flow

Error-aware types integrate with control flow constructs:

- `if` / [`match`](/silk/wiki/language/flow-match/) can be used to branch on error vs. success cases.
- Pattern matching can destructure enum-based error types, exposing error codes or payloads.
- Optionals (`T?`) can be used where “absence” is a common error shape; they compose with `?.` and `??` to keep code concise while still explicit.

The compiler must:

- ensure that branches that depend on error conditions are type-checked,
- support exhaustiveness checks when matching on error enums/types.

### Verification and Errors

Formal Silk constructs (`#require`, `#assure`, `#assert`, `#invariant`, `#variant`, `#monovariant`) apply equally to:

- success paths (e.g. postconditions describing the returned value),
- error paths (e.g. guarantees about when and how certain errors can occur).

The verifier should be able to:

- treat error-carrying types as ordinary values with invariants,
- prove that certain errors cannot happen given preconditions,
- or, conversely, require explicit handling of error cases when the proof cannot eliminate them.

### ABI and FFI Considerations

On the C99 side:

- Error values exposed through `libsilk.a` should use well-defined C types (e.g. enums or structs) documented in `C99 ABI and `libsilk.a``.
- For external functions declared via `ext`, any error behavior must be captured in the Silk-side function type and corresponding C signature (e.g. error-return codes, nullable pointers, or explicit error structs).

The compiler must:

- preserve error-related information across the FFI boundary,
- avoid implicit, hidden error channels (such as untracked global error codes) in favor of explicit parameters or return values.

### Assertions (`assert`)

`assert` is a debugging/safety construct intended to catch programmer mistakes.
It is **not** part of Silk’s typed error model and is not a replacement for
returning optionals or `Result(...)`.

Syntax (initial):

- `assert <Expr>;`
- `assert (<Expr>, <message>?);`

Rules:

- The condition expression must type-check as `bool`.
- The optional message, when present, must type-check as `string`.

Runtime behavior :

- By default (release builds), if the condition evaluates to `false`, execution
 traps immediately (a panic-like abort). In the current `linux/x86_64` backend
 this is implemented as an invalid-instruction trap.
- In debug builds (`silk build --debug` / `-g`) on `linux/x86_64`, a failed
 assertion prints a panic header, the optional message (when present), and a
 stack trace to stderr when available (via glibc `backtrace_symbols_fd`)
 before aborting.

Notes:

- Failed assertions are currently isolated by the `silk test` runner (each
 test runs in its own process). Future work may allow reporting failed
 assertions without process isolation (for example by lowering `assert` to a
 typed error in test contexts).
- See also: `Testing`.

## Typed Errors (`error`, `panic`, and `T | ErrorType...`)

Silk’s typed error system exists to eliminate the “trust gap” between a
function’s signature and its real behavior. There are no hidden exceptions and
no implicit panic channel: if a function can terminate due to a logic bug /
contract violation, it must say so in its signature, and the compiler must
enforce it.

This document specifies the surface syntax and checker rules for typed errors.


The compiler supports `error` declarations, `panic` statements, error-aware
return types (`T | ErrorType...`), and the [`match`](/silk/wiki/language/flow-match/) *statement* form for handling
typed errors (including the Terminal Arm Rule), plus the postfix `?`
propagation operator for error-producing calls.

### Overview

- An `error` represents an unrecoverable logic bug or contract violation.
- A function that can `panic` must declare that in its return type using `|`:
 - `fn get_at(xs: u8[], index: int) -> u8 | OutOfBounds;`
- A typed error is triggered with `panic`, which terminates the current function
 and propagates the error to the caller.
- Typed errors are handled explicitly via [`match`](/silk/wiki/language/flow-match/) (statement form), and any arm
 that handles an error must end in a terminal statement.

This model is intentionally closer to “typed, explicit non-local errors” than
to try/catch exceptions or an implicit panic mechanism.

#### Recoverable errors are values (not typed errors)

Typed errors are intentionally *not* the primary mechanism for routine runtime
failures such as:

- invalid user input,
- parsing failures,
- I/O failures.

Those should typically be modeled as ordinary values using [`std::result::Result`](/silk/docs/std/result/)
or optionals (`T?`) so callers can handle them and continue normal execution.

See:

- [`Errors`](/silk/docs/language/errors/) (overview),
- [`std::result`](/silk/docs/std/result/) (recoverable `Result(T, E)`),
- [`std::url`](/silk/docs/std/url/) and `an example program`
 (recoverable URL parsing example).

For ergonomic one-branch recovery over recoverable values, prefer `if let`,
`let ... else`, `while let`, or the recoverable `??` operator on optionals,
`Result` values, and other two-variant success/fallback enums rather than
`is_err()` / `is_none()` plus a second extraction step.

### Declaring Error Types (`error`)

Syntax:

```silk
error OutOfBounds {
  index: int,
  len: int
}
```

Rules:

- `error Name { ... }` declares a nominal, struct-like type that represents an
 unrecoverable logic bug / contract violation.
- An `error` declaration has the same field rules as `struct` in the current
 compiler subset (scalar fields; see
 [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)).
- An `error` type may also be used as data (returned, stored, logged) when it is
 *not* part of a `T | ...` error contract.

Implementation

- The compiler treats `error` as a distinct nominal type category (separate from
 `struct`) but reuses the same field/layout rules in the Supported forms.

### Error-Producing Function Signatures (`T | ErrorType...`)

A function declares that it may `panic` by adding one or more error types after
its success type using `|`.

Examples:

```silk
fn get_at(xs: u8[], index: int) -> u8 | OutOfBounds { ... }
fn parse() -> Frame? | FrameTooLarge { ... }
fn init() -> void | InitFailure { ... }
```

Note on `|` disambiguation:

- In function declarations, an unparenthesized `|` sequence after `->` is
 always parsed as a typed-error contract.
- To return a **union type** from a function, parenthesize the union:
 - `fn f () -> (A | B);`
 - `fn g () -> (A | B) | SomeError;`

See `Type Unions (`T1 | T2 | ...`)` for union types.

Rules:

- The leftmost type is the single *success* type.
- Each type on the right side of `|` must name a declared `error` type.
- The list of error types in a signature is the complete contract: the
 implementation may not `panic` with any other error type.

Implementation notes:

- The current compiler models typed errors as a distinct “error set” attached to
 the function signature and to expressions that may `panic`.
- The success type is still a normal Silk type (including optionals).

### Triggering a Typed Error (`panic`)

Syntax:

```silk
panic OutOfBounds {
  index: index,
  len: len
};
```

In a real implementation, the `len` value typically comes from the relevant
container/view (for example a `.len()` method via [`std::interfaces::Len`](/silk/docs/std/interfaces/)).

Rules:

- `panic` constructs a value of the named `error` type and immediately
 terminates the current function, propagating the error to the caller.
- A `panic X { ... };` statement is only legal inside a function whose signature
 includes `| X` (directly or indirectly via propagation).

Implementation notes:

- `panic` is a statement (not an expression) in Silk currently.

### Propagating Typed Errors (`?`)

The postfix `?` operator propagates a typed error from an *error-producing call
expression* to the caller without requiring an explicit [`match`](/silk/wiki/language/flow-match/) at every call
site.

Syntax:

```silk
let value: T = error_call(...)?;
```

Semantics:

- If the call succeeds, `call()?` evaluates to the call’s success value.
- If the call panics with a declared error type, `call()?` immediately returns
 from the current function, propagating the same error to the caller.

Rules:

- `?` is only legal inside a function that declares an error contract
 (`-> SuccessType | ErrorType...`).
- The callee’s error set must be a subset of the enclosing function’s error set.
 Otherwise the call must be handled explicitly with a [`match`](/silk/wiki/language/flow-match/) statement that
 maps the error into the caller’s contract.
- `?` is only meaningful on an error-producing call expression (a call whose
 signature includes `| ErrorType...`). Applying `?` to an infallible call is a
 type-check error.

Implementation notes:

- In the current compiler, `call()?` is lowered as “call + tag dispatch; on
 error return the appropriate error payload; on success yield the value”,
 using the same encoding as the [`match`](/silk/wiki/language/flow-match/) statement lowering.

#### Async Calls and `await`

Typed-error propagation already composes with async calls in the current
subset, but the fallible expression is still the async **call** rather than the
`await` operator.

For example:

```silk
error OpenFailed {
  code: int,
}

async fn open_value () -> int | OpenFailed {
  return 1;
}
```

In the Supported forms:

- `open_value()` is treated as an error-producing call whose success value is
 `Promise(int)`.
- `let p: Promise(int) = open_value()?;` is valid inside a caller whose error
 contract includes `OpenFailed`.
- `let v: int = await open_value()?;` is valid and is the supported shorthand
 when the caller wants the resolved success value directly.
- `await open_value()` without `?` or an explicit [`match`](/silk/wiki/language/flow-match/) on `open_value()` is
 rejected with `E2023`.

If you need explicit handling, match on the async call and await the promise in
the success arm:

```silk
match (open_value()) {
  p => {
    let v: int = await p;
    return v;
  },
  err: OpenFailed => {
    panic OpenFailed { code: err.code };
  }
}
```

### Handling Typed Errors ([`match`](/silk/wiki/language/flow-match/) statement + Terminal Arm Rule)

When the scrutinee expression of a [`match`](/silk/wiki/language/flow-match/) statement may `panic` (i.e. its
signature includes `|`), the compiler activates a special rule for error arms.

#### Match statement form

```silk
match (create_frame(user_size)) {
  Some(frame) => {
    io::println("ok");
  },
  None => {
    io::println("no frame");
  },
  err: FrameTooLarge => {
    log::critical("invalid frame size requested", err);
    std::abort();
  }
}
```

#### Terminal Arm Rule

If the scrutinee expression has an error contract (`T | ErrorType...`), then
for any arm that matches an `error` type, the arm’s block must end with a
terminal statement.

Terminal statements are:

- `panic <ErrorType> { ... };` (propagate or map to another error)
- `std::abort();`
- `std::halt();`
- `std::reboot();`

Implementation notes:

- `std::abort()` is lowered as a terminal action:
 - in the native backend subset, this is routed through the platform
 `abort()` so the process terminates with `SIGABRT`,
 - in non-debug builds on `linux/x86_64`, the compiler disables core dumps
 (`prctl(PR_SET_DUMPABLE, 0, 0, 0, 0)`) before calling `abort()` to keep abort fast,
 - on backends/targets where `abort()` is unavailable, it is lowered to the
 backend’s `Trap` primitive.
- `std::halt()` and `std::reboot()` are currently lowered to `Trap` in the
 native backend subset.

This rule is intentionally *context-dependent*: it is triggered by the error
contract of the scrutinee expression, not by the fact that a type is declared
with `error`.

#### Error types as data (no Terminal Arm Rule)

If a function returns an `error` type as a normal value (no `|` in its
signature), the special rule does not apply:

```silk
fn inspect_issues() -> FrameTooLarge;

match (inspect_issues()) {
  err: FrameTooLarge => {
    log::warn("non-critical issue", err);
    // Allowed to complete normally because the scrutinee is not a `T | ...`.
  }
}
```

#### [`match`](/silk/wiki/language/flow-match/) statements over Result-like values (recoverable)

The [`match`](/silk/wiki/language/flow-match/) **statement** form can also be used to destructure common
recoverable result shapes such as [`std::result::Result(T, E)`](/silk/docs/std/result/).

When the scrutinee expression is a **call expression** whose result type is
either:

- [`std::result::Result(T, E)`](/silk/docs/std/result/) (an `enum` with `Ok(T)` and `Err(E)` variants), or
- a “Result-like” struct with fields:
 - `value: T?`
 - `err: E?` where `E` is an `error` type,

then the checker accepts binder patterns of the form:

- `name => { ... }` / `_ => { ... }` for the success payload (binds `name` as `T`),
- `err: E => { ... }` for the error payload (binds `err` as `E`).

The Terminal Arm Rule does **not** apply in this form because the scrutinee is
not a `T | ErrorType...` typed-error expression; the error is a normal returned
value.

Runtime invariant (struct form, current backend): exactly one of `value` and
`err` must be `Some(...)`. If the invariant is broken, execution traps.

Implementation notes:

- The current compiler supports a match subset for optionals as an
 *expression* (`match x { None => expr, Some(v) => expr }`).
- The [`match`](/silk/wiki/language/flow-match/) expression also supports `Ok(...)` / `Err(...)` patterns for
 `Result` values (see ``match` Expression (and Statement)`).
- Typed error handling uses the *statement* form of [`match`](/silk/wiki/language/flow-match/) with block arms.

### Restrictions

#### `pure fn`

`pure fn` must not introduce or handle typed errors:

- `pure fn` may not have a `|` in its return type.
- `pure fn` may not contain `panic` statements.

The checker enforces these rules in Silk currently (see
[`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/)).

#### `ext` boundary

Typed errors must not cross the external boundary. External shims must translate
typed errors into:

- explicit error return codes,
- nullable pointers / optionals,
- explicit error structs/enums,
- or a terminal action appropriate for the platform.

the current compiler rejects `ext` declarations whose function types use
`|` (and rejects exported C ABI surfaces with `|`) in the current
implementation.

### Related proposals

- Open/variadic error sets for higher-order adapters (`E...`).
- `return <error>` as shorthand for `panic <error>` (AP131).

## Mutability

Mutability in Silk is “safe by default”: values are immutable unless explicitly marked mutable under clear rules using the `mut` keyword.

- All local bindings are immutable (read‑only) by default.
- `const` bindings are always immutable (there is no `const mut`).
- All function parameters are immutable (read‑only) by default.
- A **value** parameter may be declared `mut` to allow reassignment of the
 parameter binding inside the callee (this does not affect the caller).
- A borrowed reference parameter (`&T`) follows a two‑part `mut` borrow
 contract:
 - the parameter is declared `mut`, and
 - the call site uses `mut <expr>` to explicitly create a mutable borrow.
- A slice parameter (`T[]`) is a non-owning view; when the callee intends to
 mutate through a slice view, it also follows a two-part contract:
 - the parameter is declared `mut`, and
 - the call site uses `mut <expr>` to explicitly pass a mutable slice view.

This two‑part system makes mutation explicit and intentional.

### Local Mutability (`let mut`)

Local bindings introduced with `const` and `let` are immutable by default. To
allow a local binding to be updated, it must be declared with `let mut` (or
`var`, which is an alias for `let mut`):

```silk
fn main () -> int {
  let mut x: int = 0;
  x = 1;
  x += 2;
  return x;
}
```

Key rules:

- Only `let mut` bindings may appear on the left-hand side of an assignment.
- Pattern binders follow the same rule. Refutable forms such as
 `let mut Some(v) = maybe else { ... };`, `if let mut Some(v) = maybe { ... }`,
 `while let mut Some(v) = maybe { ... }`, chained `&& let mut ...`, and
 `for let mut ... in ...` introduce mutable payload binders.
- `move` is independent from `mut`: `let move Some(v) = maybe;`,
 `let move Some(v) = maybe else { ... };`, `if let move ...`,
 `else if let move ...`, and `while let move ...` consume the scrutinee for
 ownership-tracked values, while
 `let move mut Some(v) = maybe else { ... };` and
 `let mut move Some(v) = maybe else { ... };` also make `v` assignable.
 For simple mutable bindings, `var move value = source;`,
 `var mut move value = source;`, and `var move mut value = source;` consume
 `source` during initialization when `source` requires ownership tracking and
 then introduce `value` as assignable. The explicit `mut` after `var` is
 redundant, but accepted for modifier-order symmetry.
- The left-hand side must refer to an existing binding (an lvalue).
- The type checker enforces that the assigned value’s type matches the binding’s type.

### The Principle: Safe by Default

Example from the spec:

```silk
fn read_runner(r: &Runner) {
  // This is OK:
  io::print("Points: {}", r.point);

  // This would be a compile-time error:
  // r.point = 5;
}
```

Key points:

- Borrowed references (`&T`) are read‑only unless explicitly declared `mut`.
- Attempts to mutate through a non‑mutable reference are compile‑time errors.

### Granting Permission to Mutate

To make mutation possible **through a borrowed reference**, `mut` is used both:

- **In the function definition**, to declare that the function intends to mutate:

  ```silk
  fn reset_runner(mut r: &Runner) {
    r.point = 0;
  }
  ```

- **At the call site**, to explicitly pass a mutable argument, acknowledging that the callee is allowed to modify it (syntax defined in the language reference).

The compiler uses this to:

- encode a clear contract that the function may modify its argument,
- ensure callers are consciously opting into mutation.

### Compiler Requirements

The compiler must:

- Enforce immutability by default for parameters and references.
- Require `mut` at both the declaration and call site for mutable borrows.
- Surface clear diagnostics when mutation is attempted without proper `mut` markings.
- Integrate mutability rules with regions, buffers, and concurrency:
 - disallow patterns that would lead to data races,
 - ensure that aliasing and lifetime rules are respected when mutation is allowed.

### Current Implementation Restrictions

Silk currently implements:

- Local `let mut` bindings, including assignment and numeric compound assignment.
- `mut` value parameters (`fn inc(mut x: int) { x = x + 1; }`) as a callee-local
 mutable binding (no call-site `mut` marker is required).
- Borrowed reference parameters for:
 - `&Struct` for the supported `struct` subset, and
 - `&T` where `T` is a single-slot scalar primitive (for example `&int`,
 `&bool`, `&u64`, `&f64`).
- The two-part `mut` borrow contract for mutable reference parameters:
 - parameter declared `mut` (e.g. `fn bump(mut p: &Pair)`), and
 - call site uses `mut <expr>` (e.g. `bump(mut pair)`).
- Field updates through both:
 - local `let mut` struct bindings (`pair.a = 1`, `pair.b += 2`), and
 - `mut` borrowed reference parameters (`p.a = 1`, `p.b += 2`).
 Nested field updates (`cfg.theme.status_bg = ...`) are supported for scalar
 leaf fields in the backend.
- Local borrowed references (`&T`) as first-class values:
 - via the borrow operator `&expr` on borrowable lvalues (e.g. `&pair`, `&obj.field`, `&x`), and
 - via implicit borrow coercions in contexts that expect `&T`
 (currently implemented for `&Struct`; for example `let r: &Pair = pair;`).
 These borrows are checked with conservative **lexical lifetime** rules (they
 may not escape the scope of the borrowed stack storage).
- Local bindings of `&Struct` values that originate from heap allocation (`new`)
 or from calls that return `&Struct`:
 - these `&Struct` values are refcounted in the Supported forms,
 - copying a `&Struct` binding (e.g. `let g: &File = f;`) creates an alias to
 the same underlying heap allocation and increments the refcount.

### Borrow Safety Rules

Borrowed references (`&T`) in Silk currently are safe-by-default
and, for now, use conservative **lexical lifetime** checks:

- Borrowed references can be created and stored as local values (see above).
- The callee can mutate a borrowed reference only when:
 - the parameter is declared `mut`, and
 - the caller uses `mut <expr>` at the call site.
- Mutable borrows must be explicit and must originate from a borrowable lvalue:
 - borrowing a local binding requires a writable base (`let mut`) or an
 already-mutable view, and
 - field borrows follow the same rule (the base must be writable).

Slice views (`T[]`) are also call-scoped and safe-by-default:

- A slice value is a non-owning view (pointer + length) and may alias other
 slice views into the same underlying storage.
- Slice range borrows are created via:
 - `&a[start..end]`
 - `&a[..end]`
 - `&a[start..]`
 - `&a[r]` where `r: range` (including `..=` inclusive ranges)
- A mutable slice view is created via `mut &a[...]` and is restricted:
 - the base must be a borrowable lvalue (a name or a field-access chain rooted
 at a name), and
 - the base storage must be writable (`let mut` for fixed arrays / structs, or
 an already-mutable view such as a `mut` borrowed reference parameter), or
 already a mutable slice view.
- A function parameter of slice type may be declared `mut` to allow mutation
 through the slice view, and requires the caller to pass a mutable slice view
 using `mut <expr>`.
- When a slice value is stored in a struct field (`xs: T[]`), the stored view’s
 mutability is tracked:
 - storing `&a[...]` stores a read-only view, and
 - storing `mut &a[...]` stores a mutable view.
 A call-site `mut <expr>` marker does not upgrade a read-only stored view into
 a mutable one; passing a field as `mut` requires that the field already holds
 a mutable view.

#### Aliasing Restrictions (Per Call)

Within a single call expression, the compiler enforces conservative aliasing
rules to avoid creating multiple mutable views of the same storage:

- A given binding may be mutably borrowed at most once in a single call.
- A binding may not be both mutably and immutably borrowed in the same call.
- Multiple immutable borrows of the same binding are permitted.

For slice parameters (`T[]`), these same per-call aliasing restrictions apply.
Additionally, when both borrows are slice range borrows of the same base with
integer-literal bounds, the checker permits multiple mutable borrows in the same
call when it can prove the two ranges are disjoint (including when the slices
are first bound to locals and then passed by name).

When borrowing a range from an existing slice binding (for example `s: T[]`),
the checker interprets `&s[start..end]` as a subrange of the underlying base
(offset by `s`’s known bounds) for the purposes of overlap checks. This
disjointness reasoning is currently limited to integer-literal bounds and to
slice bindings whose own bounds are known.

For `&Struct` reference-typed local bindings and slice-typed (`T[]`) local
bindings, the compiler also tracks obvious aliasing introduced by copying and
ref “shape casts”:

- Copying a `&Struct` binding produces an alias (it refers to the same storage).
- Copying a slice binding (`T[]`) produces an alias (it refers to the same underlying storage).
- Casting `&S` to `&T` via `as` under the shape-cast rules produces an alias
 (it is a retyped view of the same storage).
- The per-call aliasing restrictions apply across aliases: within a single call
 expression, you may not take multiple mutable borrows (or both mutable and
 immutable borrows) of the same underlying reference, even if they are held
 under different local names.

Example (rejected):

```silk
fn swap(mut a: &Pair, mut b: &Pair) {
  // ...
}

fn main () -> int {
  let mut p: Pair = Pair{ a: 1, b: 2 };
  swap(mut p, mut p); // error: two mutable borrows of `p` in one call
  return 0;
}
```

Example (allowed, immutable):

```silk
fn sum2(a: &Pair, b: &Pair) -> int {
  return a.a + a.b + b.a + b.b;
}

fn main () -> int {
  let p: Pair = Pair{ a: 1, b: 2 };
  return sum2(p, p); // OK: multiple immutable borrows
}
```

### ABI Notes (External Boundaries)

At external declaration boundaries (`ext`), borrowed-view types are restricted:

- opaque handle references (`&Opaque` / `mut &Opaque`) are allowed,
- ordinary references (`&T`) are rejected,
- and slices (`T[]`) are rejected.

This keeps exported signatures ABI-stable and prevents Silk borrows from
escaping into foreign code. See the ABI and struct layout docs for the current
rules.

## Memory Model (Stack, Heap, and Moves)

This document specifies Silk’s intended memory model: how values are allocated,
passed, and how (future) heap-managed values interact with the type system.

Silk currently implements a
minimal heap model for `new` on `linux/x86_64` and a small lexical
move/cleanup model for droppable values:

- `new` is supported for allocating non-opaque `struct` values on the heap and
 producing an `&Struct` reference.
- These heap allocations are managed via reference counting (RC) inserted by the
 compiler during lowering.

Regions and a richer move/borrow model remain design-in-progress. See
[`Regions`](/silk/docs/language/regions/), [`Borrow Checking (Static Alias and Lifetime Safety)`](/silk/docs/language/borrow-checker/), and `the implementation status`
for current scope.

### Goals

- Make allocation behavior explicit and predictable.
- Prefer stack allocation for most local data.
- Prevent unsafe implicit lifetime extension (for example implicitly “moving”
 stack data into a longer-lived heap allocation).
- Keep borrow safety a compile-time property (no runtime borrow errors in the
 safe subset).

### Stack vs Heap

#### Stack allocation (default)

Rule: values created without `new` are stack values by default.

- Locals hold their data directly (for example an `int` or a small POD `struct`).
- Passing to functions is **by value**. For ownership-tracked values, this is a
 move (the source binding is consumed); for plain scalars it behaves like a
 copy.
- Initializing a new binding from a name (for example `let y = x;`) and
 assignment from a name (for example `y = x;`) also consume `x` when the value
 type requires ownership tracking (for example `Drop` types and task/promise
 handles). After the move, using `x` is rejected by the checker.
- `let move` / `var move` are the binding-level ownership-transfer spellings.
 For a simple binding, `let move y = x;` and `var move y = x;` consume `x`
 under the same ownership-tracking rules as `let y = move x;`. `mut` may be
 combined with `move` in either order: `let mut move y = x;`,
 `let move mut y = x;`, `var mut move y = x;`, and `var move mut y = x;`.
 The explicit `mut` after `var` is redundant because `var` is already mutable,
 but it is accepted for consistency. Copyable existing sources remain
 independent copies. For destructuring, `let move (a, b) = pair;`,
 `let move Some(value) = maybe;`,
 `let move Some(value) = maybe else { ... };`, `if let move ...`,
 `else if let move ...`, chained `&& let move ...`, and `while let move ...`
 request consuming pattern binding.
- Lifetime is lexical (ends when the scope ends).

This aligns with Silk currently, which is value-oriented and does
not implement a general heap allocation model.

#### Heap allocation (`new`) and boxed values

Rule: values created with `new` live on the
heap and are represented as an `&Struct` reference in user code.

- The reference value is passed by value (copying the reference representation).
- The underlying allocation’s lifetime is managed by compiler-inserted reference
 counting (RC) for values originating from `new`.

Important: this is currently an internal Silk-managed heap for Silk code, not an
FFI pointer model. The compiler does not permit `&Struct` for
non-opaque structs in `ext` signatures; only `&Opaque` handles may cross the
FFI boundary (see [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/) and
`External Declarations (`ext`)`).

##### Thread safety and `task` boundaries

`new` produces an `&Struct` reference whose lifetime is managed by
compiler-inserted reference counting (RC). In the Supported forms, RC retain and
release operations are not atomic, so sharing such references across OS threads
is unsafe.

Because `task` concurrency runs on OS threads, the checker rejects non-opaque
reference types (`&T`) at `task fn` / `async task fn` boundaries (`E2037`). This
includes `&Struct` values produced by `new` (and any other non-opaque
references).

For `async fn` results, the checker also rejects borrowed-view types that could
outlive the caller across suspension:

- non-opaque references (`&T`),
- and slices (`T[]`).

Opaque handle references (`&Handle` where `Handle` is declared as `struct Name;`)
remain permitted because they are treated as external handles rather than as
borrowed views into ordinary Silk-managed storage.

To share state across tasks, transfer ownership by value, share explicit
atomic/synchronized handles ([`std::sync`](/silk/docs/std/sync/) or [`std::atomic`](/silk/docs/std/atomic/)), pass non-owning
`*Borrow` handle views across tasks, or use [`std::sync::Arc(T)`](/silk/docs/std/sync/) when task-safe
shared ownership is required.

Atomic operations do not change the `new` reference model. [`std::atomic`](/silk/docs/std/atomic/)
provides atomic cells for their own storage; it does not make ordinary
compiler-managed references or their RC operations atomic.

[`std::sync::Arc(T)`](/silk/docs/std/sync/) is a separate shared-ownership tool. Its retain/release
operations are atomic and its final release owns payload destruction, but it
does not upgrade ordinary compiler-managed `new` RC to atomic RC.

##### Notes

- `new` is supported only in function bodies (top-level `let` initializers
 cannot contain `new` in the Supported forms).
- `new` is supported only when the checker can determine a concrete reference
 result type of the form `&Struct`. In the Supported forms, this happens
 in two ways:
 - from an expected type context `&Struct` (for example `let x: &Frame = new
 Frame{ ... };` or as a call argument where the parameter type is `&Struct`)
 - from the `new` operand itself when it names a struct type (for example
 `let x = new Frame{ ... };` or `let x = new Frame(...);`), which allows
 `let` bindings to infer `&Frame` without an explicit annotation
- Only non-opaque `struct` types are supported for `new`.
- Reference counting is applied only to `&Struct` values that originate from
 `new` (borrowed stack references are not treated as RC-managed values).
- The `silk build` CLI supports `--noheap` to disable heap allocation for the
 Supported forms:
 - heap-backed `new` (outside a `with` region) is rejected with `E2027`,
 - `async`/`task`/`await`/`yield` and capturing closures are rejected with `E2027`,
 - `ext` bindings to libc heap primitives (`malloc`/`calloc`/`realloc`/`free`/etc) are rejected with `E2027` in non-stdlib modules,
 - [`std::runtime::mem::{alloc,realloc,free}`](/silk/docs/std/runtime-mem/) traps when called without an active `with` region (no implicit heap fallback),
 - region-backed `new` inside `with` is still permitted.

##### Region-backed allocation (`with` + `region`)

In the Supported forms, `new` may also allocate from a region when an active
region context is established with `with` (see [`Regions`](/silk/docs/language/regions/)).

- Inside `with <region> { ... }`, `new` allocates from the region’s backing
 bytes instead of calling the heap allocator.
- On last-release, region-backed `new` allocations run `drop` (when defined),
 but do not free their backing storage (region memory is not reclaimed by RC).

##### Reference counting rules

- `new` initializes the allocation’s RC cell to `1`.
- Copying an RC-managed `&Struct` binding (for example `let q: &T = p;`) emits an
 RC retain (increment).
- Assigning to an RC-managed `&Struct` binding (for example `p = q;` where `p` is
 a `var`) releases the previous value; when the RHS is an RC-managed binding, a
 retain is emitted before the release to keep self-assignment safe.
- Exiting a scope emits RC releases (decrement) for RC-managed bindings declared
 in that scope, including on fallthrough, `return`, `break`, and `continue`.
- Passing `new` directly as a call argument to a `&Struct` parameter allocates a
 temporary and releases it after the call completes.
- When an RC release decrements the count to `0`, the allocation is freed.

### Destructors (`Drop`)

In Silk currently, Silk supports deterministic cleanup for
resource-owning `struct` values via [`std::interfaces::Drop`](/silk/docs/std/interfaces/).

A `struct` type is considered “droppable” when it provides a method with this
surface signature (usually via an interface impl):

```silk
import std::interfaces;

impl T as std::interfaces::Drop {
  public fn drop (mut self: &T) -> void { ... }
}
```

Automatic invocation :

- **Scope exit:** values are dropped when they go out of scope (including via
 fallthrough, `break`, and `continue`).
- **Return:** on `return`, the compiler drops all in-scope droppable bindings
 except any value moved into the return result (for example `return value;`
 and `return Some(value);` treat `value` as moved in the Supported forms).
- **Overwrite:** assigning to an existing value drops the old value before the
 new value is copied in.
- **Heap last-release:** for `new` allocations managed by compiler-inserted RC,
 `drop` is called before freeing the backing allocation when the refcount
 reaches zero.

Notes and limitations:

- `drop` is resolved statically (no dynamic dispatch).
- Values that require deterministic cleanup should be treated as
 ownership-tracked:
 - consuming a binding moves it and suppresses scope-exit cleanup for that
 binding,
 - using a moved binding is rejected by the checker,
 - explicit ownership transfer may be written as `move <name>` (see
 [`Borrow Checking (Static Alias and Lifetime Safety)`](/silk/docs/language/borrow-checker/)).

### No Implicit Heap Promotion

Planned rule: stack values cannot be implicitly promoted to heap-managed
storage. Any promotion must be explicit and must perform a copy.

This avoids accidental lifetime extension and makes performance characteristics
obvious.

The precise syntax for “heap-copy this value” is still under design; any
proposed surface form must be written down in [`Formal Grammar Spec`](/silk/docs/language/grammar/) before
it is implemented.

### Closure Captures

Silk supports capturing closures as a subset of function values.

Representation:

- A function-typed value is a small pair: `{ func_ptr, env_ptr }`.
- `func_ptr` is a pointer to the closure code.
- `env_ptr` is either `0` (non-capturing) or a pointer to a heap-allocated
 environment box that stores captured values.

Calling convention:

- When `env_ptr == 0`, an indirect call behaves like a normal function-pointer
 call: `func_ptr(user_args...)`.
- When `env_ptr != 0`, the backend passes `env_ptr` as a hidden first argument
 to the closure function: `func_ptr(env_ptr, user_args...)`.

Environment allocation and lifetime (Supported forms):

- Captures are by-value copies of **scalar** locals/parameters (`int`, fixed
 width ints, `bool`, `char`, `f32`, `f64`, `Instant`, `Duration`).
- The environment box begins with a `u64` refcount header, followed by the
 captured scalar fields in a stable order.
- Copying a closure value retains the environment (increments refcount) when
 `env_ptr != 0`.
- Dropping a closure value releases the environment (decrements refcount) when
 `env_ptr != 0`; when the refcount reaches zero the environment box is freed.

Limitations:

- Capturing non-scalar values (including `string`, structs, arrays/slices,
 optionals, and `Drop` types) is rejected in the Supported forms.
- Captures are immutable snapshots; the Supported forms does not support
 capturing by reference or mutating captured state.

### Relationship to Borrowing and Mutability

- Borrow checking is intended to be a compile-time property in the safe subset:
 invalid borrows should be rejected statically.
- See [`Mutability`](/silk/docs/language/mutability/) for the current implemented borrow rules
 (call-scoped aliasing checks for `&T` parameters in the Supported forms).
- See [`Borrow Checking (Static Alias and Lifetime Safety)`](/silk/docs/language/borrow-checker/) for the broader planned borrow checker.

## Borrow Checking (Static Alias and Lifetime Safety)

This document specifies Silk’s intended borrow-checking model for references.

Silk currently implements:

- call-scoped alias checks for mutable borrows (including slice range borrows),
- lexical lifetime checks for slice and reference borrows (no escaping borrows
 of stack locals),
- and a small explicit ownership-transfer form (`move`) used by the checker and
 lowering to prevent accidental double-drops in the safe subset.

### Goals

- Prevent use-after-free and data races in safe code.
- Make mutation explicit and intentional.
- Reject invalid borrows at compile time (no runtime borrow errors required for
 safe code).
- Keep diagnostics actionable (highlight the borrow origin, conflicting use,
 and suggest a fix).

### Notes

Today, the language subset implemented by the compiler supports only:

- call-scoped borrow alias checks for:
 - borrowed reference parameters (`&T`, `mut p: &T`), and
 - slice parameters (`T[]`, `mut s: T[]`) and slice range borrows
 (`&base[start..end]`, `mut &base[start..end]`, and `&base[r]` / `mut &base[r]`
 where `r: range`).
- first-class borrowed `&T` values created from borrowable lvalues:
 - `&expr` (borrow operator) for borrowable lvalues, for:
 - the supported `&Struct` subset, and
 - `&T` where `T` is a single-slot scalar primitive (for example `&int`,
 `&bool`, `&u64`, `&f64`).
 - implicit borrow coercions in contexts that expect `&T` are currently
 implemented for `&Struct` (for example `let r: &Pair = pair;`).

Additionally, the subset implements **lexical lifetime checks** for both slice
borrows and borrowed `&T` values so obvious use-after-scope cases are rejected
(for example returning a slice borrowed from a local fixed array, or returning
`&T` borrowed from a local struct binding).

The currently shipped subset therefore already includes borrowed views in the
positions that matter most for day-to-day code:

- local `&T` and `T[]` bindings,
- local `T?` bindings whose payload is a borrowed `&T` or `T[]`,
- struct fields and enum payloads that carry borrowed views,
- assignments through fields and mutable reference parameters,
- and whole-value returns / assignments that are checked against lexical escape
 rules even when the borrowed view is carried through an aggregate.

### Lexical Lifetimes

Slices (`T[]`) are non-owning views. Slice range borrows create slices that
point into existing storage:

- `&base[start..end]` creates a slice view whose lifetime is tied to `base`.
- `&base[r]` creates a slice view whose bounds are defined by the `range` value
 `r` (see `Types`).
- When borrowing a range from an existing slice binding `s: T[]`, the borrow’s
 underlying origin is `s`’s origin (sub-slicing does not extend lifetime).

Lexical lifetime rules enforced by Silk currently:

- A slice value that ultimately borrows from a **local fixed array binding**
 (`T[N]`) may not escape that binding’s lexical scope.
 - Returning such a slice from a function is rejected.
 - Assigning such a slice into outer-scope storage is rejected (including via
 field assignment and via mutable reference parameters).
- The same rule also applies when that borrowed slice is wrapped in `T?`
 (`Some(&xs[...])` does not allow the local borrow to escape).
- The same rule also applies when that borrowed slice is carried inside a
 struct field or enum payload; returning or assigning the aggregate does not
 allow the local borrow to escape.
- Returning a slice is permitted when the returned slice ultimately borrows
 from a **function parameter** (for example returning a sub-slice of a `T[]`
 parameter).

These rules are intentionally conservative, but they are the complete lexical
lifetime model for the currently supported language subset.

### Lexical Reference Lifetimes

Borrowed `&T` values that ultimately reference **stack storage** may not escape
that storage’s lexical scope. This includes:

- returning a borrowed `&T` that points to a local stack binding (struct or
 single-slot scalar),
- returning such a borrow wrapped in `T?`,
- returning such a borrow carried inside a struct field or enum payload,
- and assigning such a borrowed reference into outer-scope storage.

Returning a reference is permitted when the returned `&T` ultimately refers to
an input reference parameter (that is, storage owned by the caller), and not to
stack locals.

When multiple input references or slices are in scope, no explicit lifetime
label syntax is required in the current language. A returned borrowed view may
refer to any caller-owned input borrow that reaches the return expression
through the supported control-flow forms. If any path introduces a local stack
or fixed-array origin, the lexical escape check still rejects the return.

### Local Mutation While Borrowed

Silk currently also rejects direct mutation of ordinary local
storage while a borrow of that same storage remains live.

This applies to:

- whole-binding assignment (`x = ...`) when `x` is a local stack value or local
 fixed array,
- field assignment (`x.f = ...`) into a local aggregate that is still borrowed,
- and index assignment (`xs[i] = ...`) into a local fixed array that still has
 a live borrowed slice.

In other words, an ordinary local borrow freezes the borrowed local storage
against direct mutation until that borrow ends.

Writes performed through the unique mutable borrow itself remain allowed. For
example, mutation through `mut r: &T` is permitted when it is not competing
with a separate live borrow of the same local storage.

This rule is intentionally local-storage-specific. Borrowed access to
caller-owned or external-handle storage is governed by the existing boundary
rules instead.

### Borrow-Carrying Wrappers and Conservative Control Flow

The current checker also preserves borrow identity through a small set of
wrapper and control-flow forms:

- `Some(<borrow>)` preserves the underlying borrow identity.
- Local `T?` bindings whose payload type is `&T` or `T[]` participate in the
 same local mutation, lexical escape, move, and `await` checks as direct
 borrowed bindings.
- Local named struct / enum bindings whose fields or payloads carry `&T` or
 `T[]` also participate in the same local mutation, lexical escape, move, and
 `await` checks as direct borrowed bindings.
- Refutable-pattern binders also preserve borrow identity when the scrutinee
 already proves a single local borrow origin. In the Supported forms, this
 includes:
 - `if let Some(x) = r { ... }`
 - `let Some(x) = r else { ... };`
 - `while let Some(x) = r { ... }`
 - statement `match (r) { Some(x) => ..., None => ... }`
 where `r: T?` and `T` is a borrowed `&U` or `U[]`.
 - `if let Ok(x) = r { ... }` / `if let Err(x) = r { ... }`
 - `let Ok(x) = r else { ... };` / `let Err(x) = r else { ... };`
 - `while let Ok(x) = r { ... }` / `while let Err(x) = r { ... }`
 - statement `match (r) { Ok(x) => ..., Err(y) => ... }`
 for supported result-shaped enums whose payload type carries a borrow,
 including monomorphized [`std::result::Result(T, E)`](/silk/docs/std/result/) instantiations, and the
 equivalent qualified enum-variant forms such as `State::Ready(x)`.
- `if` expressions preserve borrow identity when:
 - every borrowing branch resolves to the same local origin,
 - or one branch is non-borrowing (`None`, for example) and the other carries
 the borrow,
 - or all borrowing branches are caller-owned inputs.
- [`match`](/silk/wiki/language/flow-match/) expressions preserve borrow identity under the same conservative
 rule:
 - every borrowing arm must resolve to the same local origin,
 - or one or more arms are non-borrowing while the remaining borrowing arms
 resolve to that same origin,
 - or all borrowing arms are caller-owned inputs.

When a borrowed control-flow expression could refer to multiple distinct local
origins, the Supported forms rejects it with `E2122` instead of guessing.

### Boundary Safety

The current compiler also enforces conservative rules at boundaries where a
borrowed view could outlive the storage it refers to.

#### `async fn` boundaries

At an `async fn` boundary, the result type may not contain ordinary
borrowed-view types:

- non-opaque references (`&T`),
- and slices (`T[]`).

This includes such types nested inside structs, enums, optionals, and function
types. The reason is suspension: an `async fn` call returns a `Promise(T)`, so
the eventual result may outlive the stack frame that originally produced the
borrowed view.

Opaque handle references are allowed:

- `&Handle` is permitted when `Handle` is declared as an opaque `struct Name;`.

These are treated as external handles rather than borrow-checked views into
ordinary Silk storage.

Borrowed parameters are permitted in the Supported forms, but the checker also
enforces a conservative async call-site rule:

- an ordinary reference or slice that still resolves to function-local stack
 storage or a local fixed array may not be passed into an `async` call unless
 that call is awaited immediately in the same expression,
- opaque handle references remain allowed because they are not borrow-checked
 views into ordinary Silk storage.

This is the borrow model for the current async subset. Additional async surface
area must define equivalent suspension and escape rules before it lands.

#### External ABI boundaries

At top-level external ABI boundaries, ordinary borrowed views are also
rejected:

- `ext` declarations may not use ordinary references or slices in parameters
 or results,
- unnamed C-facing root-package top-level `export fn` declarations are subject
 to the same rule because they define the compiler’s C-facing symbol surface,
- named-package Silk object exports may accept slice parameters in the
 compiler-owned package ABI; that does not make slices part of the external C
 ABI surface,
- only opaque handle references (`&Handle` where `Handle` is `struct Name;`)
 may cross that boundary.

This rule does not apply to ordinary impl/public methods inside Silk modules;
those remain normal intra-Silk calls.

#### `await` suspension points

At a concrete `await` / `await *` suspension point inside an `async` function,
the checker also rejects live borrowed views that still resolve to ordinary
function-local storage:

- a borrowed reference (`&T`) that still points at a local stack value,
- a slice (`T[]`) that still points at a local fixed array,
- and either of the above when the borrowed view is stored in a local struct
 field instead of a standalone local binding.

This rule is intentionally conservative. It applies only to borrows rooted in
ordinary local Silk storage. The following remain allowed:

- borrowed views rooted in caller-owned storage that have already passed the
 boundary rules,
- and opaque handle references (`&Handle` where `Handle` is an opaque
 `struct Name;`).

The practical rule is: if an `await` may suspend, end any live borrow of local
stack / fixed-array storage before the suspension point.

#### External ABI boundaries

At `ext` boundaries, the same borrowed-view restriction applies:

- ordinary references and slices may not cross the boundary,
- and only opaque handle references (`&Handle` where `Handle` is opaque) are
 permitted by reference.

This keeps Silk’s borrow rules out of the C ABI and avoids exposing non-stable
borrowed layouts to foreign code.

### Ownership Transfer (`move`)

Silk’s safe subset includes a small explicit ownership-transfer form:

- `move <name>`

This expression:

- consumes the binding `<name>` when its type requires ownership tracking
 (for example values that are dropped on scope exit),
- and makes `<name>` unavailable for further use until it is reinitialized
 (for `var`) or permanently (for `let`).

This enables moving values into other values (for example as call arguments or
as the payload of `Some(...)`) without accidentally copying a resource-owning
value and dropping it twice.

In the Supported forms, ownership transfer is intentionally conservative:

- A binding may not be moved while it has any live borrows (reference or slice
 views) in scope, including borrows stored in struct fields.
- A by-value call argument that requires ownership tracking is treated as a
 move, and is rejected when the same binding is also borrowed in that call.
- When a value type requires ownership tracking, binding initialization and
 assignment from a name are also treated as moves:
 - `let y = x;` consumes `x`,
 - `y = x;` consumes `x`.
- `let move` / `var move` are the equivalent binding-level spellings for an
 explicit initialization-time move:
 - `let move y = x;` and `var move y = x;` consume `x` when `x` requires
 ownership tracking,
 - `let mut move y = x;`, `let move mut y = x;`, `var mut move y = x;`, and
 `var move mut y = x;` are accepted combined modifier forms,
 - copyable existing sources still copy, so both bindings remain independent,
 - `let move Some(value) = maybe;`,
 `let move Some(value) = maybe else { ... };`, `if let move Some(value) =
 maybe { ... }`, `else if let move Some(value) = maybe { ... }`, and
 `while let move Some(value) = next() { ... }` consume the pattern scrutinee
 under the same move and borrow checks.

### Completeness

The borrow checker is complete for the currently documented and
regression-tested Silk language subset, including the wrapper and control-flow
forms described above. New language features may still require new borrow
rules, but those are not treated as pre-declared borrow-checker roadmap items.
Any such extension must be specified in [`Formal Grammar Spec`](/silk/docs/language/grammar/) and in this
document before implementation lands, and must be reflected in diagnostics
([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/)) and tests.

## Attributes (`attr(...)`)

Silk supports first-class **attributes** that can annotate declarations and can
also be queried at compile time for conditional compilation.

Attributes come in two forms:

- **Tags**: `attr(one, two, three)`
- **Key/value pairs**: `attr(arch="x86_64", feature="tui")`

Values may be:

- booleans (`true` / `false`)
- integers (numeric literals)
- strings (`"..."` or raw string literals)
- identifiers (treated as a string value, e.g. `abi=c`)

### Notes



Defined in Silk currently:

- `attr(...)` as a prefix annotation on declarations and statements.
- `attr(...)` as a compile-time query expression of type `bool`.
- Comparison operators in `attr(...)` items for numeric toolchain keys:
 - examples: `attr(silk_major>=0)`, `attr(silk_minor>=2)`, `attr(silk_patch=0)`
 - and: `attr(silk_abi_major>=0)`, `attr(silk_abi_minor>=2)`, `attr(silk_abi_patch=0)`
 where `<op>` is one of `=`, `<`, `<=`, `>`, `>=` and `<n>` is an integer literal.
- Declaration gating:
 - when an `attr(...)` annotation contains `arch` / `os` / `target` / `feature`,
 the annotated declaration is included only when the key/value constraints
 match the current build target.
- Conditional compilation:
 - `if <cond> { ... } else { ... }` prunes branches at compile time when
 `<cond>` is an attribute-query boolean expression (built from `attr(...)`,
 `!`, `&&`, `||`, and parentheses).
 - The pruned branch is not type-checked and is not lowered/code-generated.
- `attr(abi=c) fn (...) -> ...` in type positions is accepted as a synonym for
 `c_fn (...) -> ...` (C ABI callback pointer types).
- `export attr(abi=c) fn ...` and `attr(abi=c) export fn ...` select the
 C-facing object symbol spelling for an exported function while preserving
 normal Silk package import/export semantics.
- Task scheduling hints on `task` functions:
 - `attr(task=pool)` / `attr(task="pool")` schedules the task on the global
 task pool (see “Task scheduling” below),
 - `attr(task_pool)` is accepted as a tag-form synonym for `attr(task=pool)`.
 - `attr(task=thread)` / `attr(task="thread")` forces a dedicated OS thread
 for each call instead of the default task-pool schedule.

Not yet fully implemented:

- Objective-C / FFM / WASI-component / other ABI selectors beyond the initial
 `abi=c` support.
- Arbitrary declaration attributes that override the C-visible symbol name,
 such as a future `attr(c_name="...")` design. The implemented
 declaration-level C ABI spelling is limited to `export attr(abi=c) fn` /
 `attr(abi=c) export fn` and derives the C symbol from the Silk namespace.

### Syntax

#### Attribute list

```silk
attr(one, two, debug=false, arch="x86_64", abi=c)
attr(silk_minor>=2, arch="x86_64")
```

Items are comma-separated. A trailing comma is permitted.

#### Attribute operators

An attribute item may be either:

- a tag: `attr(one)`, or
- a key/value item: `attr(arch="x86_64")`.

In the Supported forms, key/value items use one of:

- `=` for string/identifier/bool keys (for example `arch="x86_64"`, `abi=c`),
- `=`, `<`, `<=`, `>`, `>=` for numeric toolchain keys (for example
 `silk_minor>=2`).

#### Annotation form (prefix)

Attributes may prefix most declarations:

```silk
attr(one) fn hello () -> int { return 0; }
attr(feature="tui") struct TTY { /* ... */ }
attr(arch="x86_64", os="linux") interface Builder { /* ... */ }
```

Attributes may also prefix statements inside blocks:

```silk
fn main () -> int {
  attr(one, two) let x: int = 1;
  return x;
}
```

Notes:

- Statement-level attributes are metadata only; use `if attr(...) { ... }` for
 compile-time selection inside blocks.

#### Query form (expression)

`attr(...)` may be used as a boolean expression:

```silk
if attr(arch="x86_64") {
  // compiled only when the target arch is x86_64
} else {
  // compiled otherwise
}
```

Compound expressions are supported:

```silk
if attr(os="linux") && (attr(arch="x86_64") || attr(arch="wasm32")) {
  // ...
}
```

`attr(...)` queries are compile-time only; they are evaluated by the compiler
and do not exist as runtime calls.

### Built-in attribute keys

Silk currently recognizes the following keys in queries and
conditional compilation contexts:

- `arch`: `"x86_64"`, `"aarch64"`, or `"wasm32"`
 - The ARM64 family accepts `"aarch64"` as the canonical spelling, plus the aliases `"arm64"` and `"aarch"` in any letter case.
- `os`: `"linux"`, `"macos"`, `"ios"`, `"android"`, `"windows"`, `"wasi"`, or `"unknown"`
 - `os` comparisons accept those names in any letter case.
- `target`:
 - `"linux-x86_64"`, `"linux-x86_64-musl"`, `"linux-aarch64"`, or
 `"linux-aarch64-musl"`
 - `"macos-x86_64"` or `"macos-aarch64"`
 - `"ios-aarch64"`, `"ios-simulator-aarch64"`, or `"ios-simulator-x86_64"`
 - `"android-aarch64"`
 - `"windows-x86_64"` or `"windows-aarch64"`
 - `"wasm32-unknown-unknown"` or `"wasm32-wasi"`
- `feature`: an enabled feature name (see “Features” below)
- Toolchain version keys (numeric; compare against an integer literal using `=`, `<`, `<=`, `>`, `>=`):
 - `silk_major`, `silk_minor`, `silk_patch`
 - `silk_abi_major`, `silk_abi_minor`, `silk_abi_patch`

### ABI selection (`abi=c`) and `c_fn`

In type positions, `attr(abi=c) fn (...) -> R` is equivalent to `c_fn (...) -> R`.
This is intended for C callback pointer types:

```silk
type InfoCb = attr(abi=c) fn (u64, u64) -> void;
type InfoCb2 = c_fn (u64, u64) -> void; // equivalent
```

On exported function declarations, `attr(abi=c)` selects a C-facing object
symbol spelling:

```silk
export attr(abi=c) fn add_i64 (a: i64, b: i64) -> i64 {
  return a + b;
}
```

`attr(abi=c) export fn ...` is accepted as the equivalent prefix form. The
function remains a normal Silk export, so Silk code imports and calls it by its
package-qualified Silk name. The attribute changes only the emitted object
symbol used by C, Objective-C, Swift, linkers, and dynamic loaders.

Symbol names are derived as follows:

- in the global package, the object symbol is the function name exactly, for
 example `add_i64`;
- in a package or module namespace, Silk namespace separators are collapsed to
 one `_` and the function name is separated from that namespace by one `_`,
 for example package `ui::model` function `add_i64` emits
 `ui_model_add_i64`.

Because this spelling is intentionally clean and C-like, different Silk package
and function names can normalize to the same object symbol. The compiler
rejects `attr(abi=c)` export symbols that collide with another C ABI export or
with any other function symbol emitted for the selected output before object or
library emission. Library outputs validate the root package's exported C ABI
symbols against the dependency functions as they are actually emitted into that
output, including dependency functions that become internal raw symbols rather
than public package-qualified exports.

Declaration-level `attr(abi=c)` currently applies only to top-level exported
functions. A top-level `package` or `module` declaration participates in the
namespace-derived C symbol spelling above, but functions nested inside
`module Name { ... }` inline module blocks are rejected until inline-module C
ABI symbol export is implemented end to end.

The C ABI selection does not relax the supported exported-function ABI rules.
C-facing signatures must still use types that the selected target backend can
marshal at a C call boundary.

### Task scheduling (`task=pool` / `task=thread`)

In the current hosted subset, `task fn` execution is implemented on OS threads.
By default, calling a `task fn` schedules that task on the global task pool.

When a `task fn` (or `async task fn`) is annotated with:

- `attr(task=pool)` (or `attr(task="pool")`), or
- `attr(task_pool)` (tag-form synonym),

the compiler keeps the default global **task pool** schedule for that task.

When a `task fn` (or `async task fn`) is annotated with:

- `attr(task=thread)` (or `attr(task="thread")`),

the compiler spawns a dedicated OS thread for each call instead of using the
global task pool.

The task pool is:

- created lazily on the first pooled task submission,
- backed by OS worker threads,
- Designed as a shared queue-based worker pool (see
 `the implementation`).

#### Configuration

On hosted targets, the worker count defaults to the detected CPU count (clamped
to a small fixed maximum).

You may override it by setting:

- `SILK_TASK_POOL_THREADS=<n>`

to request `n` worker threads (values `<= 0` are treated as `1`; non-numeric
values are ignored and the default is used).

You may also bound queued work by setting:

- `SILK_TASK_POOL_MAX_QUEUED=<n>`

to request at most `n` queued tasks beyond the worker set (`0` or missing means
unbounded). When the queue is full, non-worker submitters block until space is
available; worker threads fall back to inline execution for that submission so
the pool does not deadlock itself.

### Features

Features are named build-time toggles intended for conditional compilation.

In Silk currently, features may be enabled from:

- the CLI (`--feature` / `-F`), and
- package manifests (`silk.toml`):
 - the root package via `[build].features`, and
 - dependency packages via `[dependencies].<dep>.features`.

In `silk.toml`, `[build].features` may be either:

- an array of strings (`["NAME", "NAME=VALUE", ...]`), or
- an inline table (`{ NAME = <bool|int|string>, ... }`).
 - `NAME = true` is equivalent to `NAME` (boolean enabled),
 - any other value is equivalent to `NAME=VALUE`.

Use `attr(feature="name")` in queries and conditional compilation:

```silk
if attr(feature="tui") {
  // code compiled when the build enables the "tui" feature
}
```

#### Feature scoping (package builds)

When building a package graph (via `silk build/check/test --package ...`),
features are **scoped per package**:

- `attr(feature="...")` queries observe only the enabled features for the
 current module’s package.
- Root package features do not implicitly affect dependency packages.

Dependency-scoped features are enabled via the root package manifest’s
dependency entries:

```toml
[dependencies]
ui = { path = "../ui", sha256 = "sha256:...", features = ["tui"] }
```

#### Feature values

Features may optionally carry values. Use `attr(feature="name=value")` to
require a specific value:

```silk
if attr(feature="MY_FEATURE=123") {
  // compiled only when MY_FEATURE is set to 123
}

if attr(feature=enable_this_feature) {
  // compiled only when enable_this_feature is enabled
}
```

Rules (Supported forms):

- Feature specs are of the form `NAME` or `NAME=VALUE`.
 - `NAME` starts with a letter or `_` and may contain letters, digits, `_`,
 and `-`; this permits user-facing names such as `security-provider`.
 - When `VALUE` is omitted, the feature is treated as boolean `true`.
 - When `VALUE` is present:
 - `true` / `false` are parsed as booleans,
 - integer literals (including `0x...` / `0b...` / digit separators) are
 parsed as integers,
 - all other values are treated as strings.
- `attr(feature="NAME")` is `true` when the feature is enabled:
 - boolean features are enabled only when they are `true`,
 - non-boolean-valued features are enabled when present.
- `attr(feature="NAME=VALUE")` is `true` only when the named feature exists and
 its value equals `VALUE` after parsing.

Precedence:

- CLI `--feature` / `-F` entries override manifest-provided feature values of
 the same name.
 - For package builds, unscoped `--feature NAME[=VALUE]` entries target the
 **root package**.
 - You may target a specific package with a namespaced spec:
 `--feature <package>/<spec>` (for example `--feature ui/tui` or
 `--feature ui/tui=false`).
 - Namespaced feature specs are accepted only for package builds (those that
 use `--package`).

- For package builds, multiple manifests in the package graph may request
 features for the same dependency package. If the same feature name is
 assigned multiple different values for a single package, the build fails
 unless a CLI `--feature <package>/<spec>` entry overrides it.

## Atomics



Silk atomics are compiler-backed operations for low-level thread
synchronization. They are distinct from ordinary loads/stores and from
`volatile` memory access:

- ordinary loads/stores are not synchronization operations,
- atomics synchronize between OS threads according to an explicit memory
 ordering,
- `volatile` remains for externally observed memory such as MMIO and must not
 be used as a replacement for atomics.

For ordinary application code, prefer [`std::sync`](/silk/docs/std/sync/) primitives such as mutexes,
condition variables, channels, and cancellation tokens. Atomics are intended
for small low-level coordination patterns such as counters, readiness flags,
once-style state, and cheap cancellation flags.

### Ordering

The public ordering enum is [`std::atomic::Ordering`](/silk/docs/std/atomic/):

```silk
export enum Ordering {
  Relaxed,
  Acquire,
  Release,
  AcqRel,
  SeqCst,
}
```

Meaning:

- `Relaxed` performs an atomic operation without establishing synchronization.
- `Acquire` prevents later memory operations from moving before the atomic
 operation.
- `Release` prevents earlier memory operations from moving after the atomic
 operation.
- `AcqRel` combines acquire and release behavior for read-modify-write
 operations.
- `SeqCst` participates in the single sequentially consistent order for all
 sequentially consistent atomics.

### Operation Rules

Atomic operations have operation-specific ordering contracts:

- `load` accepts `Relaxed`, `Acquire`, or `SeqCst`.
- `store` accepts `Relaxed`, `Release`, or `SeqCst`.
- `swap`, `fetch_add`, and `fetch_sub` accept any `Ordering`.
- `compare_exchange` accepts any success ordering, but the failure ordering
 must not be `Release` or `AcqRel`.
- `fence` accepts any `Ordering`; `Relaxed` is a no-op fence.

Invalid statically visible orderings are rejected by the checker with `E2127`.

```silk
import std::atomic;

fn main () -> int {
  let mut value = std::atomic::AtomicU64.init(1);

  // error[E2127]: atomic loads cannot use Release or AcqRel
  let current = value.load(std::atomic::Ordering::Release);
  return current as int;
}
```

### Thread Safety

Atomic fields are task-safe when the containing type is otherwise task-safe.
This means a struct containing `AtomicBool` or `AtomicU64` can cross a `task`
boundary by value under the same task-safety rules as other structs composed of
task-safe fields.

Copying an atomic value by value copies the atomic storage. It does not create
shared ownership. To share one atomic cell across tasks, keep the owning value
alive in the parent scope and pass the module’s non-owning borrow view across
the task boundary.

`new` references remain non-atomic. The reference counting used for ordinary
`new` allocations is not made thread-safe by this feature. Thread-safe shared
ownership remains a separate future type, such as `Arc(T)`.

### Notes

The current hosted/native subset exposes:

- [`std::atomic::Ordering`](/silk/docs/std/atomic/),
- [`std::atomic::AtomicBool`](/silk/docs/std/atomic/),
- [`std::atomic::AtomicBoolBorrow`](/silk/docs/std/atomic/),
- [`std::atomic::AtomicU64`](/silk/docs/std/atomic/),
- [`std::atomic::AtomicU64Borrow`](/silk/docs/std/atomic/),
- [`std::atomic::fence`](/silk/docs/std/atomic/).

Lowering routes these operations through runtime symbols backed by native
compiler atomic builtins on the hosted POSIX path. They are not lowered as
ordinary Silk field loads or stores.


## Buffers

`Buffer(T)` provides low-level access to a contiguous block of memory. It is
intentionally unsafe and used as a foundation for higher-level collections and
strings.

Key points:

- `Buffer(T)` is a “fat pointer” with:
 - a raw pointer to the start of the memory block,
 - a capacity (number of elements that can be stored).
- `Buffer(T)` does **not** track the number of initialized elements (length).
- `Buffer(T)` uses the current compiler’s **scalar-slot** layout (for example
 `sizeof(u8) == 8`). For packed bytes suitable for OS/FFI byte APIs, use
 [`std::buffer::BufferU8`](/silk/docs/std/buffer/).
- The current API includes operations such as:
 - allocation: [`std::buffer::Buffer(T).init(cap)`](/silk/docs/std/buffer/) / [`std::buffer::alloc(T; cap)`](/silk/docs/std/buffer/)
 - reads/writes: `buf.read(i)` / `buf.write(i, v)` and module-level wrappers
 - views: `buf.view(len)` / `buf.slice(start, end)` returning [`std::arrays::Slice(T)`](/silk/docs/std/arrays/)

Safety model (layered):

- Layer 1: unsafe `Buffer(T)` primitive (`ptr + cap`, no tracked initialization).
- Layer 2: verifier checks (borrow/ownership rules in the language subset).
- Layer 3: Formal Silk proofs (contracts, invariants, and struct requirements).

### Notes



The shipped stdlib provides [`std::buffer::Buffer(T)`](/silk/docs/std/buffer/) as an owning, fixed-capacity
buffer for scalar-slot `T` values, backed by [`std::runtime::mem::{alloc,free}`](/silk/docs/std/runtime-mem/).
The buffer surface is written so it can be used in verified code:

- structural invariants are captured in [`std::formal::buffer_well_formed(ptr, cap)`](/silk/docs/std/formal/),
- bounds checks are expressed via [`std::formal::bounds_i64`](/silk/docs/std/formal/) / `slice_range_i64`,
- and higher-level containers can layer length tracking and element lifecycle
 rules on top.

[`std::buffer`](/silk/docs/std/buffer/) also continues to provide:

- `BufferU8`: a packed, growable byte buffer for OS/FFI byte APIs (byte-addressed
 `ptr`, with `len`/`cap` in bytes), and
- width-oriented aliases backed by [`std::vector::Vector(T)`](/silk/docs/std/vector/) for convenience.

## Regions

Regions provide a fixed-size, **statically allocated** block of memory that can
be used as an allocation context for `new`.

Regions are represented at runtime as a first-class `Region` handle value. A
`Region` value may be passed to functions, stored in structs, and exported.

### Notes



Supported forms:

- Parsing and type-checking of:
 - `const region <name>: u8[N];`
 - `with <name> { ... }`
 - `with <bytes> { ... }` / `with(<bytes>) { ... }` (anonymous region for the block)
 - `with <bytes> from <region> { ... }`
 - `with <bytes> from <region>[<start>..] { ... }`
 - `with <bytes> from <region>[<start>..<end>] { ... }`
- `Region` is a primitive handle type:
 - `const region name: u8[N];` binds `name` as a `Region` value,
 - `Region` values may be passed and stored (including in struct fields),
 - `with <name> { ... }` accepts any `Region`-typed binding (including function parameters and locals).
- Inside a `with <region> { ... }` block, `new` allocations for non-opaque
 `struct` values allocate from the active region instead of the heap.
- Within the dynamic extent of a `with <region> { ... }` block (including calls
 performed while the block is active), raw allocations via
 [`std::runtime::mem::alloc`](/silk/docs/std/runtime-mem/) allocate from the active region (8-byte aligned).
- Region allocation overflow traps at runtime.

Limitations (Supported forms):

- The region backing store is currently restricted to `u8[N]` (a fixed-size
 byte array type annotation).
- Only the existing `new` subset is affected (non-opaque `struct` allocations
 that produce `&Struct`).
- Region-backed `new` allocations are still reference-counted:
 - last-release runs `drop` (when defined),
 - but the backing bytes are not freed (region memory is reclaimed only by
 reusing the region cursor, as described below).

### Syntax

#### `Region` handle type

`Region` is a primitive value type representing a region allocation context.

Conceptually, a `Region` value contains:

- a base pointer to the backing bytes,
- a pointer to a mutable cursor cell (shared by copies of the handle), and
- a byte limit used for overflow checking.

Copying a `Region` value copies the handle; copies refer to the same backing
store and cursor.

#### Declaring a region

A region declaration has the surface form:

```silk
const region region_buf: u8[1024];
```

The backing-size expression may also use literal arithmetic that folds at parse
time:

```silk
const region region_buf: u8[1024 * 1024];
```

Rules:

- `const region` is a declaration form (it is not a type).
- A region declaration has no initializer.
- The type annotation specifies the region backing size and must be a fixed
 byte array type: `u8[N]`.
 - In the current parser subset, `N` may be an integer literal or a
 literal-only integer arithmetic expression using `+`, `-`, `*`, `/`, `%`,
 and parentheses.
 - In the current region subset, that expression must fold to a concrete byte
 count at parse/type-check time.
- The declared name is bound as a `Region` value.

#### Using a region: `with`

`with` establishes a region allocation context for the enclosed block.

##### 1) Bind an existing region

`with <region> { ... }` activates a named region binding:

```silk
struct Frame { x: int }

fn main () -> int {
  const region region_buf: u8[1024];

  with region_buf {
    let p: &Frame = new Frame{ x: 1 };
    // ...
  }

  return 0;
}
```

The `<region>` name may refer to any `Region`-typed binding, including a region
parameter passed to a function:

```silk
struct Frame { x: int }

fn alloc_in (r: Region) -> int {
  with r {
    let p: &Frame = new Frame{ x: 1 };
    return p.x;
  }
}
```

##### 2) Use an anonymous region with an explicit byte budget

`with <bytes> { ... }` (or `with(<bytes>) { ... }`) creates an anonymous region
backed by `<bytes>` writable bytes and activates it for the block:

```silk
struct Frame { x: int }

fn main () -> int {
  with 1024 {
    let p: &Frame = new Frame{ x: 1 };
    // ...
  }
  return 0;
}
```

Rules (Supported forms):

- `<bytes>` must be a positive integer literal.

##### 3) Use a slice of an existing region (`from`)

`with <bytes> from <region> { ... }` creates an anonymous region backed by the
first `<bytes>` bytes of `<region>`:

```silk
struct Frame { x: int }

fn main () -> int {
  const region region_buf: u8[2048];

  with 1024 from region_buf {
    let p: &Frame = new Frame{ x: 1 };
    // ...
  }

  return 0;
}
```

You may also specify a byte slice of the source region:

```silk
with 1024 from region_buf[64..] {
  // uses bytes 64..(64 + 1024) of `region_buf`
}

with 1024 from region_buf[64..1088] {
  // uses bytes 64..1088 of `region_buf`
}
```

Rules (Supported forms):

- `<bytes>` must be a positive integer literal.
- `<region>` must name a `Region` value that has a compile-time-known backing size
 in the Supported forms (for example a `const region` declaration).
- Slice bounds use **byte offsets** (the region backing store is `u8[N]`).
- `<start>` / `<end>` must be non-negative integer literals.
- When an explicit `<end>` is present, it is exclusive (`[start..end]`).
- The `from` slice must contain at least `<bytes>` writable bytes:
 - `with <bytes> from r { ... }` requires `<bytes> <= sizeof(r)`.
 - `with <bytes> from r[start..end] { ... }` requires `<bytes> <= end - start`.
 - `with <bytes> from r[start..] { ... }` requires `<bytes> <= sizeof(r) - start`.

### Semantics

#### Region-backed `new`

Within a `with <region> { ... }` block:

- any `new` allocation performed by the compiler’s `new` lowering uses the
 active region as its backing store,
- allocations are **8-byte aligned** in the Supported forms,
- if the region does not have enough remaining space, the program traps.

Outside of a `with` block, `new` uses the current heap model described in
[`Memory Model (Stack, Heap, and Moves)`](/silk/docs/language/memory-model/).

#### Region-backed raw allocation ([`std::runtime::mem::alloc`](/silk/docs/std/runtime-mem/))

Within the dynamic extent of a `with <region> { ... }` block (including calls
performed while the block is active):

- [`std::runtime::mem::alloc(n)`](/silk/docs/std/runtime-mem/) allocates an `n`-byte payload from the active
 region (8-byte aligned) and reserves an additional 8-byte header immediately
 before the returned pointer (used by the runtime to distinguish region-backed
 and heap-backed pointers and to record the allocation size),
- if the region does not have enough remaining space, the program traps.

Implication for `with <bytes>` limits: each `alloc(n)` consumes at least
`n + 8` bytes of region capacity (plus any alignment padding from 8-byte
alignment).

Region-backed raw allocations are bump-allocated. In the current runtime model:

- [`std::runtime::mem::free`](/silk/docs/std/runtime-mem/) is a no-op for region-backed pointers,
- [`std::runtime::mem::realloc`](/silk/docs/std/runtime-mem/) reallocates by allocating a new region block and
 copying bytes (it never calls libc `realloc` on a region-backed pointer).

#### Nested `with`

Nested `with` blocks use the innermost active region:

```silk
with a {
  with b {
    // `new` uses region `b` here.
  }
}
```

### Reclaiming Region Memory

Regions are bump allocators: each allocation advances a cursor within the
backing byte buffer.

Because region-backed `new` allocations are still RC-managed in the current
subset and do not free backing bytes on last-release, reclaiming region memory
requires resetting the region cursor so the backing bytes can be reused.

Current behavior:

- `with <region> { ... }` activates the region but does **not** reset its cursor.
 - allocations across multiple `with <region>` blocks accumulate and can
 eventually overflow and trap.
- `with <bytes> { ... }` creates an anonymous region and resets its cursor to `0`
 on entry so repeated execution of the block starts from an empty region.
- `with <bytes> from <region>[...] { ... }` creates an anonymous region backed by
 a subrange of `<region>` and resets its cursor to the slice start on entry.

Important limitation:

- The compiler does not yet enforce “region allocations must not escape the
 `with` block”. Because anonymous-region cursors are reset on entry, code must
 treat pointers/`&Struct` values allocated inside `with <bytes> { ... }` and
 `with <bytes> from ... { ... }` as block-scoped.

### Exports

Region declarations may be exported and imported like other top-level bindings:

```silk
export const region global_region_buf: u8[4096];
```

Exporting a region exports a `Region` handle that refers to the same backing
bytes and cursor cell. Importing a region binds a `Region` value that may be
used with `with` like a locally declared region.

## Concurrency

Concurrency in Silk is built around two orthogonal function modifiers:

- `async` — marks a function as pausable/awaitable (concurrency),
- `task` — marks a function as safe to execute on a worker pool (parallelism),

plus structured concurrency blocks (`async { ... }` and `task { ... }`)
intended to provide **structured concurrency**.

- The runtime can manage a thread pool to execute tasks.
- The compiler is intended to enforce task-safety rules when values cross task
 boundaries (Send/Sync-like constraints).

### Notes

This document describes the **language design** for concurrency and the subset
implemented by the compiler/runtime today.

#### Notes

- Parsing of `task fn`, `async fn`, and `async task fn` / `task async fn`.
- Parsing of `yield <expr>` and `yield * <expr>` (see `yield` below).
- Parsing of `await * <expr>` as a unary `await` applied to a unary `*`
 operand (see `await` below).
- Calling a function with a concurrency discipline produces a handle:
 - calling a `task fn` produces `Task(T)`,
 - calling an `async fn` produces `Promise(T)`,
 - calling an `async task fn` produces `Promise(Task(T))`,
 where `T` is the function’s declared surface result type.
- `yield` is implemented with two forms:
 - **send** (`yield <value>;`) inside a task: writes one task value (convertible
 to the enclosing task’s `T`) to the task’s receiver and continues execution.
 This form is only permitted inside a `task fn` / `async task fn` body.
 - **receive** (`yield <task_handle>`) in value position: waits until the task
 produces its next value and yields `T`.
 - under the hosted async executor, this wait suspends the current coroutine
 instead of blocking the executor owner OS thread,
 - outside an executor (or on non-owner threads), it blocks the current OS
 thread until the task produces a value.
- `yield` on a temporary task handle is eager in the Supported forms:
 - `yield <task_expr>` where `<task_expr>` is not a named handle drains the
 task (joining dedicated-thread tasks only) and yields its final value `T`
 (so the temporary handle does not leak).
- `yield * <task_handle>` in value position drains a task:
 - `yield * Task(T)` receives **all** remaining values from the task, joins
 the dedicated worker thread when the task uses `attr(task=thread)`, and
 yields a collected `T[]` result (with the task’s final return value as the
 last element). Pooled/default tasks skip the join.
 - `yield *` also accepts fixed task arrays and returns one concatenated
 collected `T[]` in source order, including:
 - named bindings such as `yield * tasks`,
 - direct fixed-array expressions,
 - struct-field carriers such as `yield * box.tasks`,
 - nested field expressions such as `yield * make_box().tasks`.
- `yield * <task_handle>;` as a statement inside a task function forwards values:
 - drains the right-hand task and forwards all remaining values to the
 enclosing task’s receiver, then joins/cleans up the drained task.
 - the same forwarding sugar accepts fixed task arrays and drains them in
 source order, including field-carried fixed arrays.
- `await <expr>` is implemented as a Promise unwrap operation:
 - `await Promise(T)` unwraps and yields `T`,
 - `await Promise(Task(T))` unwraps and yields `Task(T)`,
 - `await Task(T)` is rejected (use `yield` / `yield *` for task values).
- `await * <promises>` unwraps a collection of promises:
 - `await * Promise(T)[]` yields a collected `T[]` by awaiting each promise,
 - `await * Promise(T)` is rejected (the `*` form requires a collection).
- `await` and the structured block form are still **async-context-only**:
 - `await` is only allowed inside functions declared with `async` (including
 `async task fn`),
 - `async { ... }` and `task { ... }` are only allowed inside functions declared
 with `async`.
 - `async loop { ... }` and `task loop { ... }` are only allowed inside functions
 declared with `async`.
- Conservative suspension-safety rules are enforced at `async fn` boundaries:
 - `async fn` result types must not contain ordinary borrowed views (`&T` or
 `T[]`), including when nested inside structs, enums, optionals, or
 function types,
 - references to opaque structs (`struct Name;`) remain permitted in async
 results because they are treated as external handles rather than
 borrow-checked views into Silk storage,
 - borrowed async parameters are permitted, but an ordinary borrow of
 function-local stack storage or a local fixed array may not be passed into
 an async call unless that call is awaited immediately in the same
 expression.
- Conservative suspension-safety rules are also enforced at concrete `await`
 points:
 - `await` / `await *` reject a live borrowed reference that still points at
 a local stack value,
 - `await` / `await *` reject a live slice that still points at a local fixed
 array,
 - and the same rule applies when the borrowed view is stored in a local
 struct field.
- `async { ... }` / `task { ... }` are accepted as structured concurrency surface
 forms and establish lexical scopes with deterministic runtime-backed cleanup:
 - live `Promise(T)` bindings are awaited/destroyed on scope exit,
 - live `Task(T)` bindings are drained/destroyed on scope exit,
 - and the same cleanup runs for lowered early-exit paths such as `return`.
 - these blocks do not create nested executors or inject implicit cancellation
 tokens in the Supported forms.
- `yield` is **task-context-only**:
 - `yield` is only allowed inside `task` functions (`task fn` / `async task fn`)
 and inside `task { ... }` / `task loop { ... }` blocks.
- Initial task-safety rules are enforced at the `task fn` boundary:
 - `task fn` / `async task fn` parameter and result types must not contain
 non-opaque reference types (`&T`), including within structs and optionals.
 - references to opaque structs (types declared as `struct Name;`) are permitted
 (opaque structs are handle types and cannot be dereferenced or field-accessed
 in Silk).
 - `Task(T)` and `Promise(T)` handles are permitted at task boundaries, but
 their inner `T` must itself satisfy the task-safety rule above. This
 supports patterns like `Task(Promise(T))` (for tasks that produce promises)
 and `await * yield * t` for `t: Task(Promise(T))`.
 - [`std::sync::Arc(T)`](/silk/docs/std/sync/) handles are permitted at task boundaries when `T`
 satisfies the same task-safety rule. Moving an `Arc(T)` into a task
 transfers that handle; call `clone()` explicitly before spawning multiple
 tasks that need shared ownership. Borrowed non-opaque references inside
 `Arc(T)` are rejected at the task boundary.

#### Thread Safety and Sharing

`task` concurrency runs on OS threads. Crossing a task boundary is therefore a
thread-crossing operation.

In Silk currently:

- Passing values into a `task fn` is **by value**. For ownership-tracked values
 (for example `Drop` types and `Task(T)` / `Promise(T)` handles), this is a
 move: ownership transfers into the task and there is no implicit sharing.
- The checker enforces a conservative task-safety rule at `task fn` / `async task fn`
 boundaries (`E2037`):
 - non-opaque references (`&T`) are rejected (including nested inside structs and optionals),
 - references to opaque structs (types declared as `struct Name;`) are permitted
 (opaque structs cannot be dereferenced or field-accessed in Silk),
 - task boundary types are otherwise restricted to primitives, optionals, and
 structs/enums composed of task-safe members.
- Shared mutable state must be synchronized explicitly (for example via
 [`std::sync`](/silk/docs/std/sync/) primitives, [`std::atomic`](/silk/docs/std/atomic/) atomics, or by communicating through
 channels).
- To share a runtime handle across tasks without transferring ownership, prefer
 stdlib APIs that follow the `T` / `TBorrow` pattern (for example
 `Channel(T)` + `ChannelBorrow(T)` and `AbortSignal` + `AbortSignalBorrow`).
- To share ownership of immutable or internally synchronized state across
 tasks, use [`std::sync::Arc(T)`](/silk/docs/std/sync/) and clone the handle explicitly. `Arc(T)` does
 not permit unsynchronized mutation of `T`; put synchronization inside `T`
 (for example a `Mutex`-like handle) when mutation is required.
- To share one atomic cell across tasks, keep the owning [`std::atomic`](/silk/docs/std/atomic/) value
 alive in the parent scope and pass `AtomicU64Borrow` or `AtomicBoolBorrow`
 across the task boundary.

Note: this includes `&Struct` values produced by `new`. The compiler-inserted
reference counting (RC) used for `new` is non-atomic in the Supported forms and
is not safe to share across OS threads.

These rules prevent common “accidentally share a borrowed view across threads”
bugs in the Supported forms. They do not prevent data races in programs that
explicitly share memory through FFI or other low-level mechanisms; such sharing
must be synchronized by the program.

#### Important Limitations

- Hosted async runtime bring-up exists on supported hosted POSIX targets
 (`linux/*` and Apple Silicon `macos/aarch64` today):
 - `await` is a true suspension point backed by a single-threaded executor
 (fibers), so awaiting a pending `Promise(T)` can park and resume without
 blocking the OS thread.
 - The current implementation uses stackful coroutines in `libsilk_rt`
 (`the implementation`) rather than a compiler state-machine coroutine
 transform. The long-term design remains a compiler transform + stable
 [`std::runtime::event_loop`](/silk/docs/std/runtime-event_loop/) surface (see [`Async Runtime (Hosted)`](/silk/docs/compiler/async-runtime/)).
 - The shipped executor is **thread-affine**:
 - only the thread that created the executor may spawn and drive stackful
 coroutines (stackful coroutines are never migrated across OS threads),
 - [`std::runtime::event_loop::{poll,deinit}`](/silk/docs/std/runtime-event_loop/) must be called on that same
 thread,
 - other OS threads (including `task fn` workers) may still call `async fn`
 entrypoints, but those calls run synchronously (no coroutine spawn), and
 `await` on a non-owner thread blocks the OS thread until the promise is
 resolved.
 - Awaiting a `Task(T)` is rejected by design; use `yield` / `yield *` for task values.
 - Executable entrypoints currently support `fn main (...) -> int`,
 `fn main (...) -> void`, `async fn main (...) -> int`,
 `async fn main (...) -> void`, `fn main(argc: int, argv: u64) -> int`,
 and `fn main(argc: int, argv: u64) -> void`.
 Task-backed entrypoints such as `task fn main` and `async task fn main`
 are rejected by the executable runtime path; keep task work inside an
 ordinary or async `main`.
- The runtime subset implements `task` execution using OS threads:
 - By default, calling a `task fn` schedules that task on the global task
 pool (a shared queue-based worker pool). The pool is created lazily.
 - `attr(task=thread)` forces a dedicated OS thread per call.
 - The pool worker count is configurable via `SILK_TASK_POOL_THREADS`.
 - The queued backlog is configurable via `SILK_TASK_POOL_MAX_QUEUED`.
- Full Send/Sync-style checking (beyond the conservative boundary restriction
 described above) is not implemented yet. In particular, the compiler does not
 attempt to prove absence of data races for shared state; programs must use
 explicit synchronization for any shared mutation.
- A small initial set of standard-library primitives exists now under
 [`std::task`](/silk/docs/std/task/) and [`std::sync`](/silk/docs/std/sync/) for supported hosted POSIX targets. Some
 OS-facing std modules already integrate with the async executor/event loop
 for timers, fd readiness, I/O, and TCP connect/accept; wider cancellation and
 platform parity remains follow-up work.
- For cooperative cancellation across tasks and `async` functions, `std::`
 provides WHATWG-style abort signals via [`std::abort_controller`](/silk/docs/std/abort_controller/) (see
 `the standard library`).

### Core Keywords: `async` and `task`

#### `async`

- Marks a function as **awaitable** (pausable).
- Primary domain (design): I/O-bound concurrency on an event loop/executor.

#### `task`

- Marks a function as **task-safe** and eligible to be executed as a parallel
 task on a worker pool.
- Primary domain (design): CPU-bound parallelism and offloading blocking work.
- In the intended design, *calling a `task fn` is non-blocking* and produces a
 task handle.

#### `await`

`await <expr>` is the surface syntax for unwrapping a `Promise(T)` handle.

In Silk currently:

- `await Promise(T)` unwraps the completed promise and yields `T`.
- `await Promise(Task(T))` yields `Task(T)` (which can then be consumed via `yield` / `yield *`).
- `await Task(T)` is rejected; use `yield` / `yield *` for task values.
- `await` / `await *` also reject live borrows of ordinary local stack or
 fixed-array storage at the suspension point; end such borrows before
 awaiting.
- Ordinary non-entrypoint `async fn` bodies support `await * Promise(T)[]`
 fan-in, including named fixed-array locals such as:
 - `let promises = [reader(), writer()];`
 - `await * promises;`
 The compiler/runtime path drains those promise handles and treats the bound
 fixed array as consumed so scope cleanup does not attempt to drop the same
 handles twice.

##### Typed Errors Across Async Calls

Typed-error handling composes with async calls in the Supported forms, but the
fallible operation remains the **async call site** rather than the `await`
itself.

For an async function like:

```silk
error OpenFailed {
  code: int,
}

async fn open_value () -> int | OpenFailed {
  return 1;
}
```

the current checker behavior is:

- `await open_value()` is rejected with `E2023` because the fallible async call
 has not been handled yet.
- `let p: Promise(int) = open_value()?;` is accepted inside a matching error
 contract.
- `let v: int = await open_value()?;` is accepted and is the supported
 propagation form in the Supported forms.
- Explicit handling with [`match`](/silk/wiki/language/flow-match/) applies to the async call itself, so the
 success arm receives the `Promise(T)` handle:

```silk
match (open_value()) {
  p => {
    let v: int = await p;
    return v;
  },
  err: OpenFailed => {
    panic OpenFailed { code: err.code };
  }
}
```

##### Task/Promise Handle Ownership

In Silk currently, `Task(T)` and `Promise(T)` are **single-use
handles**:

- A `Promise(T)` handle may be **awaited at most once**. `await` consumes the handle.
- A `Task(T)` handle may be **drained/joined at most once** via `yield *`
 (and `yield` on a temporary task expression drains as well, joining
 thread-per-call tasks).
- Handles are **non-copyable**: you may not copy a handle into another binding
 or use it as a normal value expression.
- Discard bindings may not consume handles:
 - `let _ = task_call();` is rejected for `Task(T)`,
 - `let _ = async_call();` is rejected for `Promise(T)`,
 because `_` performs end-of-statement cleanup rather than structured
 scope-exit cleanup.
- Handles may be moved into ordinary bindings, reassigned after consumption,
 passed through consuming call positions, and moved into collections that
 accept move-only element values.
- Direct `Task(T)` / `Promise(T)` storage in struct and error fields is part of
 the Supported forms:
 - `struct Box { t: Task(int) }` is accepted,
 - `struct Box { p: Promise(int) }` is accepted,
 - consuming field access such as `yield * box.t` and `await box.p` is
 tracked with the same single-use rule as local handle bindings,
 - whole-value initialization/copy/reassignment refreshes the stored field
 handle state for the destination aggregate.
- A consumed handle may not be used again (including attempting to `await` it a
 second time, or attempting to `yield *` it a second time).
- Consuming a handle that was created outside the current loop body is rejected
 in the Supported forms (a loop may iterate multiple times).

These rules are enforced at compile time and exist to prevent double-free and
use-after-free bugs in the current runtime lowering, where `await` frees the
underlying handle storage after join/unwrap.

##### Handle Lifetime and Cleanup

In Silk currently, `Task(T)` and `Promise(T)` handles are stored in
heap-allocated handle memory:

- `await` unwraps a promise and then frees the promise handle storage.
- `yield *` drains a task and then frees the task handle storage (joining
 dedicated-thread tasks).
- `yield *` over a fixed task array consumes each contained handle exactly once
 and marks the named array binding moved so cleanup does not attempt to free
 the same handles again.
- If a handle is **not consumed** (`await`/`yield *`), the compiler inserts
 automatic cleanup when the handle binding is overwritten or goes out of scope:
 - `Task(T)` cleanup joins the worker thread for `attr(task=thread)` tasks and
 then frees the handle storage. Pooled/default tasks skip the join since
 there is no per-call worker thread to join.
 - `Promise(T)` cleanup frees the handle storage.

Because tasks are implemented using OS threads in the Supported forms, this
automatic cleanup can block the current OS thread when it joins a task. Promise
cleanup uses the hosted async runtime’s destroy helper and may suspend the
current coroutine while waiting for a pending promise to resolve when running
under an executor.

#### `yield`

`yield` is the task-side counterpart to `await`.

In the intended model for tasks:

- A `task fn ... -> T` produces a `Task(T)` handle when called.
- Inside the task body, `yield <expr>;` sends a value (convertible to `T`) to
 the task’s receiver and continues execution.
- `return <expr>;` sends the final task value (of type `T`) and terminates the
 task.
- Outside the task, `yield <task_handle>` blocks until the task produces its
 next value and yields it.
- The receive form is a value-position expression, not a statement form:
 - `let value = yield task_handle;` receives one value,
 - `yield task_handle;` is parsed as the statement/send form and is therefore
 not the right way to wait on another task handle.
- `yield * <task_handle>` drains all remaining task values and then joins the
 worker thread for cleanup when the task uses `attr(task=thread)`.
 Pooled/default tasks skip the join. In value position, `yield *` yields a
 collected `T[]`.
- `yield * <task_handle>;` as a statement forwards all remaining values from the
 right-hand task to the enclosing task’s receiver and then joins/cleans up the
 drained task.

In Silk currently:

- `yield` is a blocking OS-thread operation (like the rest of the current
 concurrency runtime).
- `yield` is permitted only inside `task fn` / `async task fn` bodies and inside
 `task { ... }` / `task loop { ... }` blocks.
- The statement forms (`yield <value>;` and `yield * <task_handle>;` forwarding)
 require an enclosing task function (`task fn` / `async task fn`), since they
 send values to the task’s receiver.

##### Collected Array Ownership

In the Supported forms, `yield *` and `await *` produce a heap-allocated
collection of values (`T[]`) for convenience. This is a current behavior:

- the compiler inserts deterministic cleanup for these collections when their
 bindings are overwritten or go out of scope,
- the returned `T[]` value must not be copied, and must not escape its defining
 scope until a stable owning collection type is specified.

#### Structured Concurrency Blocks and Loops

`async { ... }`, `task { ... }`, `async loop { ... }`, and `task loop { ... }`
introduce surface syntax for structured regions.

In Silk currently, these forms remain lexical scopes, but they are
runtime-backed for live-handle cleanup:

- live `Promise(T)` bindings are awaited/destroyed on scope exit,
- live `Task(T)` bindings are drained/destroyed on scope exit,
- task waits performed during that cleanup use the hosted async fd-wait path
 when running under the executor, so cleanup inside async code suspends the
 current coroutine rather than blocking the executor owner thread,
- and no implicit nested scheduler or abort-controller injection occurs.

### Current Runtime Boundaries

This language document describes the shipped concurrency subset and its current
boundaries. Longer-term runtime architecture notes are tracked in
[`Async Runtime (Hosted)`](/silk/docs/compiler/async-runtime/); the runtime backlog items for the current
hosted subset are now implemented.

Current boundaries and non-goals:

- `async` is cooperative: there is no preemptive async scheduling.
- `task fn` calls default to the global task pool; `attr(task=thread)` is the
 explicit dedicated-thread opt-out.
- `yield` / `yield *` waits inside executor-driven async code suspend the
 current coroutine, but the same operations still block when no executor is
 active or when run from non-owner threads.
- structured blocks/loops guarantee deterministic live-handle cleanup on scope
 exit and early exit, but they do not inject implicit cancellation tokens or
 nested executors.
- Task-boundary safety still uses the conservative current rule that rejects
 ordinary non-opaque `&T` across `task fn` / `async task fn` boundaries.
- `await Task(T)` remains rejected; task values are consumed via `yield` /
 `yield *`.
- Hosted async coroutines are not migrated across OS threads; parallelism is
 expressed via `task` and explicit synchronization.

## Formal Silk

Formal Silk is Silk’s compile-time formal verification language. It is written
using syntax that does not exist at runtime and is discharged at compile time
using the Z3 SMT solver.



When Formal Silk syntax is present, compilation generates verification
conditions (VCs), proves them with Z3, and fails the build if any VC cannot be
proven. This behavior applies to:

- the `silk` CLI (`silk check`, `silk test`, `silk build`), and
- the C ABI build entrypoints (`silk_compiler_build`, `silk_compiler_build_to_bytes`).

### Proof requirements are opt-in by syntax

Silk requires proofs only when verification syntax is present in the compiled
module set:

- any use of `#...` directives (`#require`, `#assure`, `#assert`, `#invariant`,
 `#variant`, `#monovariant`, `#const`) — including `#require` attached to
 `struct` declarations.

When verification syntax is present, compilation MUST:

1. generate VCs,
2. prove them using Z3, and
3. fail compilation with clear diagnostics if any VC cannot be proven.

When verification syntax is not present, compilation does not require proofs.

### Z3 linkage and overrides

On supported native hosts, Silk links the built-in Z3 static library and its
headers ([`vendor/include`](https://github.com/oro-computer/silk/tree/master/vendor/include)) directly into the compiler when the host archive is
present:

- `linux/x86_64` -> [`vendor/lib/x64-linux/libz3.a`](https://github.com/oro-computer/silk/blob/master/vendor/lib/x64-linux/libz3.a)
- `macos/aarch64` -> [`vendor/lib/aarch64-macos/libz3.a`](https://github.com/oro-computer/silk/blob/master/vendor/lib/aarch64-macos/libz3.a) (optional and staged
 when present)

If no static host archive is present, the compiler still builds, but Formal
Silk verification reports Z3 as unavailable unless a dynamic library override is
provided.

To override the Z3 library at runtime (for example to test against a different
Z3 build), provide a dynamic library path:

- CLI: pass `--z3-lib <path>`, or
- CLI/ABI: set `SILK_Z3_LIB` in the environment.

When `--z3-lib` is provided, it overrides `SILK_Z3_LIB`.

### Debugging proofs with Z3 (`--debug`)

When a verification condition fails, the compiler reports a normal diagnostic
at the failing annotation site.

When `--debug` is passed to `silk build` or `silk test`, the verifier also emits
additional Z3 debugging output to stderr and writes an SMT-LIB2 reproduction
script under `.silk/z3/` in the current working directory (or `$SILK_WORK_DIR/z3`):

- `.silk/z3/silk_z3_m<module>_<n>.smt2`

You can replay the query with an external Z3 binary:

```sh
z3 -smt2 .silk/z3/silk_z3_m0_0.smt2
```

Successful `silk build` runs that compile exported Formal Silk surface also emit
a distributable success-path bundle under `.silk/formal/` (or
`$SILK_WORK_DIR/formal/`) keyed by the output artifact identity. See
“Distribution and export bundles” below.

### Z3 model

The current Formal Silk verifier maps Silk constructs directly to Z3:

- `bool` → Z3 Bool.
- `string` → Z3 String (Supported forms: literals and equality/inequality comparisons).
- integer primitives → fixed-width Z3 bitvectors:
 - `i8`/`u8` → BV8
 - `i16`/`u16` → BV16
 - `i32`/`u32` → BV32
 - `i64`/`u64`/`int` → BV64

 Arithmetic is modular 2^N (wraparound). Ordered comparisons and `>>` use
 signed semantics for signed integers (`i*`/`int`) and unsigned semantics for
 unsigned integers (`u*`).
- other primitive/runtime values that do not currently have dedicated numeric
 reasoning support (for example `char`, floating-point primitives, `Range`,
 `Instant`, and `Duration`) are modeled as **opaque uninterpreted values**.
- non-primitive runtime values passed through contracts
 (`&T`, named values, optionals, arrays, function values, and applied types)
 are modeled as **opaque uninterpreted values**:
 - equality/inequality works when both sides have the same Silk type,
 - the verifier does not infer field layout or numeric ordering from these
 values,
 - this is enough for exported method receivers and distributable contracts
 that only need identity-style reasoning over non-primitive parameters.

Supported operators in specification expressions (Supported forms):

- boolean: `!`, `&&`, `||`, `==`, `!=`
- string: `==`, `!=`
- integer:
 - unary: `-`, `~`
 - arithmetic: `+`, `-`, `*`, `/`, `%`
 - bitwise: `&`, `|`, `^`, `<<`, `>>`
 - comparisons: `<`, `<=`, `>`, `>=`, `==`, `!=`
- size/layout queries: `sizeof`, `alignof`, `offsetof` (type operands and other statically-sized operands in the Supported forms)

Supported name-resolution sources in specification expressions (Supported forms):

- in-scope runtime/formal bindings,
- compiler-provided metadata constants such as `BUILD_*`, `OS_*`, and `SILK_*`,
- and const-evaluable module/package `const` bindings, including exported
 qualified names such as [`std::limits::I64_MAX`](/silk/docs/std/limits/).

Other operators and expression forms are currently rejected in verified code
(see the notes below).

### The `ext` boundary

External declarations (`ext`) have no body available to the verifier.

Therefore:

- The verifier cannot generate VCs about the behavior of `ext` bodies.
- In the current verifier subset, calls are supported only to functions and
 methods that have Formal Silk contracts (see “Contracted calls” below).
 `ext` declarations do not have Formal Silk contracts yet, so verified code
 cannot call `ext` functions.

See `External Declarations (`ext`)` for the external-declaration rules.

The main constructs are:

- `#const` — formal Silk declarations used inside specifications.
- `#require` — precondition.
- `#assure` — postcondition.
- `#assert` — block-local proof obligation.
- `#invariant` — loop or state invariant.
- `#variant` — well-founded termination measure (ranking function).
- `#monovariant` — monotonic measure (non-decreasing or non-increasing).
- `theory` / `#theory` — reusable, parameterized proof obligations.

Key properties:

- These annotations appear before the function or loop they describe.
- They are used by the verifier only and incur no runtime cost.

#### Formal Silk declarations (`#const`)

Formal Silk declarations let you name intermediate values for use in specifications.

Syntax:

```silk
#const name = <Expr>;
```

Rules:

- `#const` is a statement that may appear inside function bodies (inside blocks).
- The binding is **compile-time-only** and is not lowered into runtime code.
- A `#const` binding is visible only inside specification expressions:
 - function specs (`#require`, `#assure`),
 - loop specs (`#invariant`, `#variant`, `#monovariant`).
- Using a `#const` name in a runtime expression (e.g. in [`while`](/silk/wiki/language/flow-while/) conditions or normal
 `let` initializers) is a compile-time error. Use a normal `let` binding for
 runtime values, and (optionally) introduce a `#const` alias for specifications.

Example:

```silk
fn main () -> int {
  let limit: int = 3;
  #const original_limit = limit;

  let mut i: int = 0;
  #invariant i >= 0;
  #invariant i <= original_limit;
  #variant original_limit - i;
  while i < limit {
    i = i + 1;
  }
  return 0;
}
```

#### Function annotations

For functions, the initial surface syntax is:

```silk
#require <Expr>;
#require <Expr2>;
#assure <Expr3>;
#theory TheoryName(args...);
fn name (params) -> ResultType {
  ...
}
```

- One or more `#require`, `#assure`, and contract-theory attachments
 (`#theory Name(args...);`) may appear, in any order, immediately before the
 `fn` declaration (and before any `export` modifier).
- Each annotation is terminated by a semicolon.
- The compiler front-end:
 - lexes these annotations as dedicated tokens,
 - parses the annotation expressions using the normal expression grammar,
 - type-checks each annotation expression as `bool` so obvious mistakes are
 rejected early (specifications are still compile-time-only metadata),
 - attaches them to the corresponding function in the AST as lists of
 preconditions, postconditions, and contract theories.

#### Struct requirements (`#require` on `struct`)

Struct declarations may be preceded by one or more `#require` directives:

```silk
#require <Expr>;
struct Name {
  field: int,
}
```

These `#require` expressions are **struct requirements**: properties that must
hold for all values constructed for that struct type.

Rules (Supported forms):

- Struct requirement expressions may reference the struct's fields by name.
- When Formal Silk syntax is present, the verifier proves all requirements at
 struct literal construction sites (`Name{ ... }` and `new Name{ ... }`),
 using the literal's field initializers and default initialization for any
 omitted fields.
- When a struct extends a base struct, the derived struct inherits the base
 struct's requirements (all requirements must be proven at construction).
- If the verifier cannot prove a requirement, compilation fails with `E3006`.
 The diagnostic names the failed predicate and, when the predicate references
 fields initialized by the literal or by defaults, reports those field values.

Loop specifications (`#invariant`, `#variant`, `#monovariant`) follow a similar
pattern for loops.

#### Loop annotations

For [`while`](/silk/wiki/language/flow-while/) loops, the initial surface syntax is:

```silk
#invariant <Expr>;
#variant <Expr2>;
#monovariant <Expr3>;
while condition {
  ...
}
```

Rules:

- One or more `#invariant` annotations, zero or more `#monovariant` annotations,
 and at most one `#variant` annotation may appear immediately before the
 [`while`](/silk/wiki/language/flow-while/) keyword.
- Each annotation is terminated by a semicolon.
- The compiler front-end:
 - lexes these annotations as directive tokens,
 - parses the annotation expressions using the normal expression grammar,
 - attaches them to the corresponding loop in the AST as invariants,
 monovariants, and a (single) variant expression.

The verifier will interpret:

- `#invariant` expressions (type `bool` in the Supported forms) as properties that must hold:
 - before entering the loop,
 - after each iteration (assuming the body and condition do not diverge),
 - and at `break` exits (so proofs after the loop may rely on the invariant).
- `#variant` expressions as a well-founded measure that must decrease on each
 iteration (and be non-negative at the loop head), used for termination proofs.
- `#monovariant` expressions as measures that must be monotonic on each
 iteration (either non-decreasing or non-increasing, proved consistently across
 all continuation paths).

Compiler requirements:

- Parse and represent these annotations in the AST.
- Integrate with the verifier to check specifications.
- Ensure that, if verification fails, compilation fails with clear diagnostics.

#### Block assertions (`#assert`)

Formal Silk also supports block-local proof obligations:

```silk
#assert <Expr>;
```

Rules:

- `#assert` is a statement that may appear inside function/test bodies (inside
 blocks).
- It is compile-time-only metadata and is not lowered into runtime code.
- The verifier must prove the assertion holds in the current symbolic state at
 the `#assert` site. If it cannot be proven, compilation fails.
- After a `#assert` succeeds, the asserted expression is assumed to hold for
 the remainder of the block (so later proofs may rely on it).

### Notes

Implemented end-to-end (Z3-backed, Supported forms):

- The verifier runs only when Formal Silk syntax is present.
- `#require` / `#assure`:
 - generate VCs and prove them for verified `fn` declarations and verified
 `impl` methods.
 - `#assure` may reference `result` (the return value) as a built-in formal declaration.
 - build metadata constants are available in Formal Silk expressions:
 - `BUILD_KIND`, `BUILD_MODE`, `BUILD_VERSION` as built-in compile-time `string` values,
 - and `BUILD_VERSION_MAJOR` / `BUILD_VERSION_MINOR` / `BUILD_VERSION_PATCH` as built-in compile-time `u64` values.
- Struct requirements (`#require` on `struct` declarations):
 - generate VCs and prove them at struct literal construction sites (`Type{ ... }`
 and `new Type{ ... }`),
 - include inherited requirements from base structs (`struct Child extends Base { ... }`).
- `#assert`:
 - proves the asserted expression holds at the `#assert` site,
 - and then assumes it for the remainder of the block.
- `#invariant` / `#variant` / `#monovariant` on [`while`](/silk/wiki/language/flow-while/) loops:
 - prove invariants at entry and preservation across one iteration,
 - prove variants are non-negative and decrease across one iteration.
 - prove monovariants are monotonic across one iteration (either non-decreasing
 or non-increasing, consistent across all continuation paths).
- Formal Silk declarations via `#const`:
 - may be referenced only by specification expressions,
 - are rejected in runtime expressions (`E2014`).
- `theory` (reusable assertions, initial subset):
 - `theory Name(params) { ... }` defines a reusable set of proof obligations
 (exportable/importable at top level),
 - `#theory Name(args);` applies it in a function body as compile-time-only
 assertions,
 - `#theory Name(params) { ... }` may also declare an inline (non-exportable)
 local theory inside a block.
- Contracted calls:
 - direct calls of the form `Name(args...)` are checked when `Name` resolves to
 a function with a Formal Silk contract,
 - receiver calls of the form `expr.method(args...)` are checked when the
 receiver resolves to a concrete method owner and `method` resolves to a
 method with a Formal Silk contract,
 - at every checked call site, including ordinary callers that have no Formal
 Silk annotations of their own, the verifier proves the callee’s
 preconditions (explicit `#require` and any attached-theory `#require`) under
 the caller’s current path condition; errors report `E3007`,
 - after the call, the verifier assumes the callee’s postconditions (explicit
 `#assure` plus attached-theory `#assure`/`#invariant`) into verified
 callers' symbolic state so subsequent proofs can use them,
 - if the callee has a source-visible body, the Supported forms requires that
 body to be a single return expression (no runtime statements); the verifier
 inlines that return expression in the caller’s symbolic state,
 - if the callee has **no body** (a declaration-only prototype, typically used
 when linking against a precompiled implementation), the verifier treats the
 call as **opaque**:
 - it proves the preconditions at the call site,
 - introduces an uninterpreted symbolic value for the return,
 - and assumes postconditions about that return value,
 - if the callee has **no Formal Silk contract**, verified code may still call
 it in the Supported forms:
 - if the source-visible body is a single return expression, the verifier
 may inline that return expression into the caller’s symbolic state,
 - otherwise the verifier treats the return value as opaque and assumes no
 additional facts about it,
 - unused expression-statement calls are permitted under the same rule,
 - and the verifier does not infer side-effect facts from these
 contractless calls,
 - recursion is not supported yet.
- Stdlib modules are verified when they use Formal Silk syntax, subject to the
 same current-subset restrictions described here.

Not implemented yet :

- Counterexample models (the verifier reports errors, but does not yet print
 a model).
- Verified local bindings are still limited to primitive/string-like symbolic
 types plus opaque parameter-style values. Field projections from typed
 parameters/results/receivers used in contracts and `#theory` arguments are
 supported through uninterpreted projections. Direct field assignment through
 a named aggregate or receiver (`name.field = expr`) is modeled by creating a
 fresh aggregate value, constraining the assigned field, preserving the other
 fields, and rechecking the aggregate's struct requirements. Verified
 function/method entry also assumes struct requirements for non-optional typed
 aggregate parameters, so direct field writes can prove requirements over
 untouched fields from the aggregate's starting invariant. Fully nested
 field-sensitive named-struct local-state reasoning is not yet supported in
 verified blocks.
- Verification of the full expression language and full statement language
 ([`match`](/silk/wiki/language/flow-match/), nested loops, indirect calls, and many operators are not supported
 yet in verified code). Statement-level `if` path splitting is implemented in
 the Supported forms.
- Verified assignment statements currently support local names and direct field
 writes through named aggregate or receiver values (`name.field = expr`).
 Direct field writes re-prove the target aggregate's struct `#require`
 clauses after the write. Optional-field, index, nested-field, and compound
 assignment targets are rejected with `E3005`.

### Theories (`theory` / `#theory`)

A **theory** is a reusable, parameterized block of Formal Silk directives that
can be applied at points inside a function body to assert properties about the
current symbolic state.

#### Syntax

Declaration form (top-level):

```silk
export theory a_custom_theory (x: int, y: int) {
  #const z = x + y;
  #invariant x != 0 && y != 0;
  #invariant z > 1;
}
```

An inline theory declaration may also appear as a statement inside a
function/test block:

```silk
fn main (x: int, y: int) -> int {
  #theory local_sum_nonzero (x: int, y: int) {
    #const z = x + y;
    #assure z != 0;
  }

  #theory local_sum_nonzero(x, y);
  return 0;
}
```

Use form (statement inside a function body, or inside another theory body):

```silk
fn main (x: int, y: int) -> int {
  #theory a_custom_theory(x, y);
  return 0;
}
```

Theories may apply other theories:

```silk
export theory nonzero (x: int) {
  #require x != 0;
}

export theory nonzero_sum (x: int, y: int) {
  #theory nonzero(x);
  #theory nonzero(y);
  #assure (x + y) != 0;
}
```

Notes:

- Top-level theory declarations use the `theory` keyword.
- Inline (block) theory declarations and theory use sites share the `#theory`
 token; the parser disambiguates by the token that follows the
 argument/parameter list:
 - `{ ... }` starts an inline theory declaration,
 - `;` terminates a theory use.
- A top-level theory declaration may be exported (`export theory ...`). Exported
 theories may be imported from other modules and reused.
- Inline theory declarations inside a block are not exportable/importable; they
 exist only in the containing block and may be applied via `#theory Name(...);`
 after they are declared.
- A theory body may contain `#theory Name(args);` statements. These are
 compile-time-only theory applications; they are checked by the verifier in the
 current symbolic state at the point they appear in the theory body.
- Theory recursion is rejected (direct or indirect cycles).
- Theories are not runtime functions. They can only be applied via `#theory` use
 statements; calling a theory with normal call syntax (`Name(...)`) is a
 compile-time error.

#### Prefix `#require` / `#assure` on theories

For ergonomics, a `theory` declaration may be preceded by `#require` and/or
`#assure` directives:

```silk
#require x >= 0;
export theory ensure_nonnegative_x (x: int) {
  #assure x >= 0;
}
```

These prefix directives are treated as if they were written at the beginning of
the theory body.

#### Theories as function contracts

A function may attach one or more theories as part of its Formal Silk contract
surface by placing `#theory Name(args...);` directives in the function-spec
prelude:

```silk
import { bounded_nonneg_add } from "./theories.slk";

#theory bounded_nonneg_add(x, y);
export fn add (x: int, y: int) -> int {
  return x + y;
}
```

Contract-theory attachments:

- are compile-time-only metadata (not runtime statements),
- contribute additional preconditions/postconditions to the function contract:
 - `#require` become additional function preconditions,
 - `#assure` and `#invariant` become additional function postconditions,
- are used by the verifier to check contracted call preconditions and to enable
 contracted calls in verified code (see “Contracted calls” above),
- are not permitted before a top-level `theory` declaration (only `#require` /
 `#assure` may prefix a theory declaration).

#### Importing theories

Exported theories may be imported via JS-style named imports and then applied
via `#theory` use statements.

Example:

```silk
// theories.slk
export theory ensure_positive_x (x: int) {
  #assure x > 0;
}
```

```silk
// main.slk
import { ensure_positive_x as pos_x } from "./theories.slk";

fn main () -> int {
  let x: int = 1;
  #theory pos_x(x);
  return 0;
}
```

Rules:

- Only exported theories may be imported.
- A theory use (`#theory Name(args);` or `#theory pkg::Name(args);`) resolves
 the theory name as either:
 - a local theory declared in the same module, or
 - an imported theory name from `import { ... } from "<specifier>";`, or
 - an exported theory addressed by its package-qualified name.

#### Semantics

When a theory is applied (`#theory Name(args);`):

- its parameters are bound to the provided argument expressions (as
 specification expressions),
- its `#const` formal declarations are evaluated and are visible only within the
 theory during checking,
- each `#require <Expr>;`, `#invariant <Expr>;`, and `#assure <Expr>;`
 directive in the theory body is treated as a compile-time proof obligation
 that must hold in the current symbolic state at the use site,
- each `#variant <Expr>;` directive in the theory body is treated as a
 non-negativity obligation (`Expr >= 0`) at the use site (the theory form does
 not model decrease across iterations).

Theory bodies are verifier-only:

- Theory argument expressions and theory directive expressions are **Formal Silk
 specification expressions** evaluated by the verifier.
- In the Supported forms, specification expressions do not support function
 calls or value construction (for example `foo(x)`, `Type{...}`, arrays, or
 `new`). Such expressions are rejected as unsupported Formal Silk.

The theory form is compile-time-only and has no runtime semantics.

### Distribution and export bundles

When a successful `silk build` compiles a module set that exposes reusable
Formal Silk surface, the compiler emits a machine-readable export bundle.

Exported surface that participates today:

- `export theory Name(...) { ... }`
- exported top-level functions whose contract surface is non-empty
 (`#require`, `#assure`, or contract `#theory`)
- exported/public `impl` methods whose contract surface is non-empty

The bundle is written under the compiler work directory:

- direct builds:
 - `.silk/formal/<output-identity>/manifest.json`
 - `.silk/formal/<output-identity>/bundle.smt2`
- when `SILK_WORK_DIR` is set, the same layout is rooted there instead of
 `.silk/`

The manifest records, for each entry:

- a stable entry id
- whether it is a `theory`, `function`, or `method`
- the originating module path
- the package name
- the exported symbol name
- the owner type for methods
- the normalized signature string
- the payload section id inside `bundle.smt2`
- the count of exported `#require` / `#assure` obligations
- and the attached theory ids for contracted functions/methods

The payload contract is intentionally source-oriented and portable:

- `payload_solver = "z3"`
- `payload_format = "smt2"`
- `payload_encoding = "source"`

That is, Silk currently distributes normalized SMT-LIB2 source, not a
solver-private binary snapshot. This keeps the artifact inspectable, stable
across hosts, and suitable for replay with an external `z3 -smt2 ...` tool.

Installed packages copy the same bundle under the package root:

- `share/silk/formal/<artifact-relative-path>/manifest.json`
- `share/silk/formal/<artifact-relative-path>/bundle.smt2`

This makes Formal Silk metadata distributable alongside definitions, headers,
and native artifacts, while keeping source-visible theory/prototype declarations
as the authoritative import-time verification surface. Installed package
loading and `silk package inspect` discover these bundle paths so downstream
tooling can locate the exported Formal Silk payload directly.

## Generics (Monomorphized)

This document specifies Silk’s parameterized type and declaration syntax.

Silk’s generics are **compile-time** features:
parameterized declarations are **monomorphized** into concrete, fully specified
types and functions at build time (there are no runtime type parameters).

Diagnostics rule:

- Generics are part of the Silk language design, not an out-of-language
 extension.
- When the current compiler rejects a generic form with `E2016`, that means the
 implementation is incomplete for that generic form, not that the language
 forbids generics.

Note: `Option(T)` is a special-case surface form that is treated as sugar for
`T?` in the Supported forms (see [`Optional`](/silk/docs/language/optional/)). This is still
accepted in the Supported forms even as general-purpose type-parameter generics are
implemented.

### Overview

Silk supports parameterized declarations by allowing a parameter list on
`struct`, `interface`, `enum`, `impl`, and `fn` declarations.

In Silk currently:

- **Supported**: type parameters (`T`) and const parameters (`N: usize`) on
 `struct`/`interface`/`enum`/`impl`, type application in type positions
 (`Name(args...)`), and generic functions using a compile-time parameter
 section separated by `;` in the signature (`fn id(T; x: T) -> T`) (including
 generic methods in `impl` blocks).

### Declaration syntax

#### Parameter lists

The parameter list uses parentheses:

```silk
struct Vector(T) {
  // ...
}
```

Rules:

- `T` is a type parameter.
- Type parameters may provide a default type argument using `=`:
 - `interface Serialize(S = string) { ... }`
 - defaults must be **trailing** (once a parameter has a default, all
 subsequent parameters must also have defaults).
- Const parameters are written with an explicit type annotation:
 - `N: usize`
 - const parameters are compile-time integer values and may be used in type
 positions such as array lengths (`T[N]`) and type applications.
- The parameter list may be empty (though it is uncommon): `struct Foo() { ... }`.

Supported declaration forms:

- `struct Name(T, ...) { ... }`
- `interface Name(T, ...) { ... }`
- `enum Name(T, ...) { ... }`
- `type Alias(T, ...) = Target(T, ...);`
- `impl Name(T, ...) { ... }`
- `impl Name(T, ...) as InterfaceName(T, ...) { ... }`

#### Applying parameters in types

Parameterized types are referenced using the same call-like syntax in type
positions:

```silk
Vector(int)
Mutex(Account)
Result(int, string)
```

### Applied Generic Type Qualifiers

Applied generic type names may be used directly as static member qualifiers
after their type arguments are fully known:

```silk
import { Buffer } from "std/buffer";

fn main () -> int {
  let b = Buffer(u8).empty();
  return b.capacity() as int;
}
```

This form is equivalent to introducing an explicit alias for the instantiation
and then calling the member through that alias:

```silk
type BufferOfU8 = Buffer(u8);
let b = BufferOfU8.empty();
```

The qualifier applies the same type-argument rules as type positions: type
arguments must resolve to known types, const arguments must be integer
compile-time arguments, and named imports of generic templates participate in
the same lookup rules as imported non-generic types. A generic type whose
parameters all have defaults may use `Name().member(...)` or the bare
`Name.member(...)` qualifier form; both forms instantiate the defaults before
member lookup.

### Generic enums (tagged unions)

Enums may be parameterized and are monomorphized like generic structs.

For enum constructors and match arms, callers typically introduce a local alias
for an instantiation and then use that alias to construct and match variants:

```silk
enum Result(T, E) {
  Ok(T),
  Err(E),
}

type R = Result(int, string);

fn main () -> int {
  let x: R = R::Ok(123);
  return match x {
    R::Ok(v) => v,
    R::Err(_) => 0,
  };
}
```

Default type arguments:

- When a parameterized declaration provides default type arguments, a use site
 may omit **trailing** arguments that have defaults.
- If all parameters have defaults, the type may be referenced as `Name` or
 `Name()` (both are equivalent to applying the defaults).

Type arguments may be:

- types (e.g. `int`, `&Foo`, `Option(string)`).
- integer literals for const parameters (e.g. `Vector(u8, 1024)`).

Const arguments are compile-time integer literals and participate in
monomorphization identity.

### Interfaces and applied interface types

Interfaces may be generic:

```silk
interface Channel(T) {
  fn send(value: T) -> bool;
  fn recv() -> T?;
}
```

An `impl ... as ...` conformance may apply type arguments to the interface:

```silk
struct QueueU8 {
  // ...
}

impl QueueU8 as Channel(u8) {
  // ...
}
```

Rule: when a generic interface is referenced in a concrete `impl X as I(...)`,
all interface type arguments must be fully known at that conformance site. The
only exception is when the conformance itself is generic (type parameters are
in scope), for example:

```silk
struct Data(T) { /* ... */ }
interface DataInterface(T) { /* ... */ }

impl Data(T) as DataInterface(T) {
  // ...
}
```

### Impl blocks for generic structs

If a struct is declared with type parameters, its impl blocks must also declare
those parameters:

```silk
struct Data(T) { /* ... */ }

// OK:
impl Data(T) { /* ... */ }

// Error:
// impl Data { /* ... */ }
```

This rule keeps method receiver typing unambiguous and makes monomorphization
explicit.

### Functions

Generic functions require a way to declare type/const parameters distinct from
value parameters. The initial parsed surface form is:

```silk
fn get_first(T, N: usize; v: &T[N]) -> T {
  // ...
}
```

Where the `;` separates generic parameters from value parameters inside the
function’s parameter list.

#### Alternate (Go-like) function declaration syntax

Silk also supports a Go-like generic header form:

```silk
fn (T, N: usize) get_first (v: &T[N]) -> T {
  // ...
}
```

This is sugar for the `;` form above; the compiler records the same generic
parameter list (`T, N: usize`) either way.

Rules:

- At most one generic parameter list may be provided:
 - either `fn name(T; ...)`,
 - or `fn (T) name (...)`.

#### Call syntax for generic functions

Calls mirror the signature split:

```silk
let x: int = get_first(int, 4; &xs);
```

Rules:

- the `;` separates compile-time arguments from runtime value arguments,
- compile-time arguments are a comma-separated list of:
 - type arguments (`int`, `&Foo`, `Option(string)`),
 - and integer literals for const arguments,
- runtime arguments are ordinary expressions.
- the compile-time argument list may be empty when defaults supply all generic
 parameters, for example `id_default(; 1)` uses the default type argument for
 `T` in `fn id_default(T = int; x: T) -> T`.

#### Call-site type inference (omitting `;`)

When a call does not include the generic separator (`;`), the compiler may
infer type and const arguments from runtime arguments:

```silk
fn (X, Y) add (x: X, y: Y) -> X {
  return x + y as X;
}

let a = add(1.123, 2); // infers X = f64, Y = int
```

Rules:

- Both **type** parameters (`T`) and **const** parameters (`N: usize`) may be
 inferred.
- Inference is driven by the runtime argument expressions and any types that
 are known at the call site:
 - literals (`123`, `1.0`, `"hi"`, `'a'`, `true`),
 - struct literals (`Point { ... }`),
 - explicit casts (`expr as Type`),
 - and name expressions (`x`) when the binding’s type is known (from an
 annotation like `let x: T = ...` or from a simple initializer like a
 literal/struct literal).
- Const parameters are inferred only from type structure:
 - array lengths (`T[N]`),
 - and const arguments in applied types (`Buffer(T, N)`),
 when the corresponding runtime argument type provides a concrete value.
- When inference cannot determine a type argument, compilation fails with an
 actionable diagnostic. Disambiguate by either:
 - inserting `as` casts on runtime arguments, or
 - using the explicit `;` form (`add(f64, int; 1.123, 2)`).
 When inference cannot determine a const argument, disambiguate by using the
 explicit `;` form (`take_buf(4; buf)`).

### Implementation notes

- Monomorphization produces a concrete instance for each referenced
 instantiation `Name(args...)`.
- Type names share one namespace within a `package`: `struct`, `interface`,
 `enum`, `error`, and `type` declarations may not reuse the same name.
- Name conflicts across generic arities are rejected (for example, `struct Foo`
 and `struct Foo(T)` cannot both exist in the same package namespace).
- Const parameters are currently restricted to integer primitive types; const
 values are usable in type positions (for example `T[N]`) but are not yet
 exposed as runtime values.

## Dependent Types (Const Parameters and Type-Level Computation)

This document specifies Silk’s intended support for “dependent-type-like”
patterns where types mention compile-time values (most notably integers).

The front-end can parse and preserve:

- declaration parameter lists on `struct`, `impl`, and `fn`,
- type application in type positions (for example `VectorN(int, 1024)`),

but the compiler does not yet implement constraint checking, inference, or
monomorphization/code generation for parameterized declarations. In the current
compiler subset, generic parameter lists and applied types are rejected during
type checking ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2016`). See
[`Generics (Monomorphized)`](/silk/docs/language/generics/).

### Const Parameters

Const parameters are compile-time values that appear in parameter lists with a
type annotation:

```silk
struct VectorN(T, N: int) { /* ... */ }
```

Where:

- `T` is a type parameter, and
- `N: int` is a const parameter whose value must be known at compile time.

The initial supported const-argument form in type application is integer
literals (for example `VectorN(int, 1024)`).

### Type-Level Computation

The language intends to allow certain expressions over const parameters in type
positions (design-only):

```silk
fn concat(T, M: int, N: int; a: VectorN(T, M), b: VectorN(T, N)) -> VectorN(T, M + N) {
  // ...
}
```

This requires:

- a notion of const expressions at the type level,
- evaluation rules (and overflow behavior) for those expressions,
- and a compilation strategy (typically monomorphization) that produces concrete
 layouts and code for each instantiated type.

### Function Parameter Lists (CT/RT Split)

Generic functions use a single parameter list split by a top-level `;` inside
the parentheses:

```silk
fn id(T; x: T) -> T { return x; }
fn g(T;) -> T { /* CT-only, rare */ }
fn h(x: int) -> int { return x; } // RT-only
```

This split is parsed and preserved by the front-end, but generic functions are
rejected by the current checker until monomorphization is implemented
([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2016`).

### Relationship to Arrays and Collections

Const parameters are intended to power:

- fixed-size arrays (`T[N]`),
- dependent-length collections (for example `VectorN(T, N)`),
- and compile-time-checked indexing/slicing APIs.

These features require additional language and runtime support beyond the
current implementation.

## Struct Requirements (`#require`)

Use `#require` on a `struct` to state requirements that must hold for all
values constructed for that struct type.

Example:

```silk
#require id > 0;
struct User {
  id: int,
}

#assure result > 0;
fn get_id () -> int {
  return 1;
}

fn main () -> int {
  // This fails verification:
  // let bad = User{ id: 0 };

  let user = User{ id: get_id() };
  return user.id;
}
```

Rules (Supported forms):

- `#require` expressions on a `struct` may reference that struct's fields by
 name.
- When Formal Silk syntax is present in the compiled module set, the verifier
 proves these requirements at struct literal construction sites (`Type{ ... }`
 and `new Type{ ... }`). If any requirement cannot be proven, compilation
 fails with `E3006`.
- Failed struct-requirement diagnostics include the rejected predicate and the
 referenced field initializers/defaults that were used for the construction
 proof.

See `Formal Silk`.

## Function Disciplines (`pure`, `task`, `async`)

This document specifies Silk’s intended “function discipline” system: how
functions declare whether they are pure, asynchronous, or safe to run as
parallel tasks.

Const functions (`const fn`) are specified separately in
`Const Functions (`const fn`)`. The `const` modifier is orthogonal to the
discipline system described here (a `const fn` may also be declared `pure`).

, but Silk currently now implements
`pure fn` parsing and a strict purity checker. Concurrency disciplines (`task` /
`async`) are parsed and `Task(T)` / `Promise(T)` handles plus `yield` (task
values) and `await` (promise values) are implemented in the Supported forms
(`await Task(T)` is rejected). On the hosted `linux/x86_64` target, the compiler
now ships a bring-up async runtime (single-threaded executor + stackful
coroutines in `libsilk_rt`) so `await` can suspend and resume without blocking
the OS thread. A compiler state-machine coroutine transform, structured
concurrency scope semantics, and task-safety (`Send`/`Sync`)-like rules remain
future work. See [`Concurrency`](/silk/docs/language/concurrency/) for the concurrency model and
implementation status.

### Overview

The language design distinguishes:

- `fn` — normal function (may perform effects; blocking).
- `pure fn` — function with no observable side effects (referentially
 transparent).
- `task fn` — function safe to execute on a worker pool as a parallel task.
- `async fn` — function that may suspend at `await` points (returns an
 awaitable).
- `async task fn` — async function executed as a separate task (self-contained
 worker).

### Intended Call Rules (Design)

The checker is expected to enforce:

- `pure` code may call only `pure` code (and cannot perform I/O or mutation
 outside local, non-escaping temporaries).
- `task` code may call `task` and `pure` code, and must satisfy task-safety
 rules for captured/argument data.
- `async` code may `await` other async operations; it may call `pure` code and
 may offload blocking work via explicit adapters .

Crossing discipline boundaries is intended to be explicit and diagnostic-driven
(for example suggesting the correct adapter/intrinsic).

### Standard Intrinsics

The standard library is expected to provide typed adapters to cross boundaries
safely (names and exact signatures are design work):

- lifting sync work onto a task pool,
- presenting a task as an async operation,
- running blocking work from async without stalling the event loop,
- structured spawn/join primitives.

These APIs are not yet present in the in-tree [`std/`](https://github.com/oro-computer/silk/tree/master/std/) implementation.

### Implementation Notes

Today:

- `pure fn` is parsed and checked (Supported forms):
 - a `pure fn` may call only `pure` functions; `ext` is treated as impure,
 - the checker also supports purity inference (“auto-pure”) for ordinary `fn`
 declarations and `impl` methods:
 - when an unannotated function/method has an eligible signature and its
 body satisfies the purity rules, it is treated as `pure` for call
 checking, and may be called from `pure` code,
 - functions/methods with `&T` parameters are not eligible for inference
 (explicit `pure fn` remains supported for `&T` parameters in the current
 subset),
 - `pure` cannot be combined with `task` or `async` in the Supported forms,
 - a `pure fn` may not have `mut` parameters,
 - a `pure fn` may not declare mutable locals (`var` or `let mut`) and may not
 perform mutation via assignment,
 - a `pure fn` may not allocate (`new`) in the Supported forms,
 - a `pure fn` may not have a typed-error contract (`-> T | Error...`) and may
 not contain `panic` statements.
- `task fn`, `async fn`, and `async task fn` are parsed and preserved in the AST.
- Calls across disciplines are now reflected in expression types:
 - calling a `task fn` yields `Task(T)`,
 - calling an `async fn` yields `Promise(T)`,
 - calling an `async task fn` yields `Promise(Task(T))`,
 - `yield` supports both statement and expression forms:
 - statement forms (only inside an enclosing `task fn` / `async task fn`):
 - `yield v;` sends a value to the task’s yield stream,
 - `yield * t;` forwards all values from `t` into the task’s yield stream,
 - expression forms:
 - `yield t` receives the next yielded value from `t`,
 - `yield * t` drains/collects remaining values from `t` into `T[]`,
 - `await` unwraps `Promise(T)` and yields `T` (`await Task(T)` is rejected),
 and `await * ps` unwraps `Promise(T)[]` into `T[]`.
- `await <expr>` and `async { ... }` / `task { ... }` blocks are enforced as
 **async-only** constructs:
 - `await` is only permitted inside `async` functions (including `async task fn`),
 - `async { ... }` / `task { ... }` blocks are only permitted inside `async` functions.
- `yield <expr>` is enforced as a **task-only** construct:
 - `yield` expression forms (`yield t` / `yield * t`) are permitted only inside
 `task` functions (`task fn` / `async task fn`) and inside `task { ... }` /
 `task loop { ... }` blocks.
 - `yield` statement forms (`yield v;` / `yield * t;`) require an enclosing
 `task fn` / `async task fn`.
- Lowering/codegen implements `task` execution using OS threads on `linux/x86_64`
 and implements `yield`/`yield *` for task values plus `await` for promises.
 - By default, each `task fn` call is scheduled on the global task pool.
 - `attr(task=thread)` forces a dedicated OS thread per call.
 - On hosted `linux/x86_64`, the compiler ships a bring-up async runtime
 (`the implementation`) so `await` is a true suspension point:
 - awaiting a pending `Promise(T)` parks the current fiber and allows other
 runnable fibers to execute (it does not block the OS thread),
 - outside the executor owner thread (including when no executor is active),
 `await` blocks the OS thread until the promise resolves.
 - `yield` / `yield *` waits on task output use the same hosted fd-wait
 runtime path, so waiting for task values from executor-driven async code
 suspends the current coroutine instead of blocking the executor owner
 thread.
 - the long-term design remains a compiler coroutine transform plus a stable
 [`std::runtime::event_loop`](/silk/docs/std/runtime-event_loop/) API; see [`Async Runtime (Hosted)`](/silk/docs/compiler/async-runtime/).
 - `async { ... }` / `task { ... }` blocks remain lexical scopes, but scope
 exit is now runtime-backed for live handle cleanup:
 - live `Promise(T)` bindings are awaited/destroyed,
 - live `Task(T)` bindings are drained/destroyed (joining dedicated-thread
 tasks; pooled/default tasks skip the join),
 - and the same cleanup runs on overwrite and early scope exit.
- Function types are parsed in type positions (notably for `ext`).
- Function expressions are implemented as first-class function values:
 - `fn (x: int) -> x + 1` (expression body),
 - `fn (x: int) -> int { return x + 1; }` (block body).
 - `fn (x: int) { ... }` (block body, implicit `void` result).
 - Function expressions may not declare `&Struct` parameters; only single-slot
 scalar `&T` parameters (for example `&int`) are supported in the Supported forms.
 - Function expressions are eligible for purity inference (“auto-pure”):
 - when the body satisfies the `pure` rules, the function value is treated
 as `pure` for call checking (it may be called from `pure` code),
 - otherwise the function value is impure and may not be called from `pure`
 code.
 - Capturing closures are supported as a subset:
 - a function expression may reference immutable locals/parameters from an
 enclosing scope,
 - captures are by-value copies into a heap environment (scalar-only in the
 Supported forms),
 - forming captures inside `pure` code is rejected (capture environments
 allocate),
 - capturing closures are also eligible for purity inference (a closure
 whose body satisfies the `pure` rules is callable from `pure` code).
 - Function values (both non-capturing and capturing) are supported end-to-end:
 they may be passed, returned, stored, and called indirectly.

## Const Functions (`const fn`)

### Notes

- Parser: **implemented**
- Checker rules: **implemented** (Supported forms)
- Compile-time evaluation: **implemented** (Supported forms)

This document defines the surface syntax and semantics for compile-time
functions.

In Silk currently, `const fn` (and `const pure fn`) can be called
from `const` binding initializers when all arguments and the result are
compile-time values (scalar values and eligible POD `struct` values).

### Summary

Silk supports compile-time evaluation of certain expressions to produce
compile-time constants. `const fn` (and `const pure fn`) declarations opt a
function into this compile-time evaluation system so that it can be called from
compile-time contexts (for example, a `const` binding initializer).

### Syntax

`const` is a function modifier:

```silk
const fn add (a: int, b: int) -> int {
  return a + b;
}

const pure fn add2 (a: int, b: int) -> int {
  return a + b;
}
```

Notes:

- `const pure fn` is simply a `const fn` that also opts into the `pure` rules
 (see `Function Disciplines (`pure`, `task`, `async`)`).
- `const fn` is a **compile-time-only** function:
 - it may be called only from compile-time contexts (for example `const`
 initializers and Formal Silk specifications),
 - it is not emitted as a runtime/linkable symbol in executable, object, or
 library outputs.

### Compile-Time Values

In this document, a “compile-time value” is a value that the compiler can
produce and manipulate during compile-time evaluation.

Supported forms (Implementation target):

- scalar primitives:
 - `bool`
 - fixed-width integers (`i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`)
 - `int`
 - `f32`, `f64`
 - `char`
 - `Instant`, `Duration`

- compile-time structures (POD `struct` values):
 - a non-opaque `struct` type,
 - with 1+ fields,
 - where every field type is a compile-time scalar value type, and
 - the struct does not require ownership tracking (`Drop`).

 These values are lowered as a flattened sequence of scalar slots in
 declaration order. They may be returned from and passed to `const fn`, and
 used in `const` initializers.

Planned (not yet supported for `const fn` in the Supported forms):

- `string` values (string literals are supported directly in `const` bindings),
- aggregate values beyond compile-time POD structs (enum/optional/slice/array),
- function values as compile-time values (for higher-order const evaluation).

### Rules

The Supported forms defines a deliberately small “const-eval VM” surface. A
`const fn` must fit within this surface.

#### Signature rules

In the Supported forms, a `const fn`:

- must not be `task` or `async`,
- must not declare a typed-error contract (`-> T | ErrorType...`),
- must have a non-`void` return type that is a compile-time value type,
- must have parameters whose types are compile-time value types.

#### Body rules

In the Supported forms, a `const fn`:

- must not allocate (`new`) and must not use regions/`with`,
- must not contain `panic` statements,
- must not declare `const` local bindings,
- may call only other `const fn` declarations,
- is restricted to a small expression subset over scalar values:
 - literals and local names (parameters and `let` bindings; no global `const` reads in the Supported forms),
 - `as` casts between supported scalar types,
 - unary operators: `-`, `~`, `!`,
 - binary operators:
 - arithmetic: `+`, `-`, `*` (division/modulo are not part of the const-eval subset),
 - bitwise: `&`, `|`, `^`, `<<`, `>>`,
 - comparisons: `==`, `!=`, `<`, `<=`, `>`, `>=`,
 - `if` expressions (`if cond { a } else { b }`).
 - assignments to local names: `=`, `+=`, `-=`, `*=`, plus `++`/`--`.

Additionally, `const fn` bodies may construct and use compile-time POD `struct`
values:

- struct literals (`T{ field: expr, ... }`) when `T` is a compile-time structure
 and every field expression is compile-time evaluable,
- field access (`value.field`) on compile-time structures, and
- assignment to local struct-typed names (copies the flattened scalar slots).

Control flow is limited to:

- `if` / `else` statements,
- [`while`](/silk/wiki/language/flow-while/) loops with boolean conditions,
- `break` / `continue`,
- `return` statements.

### Calling Const Functions

The initial intended compile-time use site is `const` bindings:

```silk
const fn add (a: int, b: int) -> int {
  return a + b;
}

const answer: int = add(20, 22);

fn main () -> int {
  return answer;
}
```

Const functions may also be imported/exported across modules/packages like
runtime declarations, but they are still compile-time-only: importing a `const
fn` does not make it callable from runtime code.

### “No Static Storage” Rule

Const functions do not create new static storage. In particular:

- compile-time execution may compute scalar values and fold them into constants,
- compile-time execution must not allocate heap memory,
- compile-time execution must not synthesize new global read-only data (for
 example, it cannot build a new string at compile time in the Supported forms).

String literals are still backed by read-only static storage, but they are
introduced by the literal syntax itself (see [`String Literals`](/silk/docs/language/literals-string/)), not by the
`const fn` evaluator.

### Evaluation Limits

Compile-time evaluation must terminate. The compiler enforces an
instruction budget and a call-depth budget when executing `const fn` bodies at
compile time; evaluation that exceeds these budgets is rejected as not
compile-time evaluable.

## Varargs (Variable Arguments)

Silk supports declaring functions that accept a variable number of trailing
arguments (“varargs”). This is used heavily by [`std::io::print`](/silk/docs/std/io/) /
[`std::io::println`](/silk/docs/std/io/) for formatted output.

### Syntax

Varargs are declared by prefixing the final parameter with `...`:

```silk
fn log (fmt: string, ...args: std::fmt::Arg) -> void {
  std::io::println(fmt, args);
}
```

Rules:

- A function may declare **at most one** varargs parameter.
- The varargs parameter must be **the final** parameter in the list.
- The varargs parameter must have an explicit **type annotation**.
- Varargs parameters are **not** permitted to be `mut` in the Supported forms.
- Varargs parameters may not have a default expression (`= ...`) in the current
 subset.
- The same trailing-varargs form may be used in interface method signatures,
 and `impl ... as ...` / `module ... as ...` conformance compares the varargs
 marker as part of the required signature.

### Call Semantics

At call sites:

- All non-varargs parameters are matched positionally as usual.
- Any additional arguments are collected into the varargs parameter.

Example:

```silk
std::io::println("hello {s} answer={d}", "world", 42);
```

Here `"world"` and `42` become varargs elements.

#### Forwarding

Because Silk does not yet have a general “spread” operator for calls, the
compiler supports forwarding a varargs pack when you pass a
varargs binding as the final argument.

```silk
fn log (fmt: string, ...args: std::fmt::Arg) -> void {
  // `args` is forwarded as-is to `println`.
  std::io::println(fmt, args);
}
```

This is primarily intended for building wrappers that preserve the caller’s
argument list without repacking.

### Indexing and Iteration

Varargs packs expose a `len: int` field and support array-style indexing.

```silk
fn first_or_none (...args: string) -> string? {
  if args.len <= 0 {
    return None;
  }
  return Some(args[0]);
}
```

Indexing `args[i]` traps when `i` is out of bounds (`i < 0` or `i >= args.len`),
matching slice/array indexing rules in the backend.

To iterate, use `len` + indexing:

```silk
var i: int = 0;
while i < args.len {
  let v = args[i];
  i = i + 1;
}
```

### Representation

In the compiler, a varargs parameter is lowered as a
fixed-size **pack value** with:

- `len: int` — the number of provided varargs arguments.
- `a0 .. a(N-1)` — storage for up to `N` arguments (implementation-defined,
 currently `N = 32`).

The pack is passed by value using the same “flattened scalar slot” ABI as other
POD structs.

Notes:

- `args[i]` performs bounds checks against `len` and traps on out-of-bounds.
- Directly reading `aK` is not bounds-checked; when `K >= len`, the value is
 unspecified. Prefer `args[i]` unless you are working with the raw
 representation intentionally.
- Calls supplying more than `N` varargs arguments are rejected.

### FFI (C Variadics)

This document is about Silk varargs. C variadic functions declared via `ext`
(`printf`-style `...`) are a separate concern and are **not** implemented yet
in the Supported forms.

## Language Spec Conventions

This document defines conventions used across this specification. It exists to
keep the language specification consistent and easy to navigate for both:

- first-time readers learning Silk, and
- returning readers looking up precise rules.

See also: `Silk Language Guide (Index)` for recommended reading paths.

### Document Structure (Recommended)

Concept documents should be structured so readers can answer, quickly:

- “What is this feature for?”
- “What syntax does the compiler accept?”
- “What are the rules and edge cases?”
- “What works in the current compiler today?”

Recommended sections:

1. **One-paragraph summary**
2. **Implementation status** (if the concept is Implemented)
3. **Surface syntax**
4. **Semantics** (evaluation order, scoping, control-flow behavior)
5. **Type checking rules** (static requirements and diagnostics)
6. **Examples**
 - minimal examples (smallest correct usage)
 - realistic examples (how the feature is used in real code)
7. **Common pitfalls**
8. **Related documents**

Not every concept needs every section, but the goal is that a reader should
never have to infer critical rules from examples.

### “Implementation status” Format

When a feature is not fully implemented end-to-end, the concept doc should
include an explicit “Implementation status” section near the top.

Use concrete statements, not vague language. Prefer describing support in
these layers:

- Parser: which surface forms are accepted.
- Checker: which typing/validation rules are enforced.
- Lowering/backends: which forms code-generate end-to-end on supported targets.
- C ABI / FFI: whether the feature is permitted at exported boundaries.

When something is rejected in the Supported forms, include the diagnostic code
from [`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/) when one exists.

### Examples

Examples in language docs should follow these rules:

- Use 2-space indentation and spaces only.
- Prefer complete, runnable snippets when possible:

  ```silk
  fn main () -> int {
    return 0;
  }
  ```

- When an example requires multiple files, label them with comments, e.g.:

  ```silk
  // app/main.slk
  package app;
  ```

- When an example is intentionally invalid (to show a rule), label it and
 mention the expected diagnostic.

### Terminology

These terms are used consistently across the spec:

- **Expression**: a construct that produces a value and has a type.
- **Statement**: a construct evaluated for its effects and sequencing.
- **Block**: `{ stmt* }`, a scope boundary and the unit of structured control
 flow. (Whether blocks are also expressions depends on the concept; docs must
 be explicit.)
- **current implementation**: the set of features that parse, type-check, and
 code-generate end-to-end today.

### Cross-References

When describing a rule, link to the most relevant concept doc rather than
restating it everywhere. Common cross-links include:

- [`Formal Grammar Spec`](/silk/docs/language/grammar/) for the exact accepted syntax,
- `Types` for type-system rules and special cases,
- [`Mutability`](/silk/docs/language/mutability/) for `mut` and borrowing rules,
- [`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/) for error codes,
- `the implementation status` for a high-level implementation snapshot.

## Operators

This document summarizes the operator set and precedence for Silk.

### Operator Set

The language includes the following operators and delimiters:

- Assignment and compound assignment: `=`, `+=`, `-=`, `*=`, `/=`.
- Increment/decrement: `++`, `--` (prefix and postfix).
- Arithmetic: `+`, `-`, `*`, `/`, `%`.
 - Currently:
 - integer operands support `+`, `-`, `*`, `/`, and `%`,
 - floating-point operands (`f32`/`f64`) support `+`, `-`, `*`, and `/`
 (no `%`).
 - unary `-x` is supported for both integer and floating-point operands.
 - time types support a small arithmetic subset:
 - `Duration + Duration`, `Duration - Duration`, and unary `-Duration`,
 - `Instant + Duration`, `Duration + Instant`, `Instant - Duration`,
 - and `Instant - Instant` (producing a `Duration`).
 - `range` supports shifting by an `int` offset:
 - `range + int`, `range - int`, `int + range`.
- Bitwise: `&`, `|`, `^`, `~`, `<<`, `>>`.
 - Currently, bitwise operators are defined for
 integer operands (`int` and the fixed-width integer types):
 - `&`, `|`, `^` perform bitwise AND/OR/XOR on two integer values of the
 same type and produce a result of that same type.
 - `~x` performs bitwise NOT on an integer value and produces a result of
 that same type.
 - `<<`, `>>` shift the left-hand integer operand by an integer shift
 amount of the same type; `>>` uses an arithmetic right shift for
 signed integers (`i*`/`int`) and a logical right shift for unsigned
 integers (`u*`).
- Comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`.
 - Currently, comparisons are defined for both integer
 operands and floating-point operands of the same type.
 - In the backend, `==` and `!=` are also defined for `bool`
 operands.
 - In the backend, comparisons are also defined for `Duration`
 and `Instant` when both operands have the same time type.
 - In the backend, `==` and `!=` are also defined for `string`
 operands, comparing the underlying UTF-8 byte sequences for equality
 (length check + bytewise compare).
 - In the backend, ordered comparisons over `string`
 (`<`, `<=`, `>`, `>=`) are defined as bytewise lexicographic ordering over
 the underlying UTF-8 byte sequences (unsigned byte comparison, with
 shorter-prefix ordering when one string is a prefix of the other).
 - In the backend, `==` and `!=` are also defined for supported
 optional values (`T?`, `string?`, optionals of the supported `struct`
 subset, and nested optionals `T??`):
 - `None == None` is true,
 - `Some(x) == Some(y)` compares the payload values for equality (recursively
 for nested optionals),
 - and `!=` is the logical negation of `==`.
 - Currently, `None` and `Some(...)` can appear in
 equality expressions when the other operand has an optional type (for
 example `opt == None` and `opt == Some(x)`), using that other operand’s
 type to infer the optional payload type.
 - In the backend, `==` and `!=` are also defined for the
 supported `struct` subset (see [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)),
 performing slot-wise equality over the lowered scalar slots (including
 embedded strings, nested structs, and optionals); float slots use IEEE-754
 equality semantics. Ordered struct comparisons are not implemented.
 - Float comparisons follow IEEE-754 semantics: `NaN` compares unequal to
 everything (including itself), and ordered comparisons (`<`, `<=`, `>`,
 `>=`) are false when either operand is `NaN`.
- Logical: `!`, `&&`, `||`.
 - Currently:
 - `!` is supported for `bool` operands.
- Member and scope: `.`, `::`, `?.`.
- Currently:
 - `.` and `::` are supported,
 - and `?.` is supported for optional chaining on the supported `struct` subset:
 - `opt?.field` yields `FieldType?`,
 - `opt?.method(args...)` yields `ResultType?`.
 See [`Optional`](/silk/docs/language/optional/).
- Casts: `as` and `as raw` (postfix).
 - Syntax:
 - numeric/shape cast: `<expr> as <Type>`,
 - raw bit-cast: `<expr> as raw <Type>`.
 - `as` is an explicit, potentially lossy conversion operator intended for
 primitive numeric conversions (see “Casts (`as`)” below).
 - `as raw` is an explicit bit reinterpretation operator intended for
 generic storage/marshalling of scalar values (see “Raw casts (`as raw`)”
 below).
- Typed error propagation: `?` (postfix).
 - Syntax: `<call_expr>?`.
 - This propagates typed errors from an error-producing call to the enclosing
 function; see `Typed Errors (`error`, `panic`, and `T | ErrorType...`)`.
- Ranges / varargs delimiters: `..`, `..=`, `...`.
 - `..` and `..=` form range literals of type `range` (see `Types`).
 - `...` is the varargs/rest marker (see [`Varargs (Variable Arguments)`](/silk/docs/language/varargs/)).
- Other punctuation: `?`, `??`, `->`, `=>`, `,`, `;`, `(`, `)`, `{`, `}`, `[`, `]`, `_`, `:`.
 - Currently, `??` is supported for:
 - optionals in the backend (including scalar, `string`, and
 the current `struct` subset, plus nested optionals in the supported
 payload subset; see [`Optional`](/silk/docs/language/optional/)), and
 - recoverable `Result`-like values, where `result ?? fallback` yields the
 `Ok(...)` payload and evaluates `fallback` only for `Err(...)`
 ([`std::result`](/silk/docs/std/result/)), and
 - ordinary named enums with exactly two declared variants, where the first
 declared variant is the “success” arm:
 - if the first variant is unit, `value ?? fallback` yields that enum
 value,
 - if the first variant carries exactly one payload, it yields that
 payload,
 - and if the value is the second variant, `fallback` is evaluated.
 - The right-hand side may be either:
 - an ordinary fallback expression, or
 - one of the narrow terminal control-flow forms accepted only after
 `??`:
 - `value ?? return expr`
 - `value ?? break`
 - `value ?? continue`
 - `return`, `break`, and `continue` keep their normal statement validity
 rules:
 - `return` must match the enclosing function result type,
 - `break` and `continue` are only valid inside loops.
 - This does not make those control-flow forms general expressions
 elsewhere; the grammar extension is specific to the right-hand side of
 `??`.
 The `?` token is used both in type annotations (`T?`) and as the postfix
 typed error propagation operator for error-producing calls (`call()?`; see
 `Typed Errors (`error`, `panic`, and `T | ErrorType...`)`).

The lexer and parser must recognize these tokens exactly as specified, and precedence/associativity must match the formal grammar.

### Assignment

Assignment updates an existing binding (an lvalue). Assignment is “statement-like”:
it is parsed as an expression but has type `void` and is intended to appear as an
expression statement.

#### `=`

`x = expr` evaluates `expr` and stores the resulting value into `x`.

Rules:

- The left-hand side must be an assignable lvalue. In the Supported forms, it may be:
 - an identifier that refers to a local `let mut` binding, or
 - a struct field lvalue `name.field` (or nested field lvalue `name.field1.field2...`)
 where `name` is either:
 - a local `let mut` binding of a supported POD `struct`, or
 - a `mut` borrowed reference parameter (`mut name: &Struct`).
 In the backend, nested field assignment is supported only
 when the **leaf field** lowers to a single scalar slot (for example `bool`,
 integer scalars, and `f32`/`f64`).
- Identifier lvalues must refer to `let mut` local bindings.
- The type of `expr` must match the binding’s type.
- The assignment expression has type `void`.

#### Compound assignment (`+=`, `-=`, `*=`, `/=`)

Compound assignments are shorthand for “read-modify-write”:

- `x += y` is equivalent to `x = x + y` (and similarly for `-=`/`*=`/`/=`),
 with `y` evaluated exactly once.

Rules:

- The left-hand side must be an assignable lvalue (as described above for `=`).
- In the Supported forms, compound assignments are supported only for numeric
 scalar types (integers and `f32`/`f64`), including numeric struct fields.
- The compound assignment expression has type `void`.

### Increment and Decrement (`++` / `--`)

`++x`, `x++`, `--x`, and `x--` increment or decrement an existing binding by
`1`.

In Silk, increment/decrement expressions are “statement-like”: they have type
`void` and are intended to appear only as expression statements.

Rules:

- The operand must be an assignable lvalue (the same lvalue rules as `=`).
- The operand type must be an integer scalar type (`int`, `i8`, `u8`, `i16`,
 `u16`, `i32`, `u32`, `i64`, `u64`, `size`, `usize`). (`isize` is accepted as
 an alias for `size`.)
- Prefix and postfix forms are equivalent in Silk (both update the binding and
 produce `void`).
- Conceptual desugaring:
 - `x++` and `++x` are equivalent to `x += 1;`
 - `x--` and `--x` are equivalent to `x -= 1;`

### `sizeof`

`sizeof <operand>` produces the size of a type or value in bytes.

For string values, `sizeof(value)` is the canonical way to read the UTF-8 byte
length for FFI pointer/length pairs:

```silk
let title_ptr = title as raw u64;
let title_len = sizeof(title);
```

The operand may be a direct name, a field access, or another expression that
evaluates to `string`, for example `sizeof(options.title)` or
`sizeof(make_title())`.

Use an explicit cast only when calling an API whose contract is intentionally
signed or narrower than `usize`.

Result type:

- `sizeof` always returns `usize`.

Evaluation mode:

- When the operand is a **type name** (a primitive type, `struct`/`enum` name,
 type alias, or qualified type name), `sizeof` is a compile-time constant.
- When the operand is a **compile-time constant value** (literals and other
 const-evaluable expressions), `sizeof` is a compile-time constant.
- When the operand is a **runtime value**, `sizeof` is evaluated at runtime.

`Sized` integration:

- Implemented (partial): `sizeof <string value>` produces the string’s **byte
 length** (as `usize`). This reads the current string ABI layout
 (`{ ptr: u64, len: i64 }`) through the reserved intrinsic
 `__silk_string_len`; [`std::runtime::mem::string_len`](/silk/docs/std/runtime-mem/) remains available only as
 a compatibility and target-shim helper.
- Planned (general): for other runtime values, if the operand type provides an
 instance method matching [`std::interfaces::Sized`](/silk/docs/std/interfaces/)
 (`fn size(self: &Self) -> usize`), `sizeof value` will lower to a call of
 that method.
- For type operands, if the operand type provides a static, pure method
 `pure fn size() -> usize`, the compiler may fold `sizeof Type` to that value
 when the method body is const-evaluable; otherwise it falls back to the
 compiler’s built-in size model.

Built-in size model :

- Sizes reflect the current scalar-slot lowering model ([`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)):
 each lowered scalar occupies one 8-byte slot.
- A `string` value occupies two slots (`(u64 ptr, i64 len)`), so `sizeof string`
 is `16` in the Supported forms.
- A `T[]` slice value occupies two slots (`(u64 ptr, i64 len)`), so `sizeof T[]`
 is `16` in the Supported forms.
- A `T[N]` fixed array occupies `N * sizeof(T)` bytes in the Supported forms,
 using the element’s scalar-slot size.

Notes:

- `sizeof string` (type operand) is the **representation** size (currently 16
 bytes in the scalar-slot model), while `sizeof <string value>` is the
 **content** size (byte length).
- `sizeof` is a byte-size operator. For logical element counts (for example a
 slice length or vector length), use a `.len()` method via
 [`std::interfaces::Len`](/silk/docs/std/interfaces/) on the relevant type. The standard library does not
 define a generic `length(...)` helper.

Parsing note:

- Because `Name[expr]` is also indexing syntax, fixed array **type** operands
 should be parenthesized: `sizeof (u8[4])`. Without parentheses, `sizeof u8[4]`
 is parsed as an index expression.
- Because `as` binds at postfix precedence, bare `sizeof x as T` parses as
 `sizeof (x as T)`. To cast the result of `sizeof`, write `sizeof(x) as T` or
 `(sizeof x) as T`.

### `alignof`

`alignof <operand>` produces the alignment of a type or value in bytes.

Result type:

- `alignof` always returns `usize`.

Evaluation mode:

- When the operand is a **type name** (a primitive type, `struct`/`enum` name,
 type alias, or qualified type name), `alignof` is a compile-time constant.
- When the operand is a **compile-time constant value** (literals and other
 const-evaluable expressions), `alignof` is a compile-time constant.
- When the operand is a **runtime value**, `alignof` is evaluated at runtime.

Built-in alignment model :

- Alignments reflect the current scalar-slot lowering model
 ([`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)): values are stored as 8-byte slots.
- All non-`void` types currently have alignment `8`.
- `alignof void` is `1`.

Parsing notes:

- As with `sizeof`, fixed array **type** operands should be parenthesized:
 `alignof (u8[4])`. Without parentheses, `alignof u8[4]` is parsed as an index
 expression.
- Because `as` binds at postfix precedence, bare `alignof x as T` parses as
 `alignof (x as T)`. To cast the result of `alignof`, write `alignof(x) as T`
 or `(alignof x) as T`.

### `offsetof`

`offsetof(Type, field_path)` produces the byte offset of a struct-like field
within `Type` in the current memory layout model.

Result type:

- `offsetof` always returns `usize`.

Evaluation mode:

- `offsetof` is always a compile-time constant.

Operands:

- `Type` must name a `struct` or `error` type (including nested structs).
- `field_path` is one or more field identifiers separated by `.` (for example
 `b` or `inner.header.len`).

Built-in offset model :

- Offsets reflect the current scalar-slot lowering model
 ([`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)): each lowered slot is stored in an
 8-byte cell, and composite fields (nested structs, optionals, strings, etc.)
 are expanded into their slot sequences in source order.
- `offsetof(Type, field)` returns the offset of the **first slot** of that
 field’s lowered representation, in bytes.
- When `field_path` traverses an optional `T?` field, it refers to the payload
 layout (the path implicitly skips the tag slot).

### `typename`

`typename <expr>` and `typename(<expr>)` produce a `string` naming the static
type of `<expr>`.

Result type:

- `typename` always returns `string`.

Evaluation mode:

- `typename` is always a compile-time constant string.

Operand notes (Supported forms):

- When the operand is a bare name that does **not** resolve to an in-scope
 runtime binding (for example `int`, `User`, or `std::wasm::Module`), the
 compiler interprets it as a type name and returns that type’s name.
- Formatting uses the compiler’s normal type formatting (for example `T[]`,
 `&T`, and `fn (...) -> ...`).
- For monomorphized generic instantiations, the string is the human-readable
 display name (not an internal `__silk_mono__...` symbol).

### `is`

`<expr> is <Type>` checks whether the **static type** of `<expr>` conforms to
`<Type>`.

Result type:

- `is` always returns `bool`.

Evaluation mode:

- `is` is always a compile-time constant boolean.

Rules (Supported forms):

- The right-hand side must be a type (primitive, nominal `struct`/`enum`/`error`,
 `interface`, a function type, or a type alias for one of those).
- If `<Type>` is a nominal `struct` type, `expr is Type` is true when the
 expression’s static type is exactly `Type` **or** a `struct` that `extends`
 `Type`.
- If `<Type>` is an `interface`, `expr is Interface` is true when the expression’s
 static type declares conformance (`impl T as Interface`) or when the operand is
 a module declared `module Name as Interface`.
- For primitive types, enum/error types, reference types (`&T`), slice/array types
 (`T[]`, `T[N]`), optionals (`T?`), and function types, `is` currently checks
 **exact type equality** (after resolving type aliases).

Notes:

- `is` does not perform runtime tagging or value inspection. For runtime
 discrimination of union/optional values, use [`match`](/silk/wiki/language/flow-match/) and the relevant pattern
 forms.

Examples:

```silk
type Adder = fn (x: int, y: int) -> int;
fn my_adder (x: int, y: int) -> int { return x + y; }
if my_adder is Adder { /* ... */ }

struct User { id: u64 = 0 }
struct Beep extends User { boop: string = "" }
let beep = Beep{ boop: "boop" };
if beep is User { /* ... */ }

let n = 123;
if n is int { /* ... */ }

interface Logger { fn log (value: string) -> void; }
module my_logger as Logger {
  export log (value: string) { /* ... */ }
}
if my_logger is Logger { /* ... */ }
```

### Wrapping and Overflow

The spec notes “Arithmetic Wraps” for certain operators. The checker and code generator must:

- Implement deterministic wrapping behavior for integer arithmetic where required.
- Clearly separate wrapping operations from checked or saturating variants (if exposed in the standard library).

### Casts (`as`)

`as` is a postfix operator that performs an explicit conversion to a target
type.

#### Precedence

`as` binds at postfix precedence (like calls, field access, and `?`). For example:

- `a + b as int` parses as `a + (b as int)`.
- To cast the result of `new`, use parentheses so the cast applies to the heap
 reference: `(new Type{ ... }) as &Other`. Without parentheses,
 `new Type{ ... } as &Other` parses as `new (Type{ ... } as &Other)`.

#### Supported conversions

In the compiler, `as` is supported for primitive scalar
conversions:

- Integer → Integer (including `Instant`, `Duration`, and `char`):
 - The conversion is deterministic and may be lossy. It is performed by
 canonicalizing the underlying bits to the destination integer type
 (width truncation + sign/zero extension as appropriate). For scalar widths
 up to 64 bits this matches the behavior of `ir.CastInt` in the current IR;
 `i128`/`u128` follow the analogous rule over their `{ lo, hi }` lane layout.
- Float → Float:
 - `f32`/`f64`/`f128` conversions using standard IEEE-754 conversion and rounding.
- Integer → Float:
 - Converts the integer value to the destination float type (IEEE-754),
 with rounding when the integer cannot be represented exactly.
- Float → Integer:
 - Converts by truncating toward zero.
 - If the source value is `NaN`, the result is `0`.
 - If the source value is outside the destination integer’s representable
 range, the result saturates to the nearest bound (min/max for signed,
 `0`/max for unsigned).

- Struct → Struct (safe “shape cast”):
 - A cast from `S` to `T` is permitted when both `S` and `T` name **non-opaque**
 struct types and their fields match **positionally**:
 - same field count, and
 - for each index `i`, the field type of `S` at `i` is exactly the same type
 as the field type of `T` at `i` (field names may differ).
 - This is intended for “newtype-like” wrappers and schema evolution where two
 structs have the same shape but different field names.
 - Semantics: the cast produces a **value copy** of the underlying struct
 slots, retyped as `T`. The operation does not reorder fields.
- `&Struct` → `&Struct` (safe “shape cast” for references):
 - A cast from `&S` to `&T` is permitted when `S` and `T` are compatible under
 the same Struct → Struct “shape cast” rules above.
 - Semantics: the cast produces a **retyped view** of the same referenced
 storage. It does **not** allocate and does **not** copy the underlying
 struct slots.
 - For refcounted heap references created by `new`, the cast is still a view
 only: it must **not** change which `drop` implementation runs when the
 refcount reaches zero. The allocation’s concrete type (tracked through the
 value, not the view type) determines Drop behavior at the last release.
 - This means the two references alias: reading fields through the cast view
 observes updates made through the original reference (and vice versa).
 - Because the two references alias, the compiler’s per-call mutable-borrow
 restrictions treat aliases as the same storage: a single call expression
 may not take multiple mutable borrows (or both mutable and immutable
 borrows) of the same underlying reference, even if the aliases are held
 under different local names. See [`Mutability`](/silk/docs/language/mutability/).
 - This cast is intentionally conservative: it is permitted only when the
 compiler can prove the two referenced struct layouts are identical at the
 type level (same field types in the same order). It does **not** permit
 arbitrary “reinterpret pointer” casts.

- `u64`/`usize` ↔ `T[]` / `T[N]` (unsafe pointer/slice view cast):
 - Silk’s Supported forms represents raw addresses as `u64` and permits
 pointer-width unsigned `usize` values to be used as raw addresses in these
 casts. For low-level byte-copy routines and runtime interop, `as` supports
 explicit conversions between raw addresses and array/slice views:
 - `ptr as T[]` constructs a `T[]` slice view where the pointer component is
 `ptr` and the length component is a dedicated **unknown-length** sentinel
 (currently, `i64.min`). The compiler does not validate
 the pointer value.
 - Indexing and assignment through an unknown-length slice are permitted
 but **unchecked**: the runtime performs no `index < len` bounds check.
 - Operations that require a known length (iteration, slicing, etc.) trap
 unless an explicit length is provided.
 - `ptr as T[](/silk/docs/len)` constructs a `T[]` slice view where the pointer
 component is `ptr` and the length component is `len` (element count).
 - `ptr as string(len)` constructs a `string` view where the pointer
 component is `ptr` and the length component is `len` (byte count). This
 is sugar for [`std::runtime::mem::string_from_ptr_len(ptr, len)`](/silk/docs/std/runtime-mem/) (and the
 reserved intrinsic `__silk_string_from_ptr_len`).
 - `slice as u64` / `slice as usize` extracts the pointer component of a
 `T[]` slice.
 - `arr as u64` / `arr as usize` extracts the address of element `0` of a
 fixed array `T[N]` (for `N == 0`, the result is `0`).
 - These casts remain **unsafe**:
 - the compiler does not validate pointer provenance (whether the address is
 valid for the claimed element type).
 - in the current scalar-slot subset, `T[]` / `T[N]` indexing assumes the
 pointed-to memory is laid out in Silk’s scalar-slot representation. This
 is not a packed-byte view. For packed byte access (for example string
 storage), use [`std::runtime::mem::{load_u8,store_u8}`](/silk/docs/std/runtime-mem/) or
 [`std::arrays::ByteSlice`](/silk/docs/std/arrays/).
 - In the scalar-slot backend, indexed accesses through
 arrays/slices trap when:
 - the pointer is `0`,
 - the pointer is not 8-byte aligned,
 - the explicit length is negative (when provided),
 - the index is out of bounds (`index < len`) when the slice/array has a
 known (non-unknown) length.

- `Serialize(T)`-backed casts (explicit conversion via `serialize()`):
 - When the operand type provides a unique instance method named `serialize`
 matching the [`std::interfaces::Serialize(T)`](/silk/docs/std/interfaces/) surface (`fn serialize(self: &Type) -> T`),
 `expr as T` is permitted and lowers to a call of that method.
 - The conversion is explicit (it does not introduce implicit coercions).
 - The `serialize` method must be infallible (no typed errors).
 - Purity rules apply: inside `pure fn`, the `serialize` method must be `pure`.
 - Supported forms limitation: the compiler must be able to resolve the
 receiver’s nominal type at the cast site so it can lower the implicit
 `serialize()` call. This includes name expressions, field accesses, calls,
 and array/slice indexing (`arr[i] as T`) in the Supported forms.
- `Deserialize(S)`-backed casts (explicit conversion via `deserialize(...)`):
 - When the target type provides a unique **static** method named `deserialize`
 matching the [`std::interfaces::Deserialize(S)`](/silk/docs/std/interfaces/) surface (`fn deserialize(value: S) -> Self`),
 `expr as Self` is permitted and lowers to `Self.deserialize(expr)`.
 - This rule is checked before struct shape casts: when both a `deserialize`
 conversion and a shape cast could apply, the `deserialize` conversion is
 used.
 - The conversion is explicit (it does not introduce implicit coercions).
 - The `deserialize` method must be infallible (no typed errors).
 - Purity rules apply: inside `pure fn`, the `deserialize` method must be `pure`.

Examples (Supported forms):

```silk
struct Data {
  value: string,
}

struct User {
  name: string,
}

fn main () -> int {
  let data = Data{ value: "hello" };
  let user = data as User;
  assert data.value == user.name;
  return 0;
}
```

```silk
struct A {
  value: string,
}

struct B {
  value: string,
}

fn set_value (mut b: &B, value: string) -> void {
  b.value = value;
}

fn main () -> int {
  let a: &A = new A{ value: "hello" };
  var b = a as &B;
  set_value(mut b, "world");
  assert a.value == "world";
  assert b.value == "world";
  return 0;
}
```

Notes:

- `as` does not participate in the implicit call-argument coercion mechanism
 described in `Types` (that mechanism is opt-in per
 destination struct and is used primarily for stdlib ergonomics).

### Raw casts (`as raw`)

`as raw` is a postfix operator that reinterprets the **raw bits** of a scalar
value as another scalar type. It is intended for use in generic collections
and low-level marshalling where numeric conversion would be incorrect (notably
when storing `f32`/`f64` values in integer-backed storage).

Syntax:

- `<expr> as raw <Type>`

Rules (Supported forms):

- Both the operand and the target type must be numeric primitive types
 supported by the backend:
 - 64-bit-slot scalars: `i8`/`u8`/`i16`/`u16`/`i32`/`u32`/`i64`/`u64`/`int`,
 `f32`/`f64`, plus int-like primitives lowered to those scalars such as
 `Duration`/`Instant` and `char`.
 - 128-bit wide primitives: `i128`/`u128`/`f128` (two 8-byte lanes; `f128`
 stores the raw IEEE-754 binary128 bit pattern).
- `as raw` is not permitted for `void`, optionals, arrays, maps, Silk function
 value types (`fn (...) -> R`), or structs/enums.
- Special-case: `u64 as raw c_fn (...) -> R` (and `usize as raw c_fn (...) -> R`)
 is permitted for dynamic symbol loading and C ABI interop. The reverse
 direction, `c_fn (...) -> R as raw u64` (or `usize`), extracts the raw code
 pointer. This does not apply to Silk closure-carrying `fn (...) -> R` values.
- Special-case: `string as raw u64` (and `string as raw usize`) is permitted
 and extracts the string’s underlying **byte pointer** through the reserved
 intrinsic `__silk_string_ptr`.
 Prefer this direct syntax in application code, examples, and ordinary stdlib
 facades. [`std::runtime::mem::string_ptr`](/silk/docs/std/runtime-mem/) remains available only as a
 compatibility and low-level target-shim helper.
- Special-case: `&T as raw u64` (and `&T as raw usize`) is permitted and
 extracts the reference’s underlying **address** as an integer. This is
 intended for low-level interop (for example passing `&Struct` pointers to C
 APIs that use `void *` / `T *` handles).
 - This does not make integer→reference casts legal: `u64 as raw &T` remains
 rejected in the Supported forms.
- Semantics:
 - The operand’s current canonical scalar bits are reinterpreted as the target
 type’s canonical scalar bits (bit-level truncation/masking for narrower
 target widths such as `u8`/`u16`/`u32` and `f32`).
 - For 128-bit primitives, this is lane-wise:
 - the low lane is copied as `u64` bits,
 - the high lane is reinterpreted across `u64`/`i64` as needed,
 - when casting a 128-bit value to a <=64-bit target, the low lane is used,
 - when casting a <=64-bit *integer* value to `i128`/`u128`, the high lane
 is sign-extended (`i128`) or zero-extended (`u128`) in the current
 subset.
 - No numeric conversion is performed. For example, `1.0 as u64` yields
 `1`, while `1.0 as raw u64` yields the IEEE-754 bit pattern.

Examples:

```silk
let bits: u64 = (1.0 as f32) as raw u64;
let f: f32 = bits as raw f32;
```

```silk
// Pointer + length extraction for low-level interop.
let s: string = "hello";
let ptr: u64 = s as raw u64;
let len: usize = sizeof s;
```

## `Duration` & `Instant`

`Duration` and `Instant` are time-related types with special literal and operator support.

Key ideas:

- `Duration` represents a signed time span.
- `Instant` represents a signed point-in-time on a monotonic timeline (an opaque
 epoch chosen by the runtime).
- Duration literals represent time spans with unit suffixes and are converted into
 integral ticks.
- Operators cover arithmetic, comparisons, and construction from scalars.

### Representation

In the compiler:

- `Duration` is represented as a signed 64-bit integer count of **nanoseconds**.
- `Instant` is represented as a signed 64-bit integer count of **nanoseconds**
 since a monotonic, runtime-defined origin.

These are distinct Silk types in the type system, but share the same underlying
scalar representation (`i64`) at the IR and native ABI layers.

### Operators

Supported operator subset:

- `Duration + Duration -> Duration`
- `Duration - Duration -> Duration`
- `-Duration -> Duration`

- `Instant + Duration -> Instant`
- `Duration + Instant -> Instant`
- `Instant - Duration -> Instant`
- `Instant - Instant -> Duration`

- Comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`) are supported for:
 - `Duration` vs `Duration`
 - `Instant` vs `Instant`

Other arithmetic (`*`, `/`, `%`) and bitwise operators are not defined for time
types in the Supported forms.

### Overflow

Arithmetic uses the same deterministic wrapping behavior as the underlying
`i64` operations in the current back-end subset (two’s complement wraparound).

### Notes

At maturity, this document will be expanded to fully specify:

- duration/instant division semantics and rounding rules,
- checked/saturating variants exposed by the standard library,
- the precise relationship between `Instant` and the platform clock APIs,
- and FFI-safe conversions and APIs in [`std::temporal`](/silk/docs/std/temporal/).

Compiler requirements:

- Implement type-checking and lowering for the operator subset above.
- Implement duration literal parsing as specified in [`Duration Literals`](/silk/docs/language/literals-duration/).
- Integrate with [`std::temporal`](/silk/docs/std/temporal/) in the standard library.

## External Declarations (`ext`)

Silk’s external declaration feature lets Silk code call foreign functions and
access foreign variables.

- The core construct is the `ext` definition, which declares:
 - external C functions and their Silk function types, or
 - external C variables and their Silk types.
- The compiler and runtime perform marshalling between Silk’s internal representations and the C ABI, following a documented mapping.

### Declaring an External Binding

Example:

```silk
ext foo = fn (string) -> void;
ext bar = u32;
```

Here:

- `foo` is a C function named `foo` with the given Silk function type.
- `bar` is a C variable of type `u32`.

### Binding a Different External Symbol Name

Sometimes you want the Silk binding name to differ from the linked external
symbol name (for example, when writing wrapper modules that want to expose
stable public API names without colliding with imported libc names).

Syntax:

```silk
// The binding name is `c_malloc`, but the linked symbol is `malloc`.
ext c_malloc "malloc" = fn (i64) -> u64;
ext c_free "free" = fn (u64) -> void;
```

Rules:

- The identifier after `ext` is the **Silk binding name** (used for imports and
 calls from Silk code).
- The optional string literal is the **external symbol name** used for linking
 (native) or as the import name (wasm).
- If the string literal is omitted, the external symbol name is the same as the
 binding name.

### Avoiding Shadowing (Global `::...`)

If an `ext` binding is declared in the global namespace (a module with no
`package ...;` or header-form `module ...;` declaration) and a local declaration
shadows it (for example, a wrapper function named `malloc`), use the global-name
prefix to force lookup of the global binding:

```silk
return ::malloc(bytes);
```

The global-name prefix is not limited to `ext`: it also applies to type names
and enum variant paths in expression and type positions (for example, `::Foo`,
`::Foo{...}`, or `::E::Variant`), always forcing resolution in the global
(unnamed) package.

### Verification and `ext` (Silk rule)

External declarations have no body available to the verifier.

Therefore:

- It is a compile-time error to attempt to verify an `ext` declaration.
- It is a compile-time error for verified code (code whose compilation requires
 proofs) to call an `ext` function or read an `ext` variable.

This intentionally limits verification across the `ext` boundary.

### Notes

Silk currently implements this feature under the `ext`
keyword. The docs treat `ext` as canonical.

Currently supported:

- parsing `ext` external declarations and representing them in the AST,
- optional external symbol aliases (`ext local "extern" = ...;`),
- `ext` **functions** with fixed parameter lists (`ext name = fn (T0, T1) -> R;`)
 as callable symbols in Silk (C variadic `...` is not implemented yet),
- `ext` function parameters of **function type** (`fn(...) -> R`) as C-compatible
 function pointers:
 - at the ABI level, these are passed as a single `u64` code pointer (no closure
 environment),
 - arguments must be either:
 - a top-level function name, or
 - a non-capturing `fn (...) -> ...` expression,
 - capturing closures (and arbitrary function-typed locals) are rejected for
 `ext` function-pointer parameters in the Supported forms,
- `c_fn (...) -> R` types as explicit C callback pointers (Supported forms):
 - `c_fn` is a code-pointer-only function pointer type intended for FFI,
 - unlike `fn (...) -> R` function values, `c_fn (...) -> R` values do not carry a
 closure environment and are safe to store in locals/struct fields and pass
 through APIs,
 - a `c_fn` value may be formed only from:
 - a top-level function name, or
 - a non-capturing `fn (...) -> ...` expression,
 - capturing closures are rejected when a `c_fn` is required.
- `ext` **variables** of scalar type (`ext name = T;` where `T` is a supported
 scalar such as `int`, fixed-width ints, `bool`, `char`, or `f32`/`f64`) as
 readable values in Silk,
- `string` parameters in `ext` function calls are lowered as C-string pointers (`const char *`) in the backend; the compiler-emitted backing bytes include a trailing NUL terminator, while the Silk `string` length excludes it.
- borrowed-view types are restricted at the external boundary:
 - opaque handle types declared via `struct Name;` may be used behind a
 reference (`&Name`) in `ext` function parameters and results,
 - ordinary references (`&T`) and slices (`T[]`) are rejected at `ext`
 boundaries,
 - the same ordinary-borrow restriction also applies to unnamed-package
 C-facing `export fn` signatures because they participate in the external
 ABI surface,
 - named-package Silk object exports may use slice parameters inside the
 compiler-owned package ABI; those parameters lower as `{ ptr: u64,
 len: i64 }` and are not part of the C `ext`/header surface.
- lowering calls to `ext` functions when building:
 - `silk build --kind object`, and
 - `silk build --kind static`,
 - `silk build --kind shared`,
 - `silk build --kind executable`,
 producing relocations against undefined external symbols in the generated
 `.o` / `.a`, dynamic imports in the generated `.so`, or dynamic imports in
 the generated dynamically-linked executable (linux/x86_64).
 - for shared libraries and dynamically-linked executables, external calls are
 routed through a GOT slot that is filled by the dynamic loader.
 - `ext` variable reads are supported for the same outputs, producing
 relocations against undefined external data symbols (`.o` / `.a`) or dynamic
 imports (`.so` / dynamically-linked executable) routed through the GOT.
 - for wasm targets (`wasm32-unknown-unknown`, `wasm32-wasi`), `ext` declarations map to wasm imports:
 - `ext foo = fn (...) -> ...;` becomes an imported wasm function `env.foo`,
 - `ext bar = T;` becomes an imported wasm global `env.bar` (for scalar `T`),
 - parameter/result types follow the compiler’s current scalar lowering (for example `int` → wasm `i64`).

Not implemented yet (documented design, future work):

- writing to `ext` variables (they are read-only in the Supported forms),
- `ext` variables of non-scalar types (strings, structs, optionals, arrays),
- richer string and aggregate marshalling (for example: returning `string` from `ext` calls as an owned Silk value, passing/returning user-defined structs by value beyond the current ABI-safe POD subset, and C-facing array/slice bridging).
- calling back into Silk from foreign code with capturing closures or richer
 closure environments (only plain non-capturing function pointers are
 supported as `ext` parameters in the Supported forms).

### Passing Callbacks to C (`c_fn`)

Use `c_fn (...) -> R` to model C callback pointers you want to store and pass
to foreign code.

Example:

```silk
type I64BinOp = c_fn (i64, i64) -> i64;

// C provides: int64_t call_i64_binop(int64_t (*cb)(int64_t, int64_t), int64_t a, int64_t b);
ext call_i64_binop = fn (I64BinOp, i64, i64) -> i64;

fn add (a: i64, b: i64) -> i64 {
  return a + b;
}

fn main () -> int {
  let cb: I64BinOp = add;
  let out: i64 = call_i64_binop(cb, 40, 2);
  if out != 42 { return 1; }
  return 0;
}
```

Notes:

- `c_fn` values are code pointers only; they cannot capture local variables.
- A raw dynamic symbol address can be converted explicitly with
 `addr as raw c_fn (...) -> R` or an alias of that type. This is intended for
 loader APIs such as [`std::dylib`](/silk/docs/std/dylib/); Silk does not validate the symbol signature
 at runtime, so the declared `c_fn` type must match the foreign ABI exactly.
- A `c_fn` value can be called directly from Silk using the same call syntax as
 ordinary function values. The current value is invoked as a plain C code
 pointer with no closure environment.
- If a C API needs context, pass an explicit context pointer (e.g. a `u64` that
 is a `void *` in C) alongside the callback and include that context parameter
 in the callback signature.

### Opaque Struct Handles

Opaque structs are intended for representing foreign pointers/handles safely.
They strengthen type safety at the language boundary by preventing accidental
mixups between different handle types and by disallowing invalid operations in
Silk.

Declare an opaque handle type with a fieldless `struct` declaration:

```silk
// runtime.slk
struct StringBuilder;

ext sb_new = fn () -> &StringBuilder;
ext sb_append = fn (&StringBuilder, string) -> void;
ext sb_destroy = fn (&StringBuilder) -> void;
```

Use the handle by importing the type name and the `ext` functions:

```silk
import { StringBuilder, sb_new, sb_append, sb_destroy } from "./runtime.slk";

fn main () -> int {
  let sb: &StringBuilder = sb_new();
  sb_append(sb, "hello");
  sb_destroy(sb);

  // Using `sb` after destroy is UNDEFINED BEHAVIOR (dangling foreign pointer).
  return 0;
}
```

Rules:

- The handle type must be used behind `&` (`&StringBuilder`), not by value.
- Opaque structs cannot be instantiated and do not support member access.

Safety:

- You are responsible for managing the lifetime of foreign handles. Most C APIs
 provide explicit create/destroy functions; always call the destruction
 function when you are done.
- Using a handle after destruction is undefined behavior; the compiler does not
 currently enforce this at compile time.

Notes on executable `ext` calls (current linux/x86_64 implementation):

- When an executable uses `ext` calls or `ext` variable reads, the compiler
 emits a **dynamically-linked** ELF64 executable (PIE-style `ET_DYN` with
 `PT_INTERP`, `.dynamic`, `.rela.dyn`, and a `.got`).
- External symbols are resolved by the platform dynamic loader. Dependencies
 can be declared via the CLI (`silk build --needed <soname> ...`) or via the
 C99 embedding API (`silk_compiler_add_needed_library`), and runtime search
 paths can be declared via `--runpath` / `silk_compiler_add_runpath`.

### ABI Contract (Overview)

The language defines two closely related views of the ABI:

- A “fat pointer” internal representation for `string` and `regexp`:
 - conceptually: `struct string { ptr: ptr, len: i64 }` where `ptr` is a UTF‑8 pointer.
 - conceptually: `struct regexp { ptr: ptr, len: i64 }` where `ptr` is an engine-owned bytecode pointer.
- A C ABI contract (e.g. via `silk/silk.h`) using an explicit struct:

  ```c
  typedef struct {
      char   *ptr;
      int64_t len;
  } SilkString;
  ```

- A mapping to an LLVM type used internally by the compiler:

  ```llvm
  %silk.string = type { i8*, i64 }
  ```

When calling conventional C APIs, the compiler may pass a `const char *` derived from this structure, with the guarantee that the underlying data is null‑terminated. This distinction is important:

- Internal/runtime ABI: operates on `{ ptr, len }` structs (`SilkString`).
- Compatibility calls to typical C libraries: may expose `const char *` for parameters declared as `string` in Silk `ext` declarations, with the compiler extracting the `ptr`.

Our embedding ABI for `libsilk.a` will treat `SilkString` as the canonical C representation; details are further specified in `C99 ABI and `libsilk.a``.

### Primitive Type Mapping

The spec includes a table mapping Silk primitive types to C types, for example:

- `i8`, `u8` → `int8_t`, `uint8_t`
- `i16`, `u16` → `int16_t`, `uint16_t`
- `i32`, `u32` → `int32_t`, `uint32_t`
- `i64`, `u64` → `int64_t`, `uint64_t`
- `i128` → `SilkI128` (see `C99 ABI and `libsilk.a``; `{ lo, hi }` lanes)
- `u128` → `SilkU128` (see `C99 ABI and `libsilk.a``; `{ lo, hi }` lanes)
- `int` → `int64_t` (current `linux/x86_64` baseline; do not assume C `int`)
- `f32` → `float`
- `f64` → `double`
- `f128` → `SilkF128` (see `C99 ABI and `libsilk.a``; IEEE binary128 bits in `{ lo, hi }`)
- `bool` → `bool` (or `_Bool`)
- `char` → `uint32_t` (UTF‑32)
- `string` → `SilkString` (`{ char *ptr; int64_t len; }`)
- `regexp` → `SilkString` (`{ char *ptr; int64_t len; }`, opaque bytecode view)
- `void` → `void`

Notes:

- For FFI with APIs that use a C `int` (for example many POSIX syscalls),
 prefer `i32`/`u32` in your `ext` declarations rather than `int`.
- The stable C99 ABI does **not** use compiler-specific `__int128` or
 `__float128` types for these primitives; it uses explicit `{ lo, hi }`
 structs so the ABI is portable and can be expressed in strict C99.

These mappings must be reflected exactly in the C99 ABI.

### Strings and Passing Convention

For strings, the spec makes the following points:

- Silk’s `string` is represented internally as a `{ ptr, len }` pair.
- For `ext` calls to typical C APIs:
 - the compiler can extract `ptr` and pass it as a `const char *`,
 - the data is guaranteed to be null‑terminated so standard C string functions are safe.

For regex bytecode values (`regexp`):

- Silk’s `regexp` is represented internally as a `{ ptr, len }` pair with the
 same slot layout as `string`, but the bytes are *not text* and are not
 required to be null‑terminated.
- At ABI boundaries, `regexp` uses the same C shape as `SilkString`, but C code
 must treat it as an opaque `(ptr, len)` byte span (not a C string).
- Runtime regex helpers validate malformed or undersized foreign `regexp`
 payloads before execution and report them as invalid input, but C code must
 still not fabricate regex bytecode as if it were a stable public format.
- Runtime regex helpers also track which bytecode buffers they actually
 allocated: only [`std::regex::RegExp.compile(...)`](/silk/docs/std/regex/) produces an owned regex
 allocation, while borrowed/literal/foreign `regexp` views are ignored by the
 regex free/drop path instead of being freed as if they were runtime-owned.

For the embedding ABI (`libsilk.a`):

- We treat `SilkString` (`{ char *ptr; int64_t len; }`) as the primary C representation of Silk `string` values.
- Functions exported by `libsilk.a` will use `SilkString` in their signatures wherever strings cross the boundary.

This layered design allows:

- idiomatic FFI to existing C libraries using `const char *`,
- a precise, length‑carrying ABI (`SilkString`) for embedding the compiler/runtime.

### Safety & Ownership

The external interface rules must ensure:

- No C code can violate Silk’s invariants about ownership and lifetimes.
- Any shared data representation (strings, structs, arrays, closures) is documented and stable.

### Typed Errors and the `ext` boundary

Typed errors (`error`, `panic`, and `T | ErrorType...`) must not cross the `ext`
boundary.

Rules:

- `ext` function types must not use `|` in their return types.
- Silk-to-C ABI surfaces must not expose `|` in exported function signatures.
 Shims should convert typed errors into explicit error codes, optionals, or
 domain-specific error types, or terminate in a platform-appropriate way.

Implementation

- The current compiler rejects `ext` declarations that include `|`, and rejects
 exporting error-producing functions to C ABI outputs.

The spec also includes a “Structs, Arrays, and Closures (Complex Types)” subsection for FFI. As the implementation proceeds, this document must be extended to:

- describe how user‑defined structs map to C structs (respecting the layout rules in [`Structs, Impl Blocks, and Memory Layout`](/silk/docs/language/structs-impls-layout/)),
- define how arrays and slices are represented across the boundary,
- document any stable closure representation, if exposed in the C ABI.

### Structs

The full language design includes rich user-defined structs and nested
aggregates. The current compiler implementation supports only a small subset of
structs in code generation:

- structs with 0+ fields of supported value types (scalar primitives, `string`,
 nested structs, and supported optionals) in function bodies and internal helper calls,
- on `linux/x86_64`, passing and returning these structs by value at ABI boundaries
 using a scalar-slot lowering model:
 - a struct value lowers to N scalar “eightbyte” slots in field order, and
 each slot is classified as INTEGER (integer-like scalars such as `int`,
 fixed-width integers, `bool`, `char`, `Instant`, `Duration`) or SSE (`f32`/`f64`),
 - exported function *parameters* accept these slots as separate parameters;
 for 1–2 slot structs this is ABI-compatible with a by-value C struct
 parameter for the 8-byte-field subset, while for packed structs with
 smaller fields ABI compatibility with an equivalent C struct layout is not
 yet implemented/validated; for 3+ slot structs downstream C callers should
 declare separate parameters for the slots,
 - exported function *returns* support 1+ slot structs; 1–2 slot results
 return in `rax`/`rdx` and/or `xmm0`/`xmm1` accordingly, while 3+ slot
 results return indirectly via a hidden sret pointer.

This subset is intended as a stepping stone toward fully general struct layout
and SysV ABI classification (including packed layout for smaller fields such
as `f32` and small integers, nested structs, and larger aggregates returned via
hidden sret pointers).

### Optionals

The full language design includes rich optional patterns (`?.`, [`match`](/silk/wiki/language/flow-match/), nested
optionals, etc.). The current compiler implementation supports only a limited
optional subset in code generation:

- optionals whose payload type is a supported scalar, `string`, or a supported
 ABI-safe `struct` (i.e. after slot-flattening, all scalar slots are `i64`/`u64`/`f64`),
- construction via `None` and `Some(value)`,
- unwrapping via `??` with short-circuit fallback evaluation,
- and nested optionals (`T??`) for the same supported payload subset, including
 unwrapping `T??` to `T?` via `??`.

At ABI boundaries in the current `linux/x86_64` subset, optionals are lowered
as a `Bool` tag followed by the payload scalar slots in order:

- `(tag, payload)` for scalar payload optionals,
- `(tag, ptr, len)` for `string?`,
- `(tag, slot0, slot1, ...)` for `struct?` where the payload lowers to N scalar slots.

For nested optionals (`T??`) in this subset, the payload slots are the full
inner optional representation (for example `int??` lowers as
`(tag0, tag1, i64 payload)`).

For exported functions, these slots consume the normal scalar argument and
result locations (registers then stack), and 3+ scalar results return via a
hidden sret pointer.

Compiler requirements:

- Implement `ext` declarations as specified.
- Map Silk types to C types per the ABI document.
- Enforce the documented passing conventions and ownership rules for external-call strings and other bridged types.
- Keep this document and `C99 ABI and `libsilk.a`` in sync with the actual codegen strategy.

## Inline Assembly (`asm`)

Silk provides an `asm` keyword for embedding inline assembly in a way that is
explicit in source code and assembled at compile time.

Inline assembly is inherently low-level and target-dependent. Use `asm` when
you need precise control over emitted instructions that cannot be expressed
with the standard library or `ext` bindings.

### Syntax

`asm` is an expression that takes a single string literal:

```silk
fn spin_pause () -> void {
  asm "pause";
}
```

The expression has type `void` and is intended to appear as an expression
statement.

### Semantics

- `asm "<text>";` emits the machine instructions assembled from `<text>`.
- The assembly text must be a **string literal**; it is not computed at
 runtime.
- Inline asm produces no values (type `void`).
- Inline asm is treated as an explicit side-effecting operation (it is not
 elided).

#### Assembly dialect

In the current implementation, `<text>` is assembled by the system assembler
(`as`) for the active native host backend:

- `linux/x86_64`: GNU `as` in **Intel syntax** (`.intel_syntax noprefix`)
- `macos/aarch64`: Apple `as` in its native arm64 syntax

The assembly may contain multiple instructions, for example:

```silk
fn main () -> int {
  asm "mov rax, rax\nnop";
  return 0;
}
```

#### Restrictions

- `asm` is currently implemented only for the native `linux/x86_64` and
 `macos/aarch64` backends.
- The assembled output must not require **relocations**. As a result, inline
 asm may not refer to external symbols (for example `call foo` where `foo` is
 not defined within the asm text).
- Operands (inputs/outputs), clobbers, and options are not yet modeled in the
 type system. Inline asm is therefore not suitable for expressing constraints
 like “reads memory” / “clobbers rax”; it is raw instruction emission.

### Portability and safety notes

- `asm` is target-dependent by nature. The current implementation is supported
 only for the native `linux/x86_64` and `macos/aarch64` backends.
- Using `asm` can make programs non-portable. Prefer standard library
 facilities and compiler-provided intrinsics when possible.

### Notes

- Parser: accepts `asm "<string literal>"` as an expression.
- Type checker:
 - requires a string literal operand,
 - assembles the text for the native backend and reports `E2116` when the asm
 fails to assemble or uses unsupported features (such as relocations),
 - assigns the expression type `void`.
- Code generation:
 - emits the assembled bytes in the `linux/x86_64` IR→ELF backend,
 - emits the assembled bytes in the temporary `macos/aarch64` host Mach-O backend.
- Tests:
 - end-to-end coverage via:
 - [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
 - [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
 - [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
 - [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)

Not yet implemented:

- inline asm with operands (inputs/outputs), clobbers, or options,
- any `asm` support on non-`linux/x86_64` targets/backends.
 except for the current `macos/aarch64` host Mach-O subset.

## Blocks and Statement Composition

Blocks group statements, establish lexical scopes, and provide the “body” form
for structured control-flow constructs like `if`, [`while`](/silk/wiki/language/flow-while/), and the [`match`](/silk/wiki/language/flow-match/)
statement used for typed errors.

### Surface Syntax

A block is a sequence of zero or more statements delimited by braces:

```silk
{
  stmt0;
  stmt1;
  ...
}
```

The empty block `{}` is permitted.

### Statements

Silk currently supports these statement forms (see
[`Formal Grammar Spec`](/silk/docs/language/grammar/) for exact syntax):

- Local bindings:
 - `const` (compile-time constant binding; initializer must be const-evaluable),
 - `let` and `let mut` (and `var` as an alias for `let mut`),
 - `let move` and `var move` for initialization-time ownership transfer,
 including combined modifier forms such as `let mut move` and
 `var mut move`, plus direct destructuring forms such as
 `let move (a, b) = pair;` and `let move Some(value) = maybe;`.
- Specification-only declarations: `#const` (Formal Silk; not usable in runtime expressions).
- Structured blocks: `async { ... }` / `task { ... }` (see [`Concurrency`](/silk/docs/language/concurrency/)).
- Expression statements: limited to calls, assignments, and increment/decrement
 in the Supported forms ([`Expression Statements`](/silk/docs/language/flow-expression-statements/)).
- Flow control:
 - `if` / `else` statements (including `if let` pattern destructuring),
 - [`while`](/silk/wiki/language/flow-while/) loops,
 - `break`, `continue`,
 - `return`,
 - `assert`,
 - `panic` (typed errors),
 - [`match`](/silk/wiki/language/flow-match/) statement (typed errors; see `Typed Errors (`error`, `panic`, and `T | ErrorType...`)`).

### Semantics

#### Sequencing

Statements in a block execute in source order. If a statement transfers control
out of the current block (`return`, `panic`, `break`/`continue` inside loops),
the remainder of the block is not executed on that path.

#### Scope

A block introduces a lexical scope:

- Names declared by `const`/`let`/`var` are visible only after their
 declaration within the same block, and within any nested blocks.
- Inner blocks may shadow outer bindings by reusing a name (this is a normal
 lexical-shadowing rule; the checker should reject only when a specific
 feature imposes stricter rules).
- The special name `_` is a discard binding:
 - `let _ = expr;` and `let _: T = expr;` evaluate the initializer but do not
 introduce a binding into scope.
 - `_` may be used repeatedly in the same scope without conflicts.
 - Any produced runtime value is cleaned up at end-of-statement (not at scope
 exit).
 - `Task(T)` and `Promise(T)` handles are rejected in discard bindings:
 bind the handle to a real name if you want structured scope-exit cleanup,
 or consume it explicitly with `yield *`, `await`, or `await *`.

Destructuring `let` bindings (Supported forms) bind multiple locals from a
single struct value:

- Positional (field order):

  ```silk
  struct User { id: u64, name: string }
  let (id, name) = User{ id: 123, name: "alice" };
  ```

- Named (by field name, order-independent), with aliasing:

  ```silk
  struct Record { id: u64, data: string }
  let { data, id } = Record{ id: 123, data: "a record" };
  let { data as d, id as i } = Record{ id: 456, data: "other record" };
  ```

Array destructuring binds multiple locals from a single array/slice value:

```silk
struct Record { id: u64, data: string }

let records: Record[] = [{ id: 123, data: "a" }, { id: 456, data: "b" }];
let [a, b] = records;
```

Rules (Supported forms):

- Only flat patterns are supported (no nested destructuring).
- The initializer is required.
- The initializer must have a non-opaque `struct` value type.
- The pattern must account for every field exactly once:
 - positional patterns must have exactly one binder per declared field (in
 field order),
 - named patterns must list each field exactly once (in any order),
 - use `_` to discard a field (`let (_, name) = ...;` or `let { data as _ } = ...;`).

For array/slice destructuring:

- The initializer must have an array type (`T[N]`) or slice type (`T[]`).
- Each binder is positional (index order).
- The pattern binds exactly the number of listed binders:
 - fixed arrays require an exact arity match (`[a, b]` requires `T[2]`),
 - slices trap at runtime if too short (as if indexing each element).

Enum destructuring binds payload elements from a single enum variant:

```silk
import std::result;

error Oops { code: int }

fn foo (oops: bool) -> std::result::Result(int, Oops) {
  if (oops) {
    return Err(Oops{ code: 123 });
  }
  return Ok(7);
}

fn main () -> int {
  // Destructure `Ok(...)` and bind its payload.
  // If the value is `Err(...)`, the program traps.
  let Ok(value) = foo(false);
  return value;
}
```

Rules (Supported forms):

- The initializer is required.
- The initializer must have an enum type `E` (including a monomorphized generic enum).
- The initializer value is consumed (moved); the original binding may not be
 used after destructuring.
- The pattern must be an enum variant pattern:
 - `Variant(...)` (shorthand), or
 - `E::Variant(...)` / `pkg::E::Variant(...)` / `::pkg::E::Variant(...)`.
- Binder arity must match the variant payload arity (use `_` to discard payload elements).
- If the runtime value is not the matched variant, execution traps.

#### Refutable bindings: `let ... else { ... };`

For refutable patterns where you want explicit control-flow on mismatch (instead
of trapping), Silk provides a `let ... else` statement form:

```silk
let <pattern> = <expr> else {
  // must end with a terminal statement
};
```

Semantics (Supported forms):

- The initializer expression is evaluated exactly once.
- If the pattern matches, the pattern binders are introduced into the **current
 scope** for the remainder of the block (like a normal `let` binding).
- `let mut <pattern> = ... else { ... };` introduces mutable pattern binders.
- `let move <pattern> = ...;` and `let move <pattern> = ... else { ... };`
 consume the scrutinee for
 ownership-tracked values before either branch continues. The `else` block
 cannot use the moved source binding, and the continuation receives the
 payload binders as moved values.
- If the pattern does not match, the `else` block executes.
- The `else` block must be **terminal** (it must not fall through), so the
 binders are always available after the statement on any path that continues.
- The binders are **not** in scope inside the `else` block.

Examples:

```silk
fn main () -> int {
  let maybe: int? = Some(7);
  let Some(v) = maybe else { return 0; };
  return v;
}
```

```silk
import std::result;

error Oops { code: int }

fn foo (ok: bool) -> std::result::Result(int, Oops) {
  if ok { return Ok(7); }
  return Err(Oops{ code: 123 });
}

fn main () -> int {
  let Ok(v) = foo(true) else { return 1; };
  return v;
}
```

`const` bindings are compile-time constants:

- their initializer expression must be compile-time evaluable (otherwise the
 compiler reports an error),
- the binding is immutable (there is no `const mut`),
- a `const` binding is a normal runtime value (unlike `#const`), but its value
 is computed by the compiler at compile time and does not incur runtime
 computation cost in Silk currently.

In Silk currently, compile-time evaluation for runtime `const`
bindings is restricted to:

- scalar primitive types (`bool`, integer/float scalars, `char`, `Instant`, `Duration`),
- compile-time POD `struct` types whose fields are compile-time scalar value types and that do not require `Drop`, and
- compile-time evaluable expressions composed of:
 - literals,
 - other `const` bindings,
 - calls to `const fn` functions where all arguments are themselves compile-time evaluable, and
 - struct literals and field access when the struct type is a supported compile-time POD `struct`, and
 - `as` casts between supported scalar types, and
 - a small operator subset (notably `+`, `-`, `*`, bitwise ops, shifts; `/` and `%` are currently rejected for `const`).

- `string` bindings whose initializer is either:
 - a string literal (`"..."` or `` `...` ``), or
 - another `const` string binding.

Example:

```silk
struct Point { x: int, y: int }

const origin: Point = Point{ x: 0, y: 0 };
const ox: int = origin.x;
```

Formal Silk declarations (`#const`) are compile-time-only names intended for specifications
(`#require`, `#assure`, `#assert`, `#invariant`, `#variant`, `#monovariant`). They must not be referenced
in runtime expressions (see `Formal Silk` and
[`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2014`).

#### Blocks as Expressions

The broader language design includes expression-oriented flow constructs (for
example [`match`](/silk/wiki/language/flow-match/) expressions today and `if` expressions).

In Silk currently:

- a block is not an expression and does not produce a value; it is purely a
 statement list used as the body of constructs.

The `if` expression form is a special-case expression-oriented construct; it
does not make `{ ... }` a general expression form.

If/when general block expressions are introduced, the spec will define:

- which contexts accept them (and how ambiguity with `{ ... }` struct literals
 is resolved), and
- how their result values are computed.

### Examples

#### Nested scope

```silk
fn main () -> int {
  let x: int = 1;
  {
    let y: int = 2;
    if x < y {
      return 0;
    }
  }
  return 1;
}
```

#### Formal Silk declarations for loop specifications

```silk
fn main () -> int {
  let limit: int = 3;
  #const original_limit = limit;

  let mut i: int = 0;
  #invariant i >= 0;
  #invariant i <= original_limit;
  #variant original_limit - i;
  while i < limit {
    i = i + 1;
  }

  return 0;
}
```

### Notes

Implemented end-to-end:

- Block scoping for runtime `let`/`var` bindings and nested blocks.
- Formal Silk `#const` declarations (parsed, type-checked, and rejected if used at runtime).

examples:

- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)
- [`examples/`](https://github.com/oro-computer/silk/tree/master/examples/)

## Toolchain Metadata (`SILK_VERSION`, `SILK_ABI_VERSION`, `SILK_GIT_COMMIT`)

Silk exposes a small set of compiler-provided **toolchain metadata** values to:

- runtime Silk code (as built-in compile-time constants embedded into the output),
- and Formal Silk directives (`#require`, `#assure`, theories, etc).

These values let downstream code:

- report the exact toolchain used to build an artifact,
- gate behavior on the toolchain version,
- and express minimum-version requirements in Formal Silk.

### Notes

- the toolchain metadata constants listed below are available as
 built-in compile-time constants in every module.
- `silk --version` reports the same toolchain version, ABI version,
 and git commit.

### Built-In Constants

The compiler provides the following built-in constants in every module:

- `SILK_VERSION: string`
- `SILK_VERSION_MAJOR: u64`
- `SILK_VERSION_MINOR: u64`
- `SILK_VERSION_PATCH: u64`

- `SILK_ABI_VERSION: string`
- `SILK_ABI_VERSION_MAJOR: u64`
- `SILK_ABI_VERSION_MINOR: u64`
- `SILK_ABI_VERSION_PATCH: u64`

- `SILK_GIT_COMMIT: string`

These behave like normal `const` values:

- They do not require an import.
- They may be used anywhere an expression of the corresponding type is allowed.
- They are compile-time constants (their values are fixed at compile time and
 are embedded into the output artifact).

#### `SILK_VERSION`

The Silk **toolchain** semantic version string for the compiler that is
compiling the current module (SemVer core `major.minor.patch`).

#### `SILK_VERSION_MAJOR` / `SILK_VERSION_MINOR` / `SILK_VERSION_PATCH`

The SemVer **core triplet** (`major.minor.patch`) of `SILK_VERSION` exposed as
`u64` values for convenient comparisons (especially in Formal Silk).

#### `SILK_ABI_VERSION`

The semantic version string of the embedding ABI exposed by `libsilk.a`.

This must match:

- the `SILK_ABI_VERSION_*` macros in [`include/silk/silk.h`](https://github.com/oro-computer/silk/blob/master/include/silk/silk.h), and
- the values reported by `silk_abi_get_version(...)`.

#### `SILK_ABI_VERSION_MAJOR` / `SILK_ABI_VERSION_MINOR` / `SILK_ABI_VERSION_PATCH`

The SemVer core components of `SILK_ABI_VERSION` exposed as `u64` values.

#### `SILK_GIT_COMMIT`

The git commit hash of the Silk toolchain used to compile the current module.

Rules:

- When the toolchain build can determine a git commit, this is set to a stable
 hash string.
- When the toolchain build cannot determine a commit (for example when building
 from a source snapshot without git metadata), this is set to `"unknown"`.

### Examples

#### Printing toolchain information at runtime

```silk
import { println } from "std/io";

fn main () -> int {
  println("silk={}, abi={}, commit={}", SILK_VERSION, SILK_ABI_VERSION, SILK_GIT_COMMIT);
  return 0;
}
```

#### Formal Silk: minimum toolchain requirement

```silk
#require SILK_VERSION_MAJOR > 0 || (SILK_VERSION_MAJOR == 0 && SILK_VERSION_MINOR >= 2);
```

### Related

- CLI output: the `silk` CLI and [`silk(1)`](/silk/docs/man/silk.1/) (`silk --version`)
- Conditional compilation: `Attributes (`attr(...)`)` (`if attr(...) { ... }`)

## Build Metadata (`BUILD_KIND`, `BUILD_MODE`, `BUILD_VERSION`)

Silk exposes a small set of compiler-provided **build metadata** values to both
runtime code and Formal Silk (compile-time verification) so programs can adapt
to build configuration and so theories can express “this code is only valid in
test builds”, “this feature requires a minimum version”, and similar policies.

### Notes

- build metadata is available to runtime code via [`std::runtime::build`](/silk/docs/std/runtime-build/)
 ([`std::runtime`](/silk/docs/std/runtime/)).
- build metadata is available as built-in compile-time constants:
 `BUILD_KIND`, `BUILD_MODE`, `BUILD_VERSION`.

### Built-In Constants

The compiler provides the following built-in constants in every module:

- `BUILD_KIND: string`
- `BUILD_MODE: string`
- `BUILD_VERSION: string`
- `BUILD_VERSION_MAJOR: u64`
- `BUILD_VERSION_MINOR: u64`
- `BUILD_VERSION_PATCH: u64`

These behave like normal `const` string values:

- They do not require an import.
- They may be used anywhere a `string` expression is allowed.
- They are compile-time constants (their values are fixed at compile time and
 are embedded into the output artifact).

#### `BUILD_KIND`

The output kind currently being built:

- `"executable"`
- `"object"`
- `"static"`
- `"shared"`

#### `BUILD_MODE`

The build mode currently being built:

- `"debug"`
- `"release"`
- `"test"`

Notes:

- `"test"` is the mode used by `silk test`.
- Debug stack traces and debug assertion behavior are controlled separately by
 [`std::runtime::build::is_debug()`](/silk/docs/std/runtime-build/) (see [`std::runtime`](/silk/docs/std/runtime/)).

#### `BUILD_VERSION`

The semantic version of the current package when building from a manifest.

- When building from a package manifest (`silk.toml`), `BUILD_VERSION` is the
 manifest `version`.
- When not building from a manifest, `BUILD_VERSION` is `"0.0.0"`.

#### `BUILD_VERSION_MAJOR` / `BUILD_VERSION_MINOR` / `BUILD_VERSION_PATCH`

The SemVer **core triplet** (`major.minor.patch`) of `BUILD_VERSION` exposed as
`u64` values for convenient comparisons (especially in Formal Silk).

Rules:

- These parse the `major.minor.patch` prefix of `BUILD_VERSION`.
- Any trailing `-prerelease` or `+build` suffix is ignored.
- On parse failure, all three values default to `0`.

### Relationship to [`std::runtime::build`](/silk/docs/std/runtime-build/)

The standard library provides [`std::runtime::build`](/silk/docs/std/runtime-build/) functions that return the
same metadata:

- [`std::runtime::build::kind() -> string`](/silk/docs/std/runtime-build/)
- [`std::runtime::build::mode() -> string`](/silk/docs/std/runtime-build/)
- [`std::runtime::build::version() -> string`](/silk/docs/std/runtime-build/)

Use [`std::runtime::build`](/silk/docs/std/runtime-build/) when you prefer explicit namespacing or when writing
code intended to run under alternate stdlib roots.

The same module also owns the reusable Formal Silk vocabulary for build
metadata:

- [`std::runtime::build::build_kind_is(...)`](/silk/docs/std/runtime-build/)
- [`std::runtime::build::build_mode_is(...)`](/silk/docs/std/runtime-build/)
- [`std::runtime::build::{requires_debug_mode,requires_release_mode,requires_executable_kind,requires_object_kind,requires_static_kind,requires_shared_kind}`](/silk/docs/std/runtime-build/)
- [`std::runtime::build::build_version_at_least(...)`](/silk/docs/std/runtime-build/)

### Examples

#### Build-mode gated behavior

```silk
fn main () -> int {
  if BUILD_MODE == "test" {
    // Test-only behavior.
    return 0;
  }
  return 0;
}
```

#### Version-gated behavior

For semver parsing and comparison, use [`std::semver`](/silk/docs/std/semver/) at runtime.
For Formal Silk version gating against build metadata, reuse
[`std::runtime::build::build_version_at_least(...)`](/silk/docs/std/runtime-build/).

## Target Metadata (`OS_PLATFORM`, `OS_ARCH`, `OS_IS_UNIX`, `OS_IS_POSIX`)

Silk exposes a small set of compiler-provided **target metadata** values to both
runtime code and Formal Silk (compile-time verification).

These values let programs adapt to the compilation target (platform/OS and CPU
architecture) without requiring environment-specific runtime queries.

### Notes

- target metadata is available as built-in compile-time constants
 in every module:
 - `OS_PLATFORM`, `OS_ARCH`, `OS_IS_UNIX`, `OS_IS_POSIX`.
- the standard library re-exports these via [`std::os`](/silk/docs/std/os/)
 (`the standard library`).

### Built-In Constants

The compiler provides the following built-in constants in every module:

- `OS_PLATFORM: string`
- `OS_ARCH: string`
- `OS_IS_UNIX: bool`
- `OS_IS_POSIX: bool`

These behave like normal `const` values:

- They do not require an import.
- They may be used anywhere an expression of the corresponding type is allowed.
- They are compile-time constants (their values are fixed at compile time and
 are embedded into the output artifact).

#### `OS_PLATFORM`

A canonical target platform/OS name string.

Current compiler target set and values:

- `linux-x86_64`, `linux-x86_64-musl`, `linux-aarch64`, and
 `linux-aarch64-musl`:
 - `OS_PLATFORM == "linux"`
- `macos-x86_64` and `macos-aarch64`:
 - `OS_PLATFORM == "macos"`
- `ios-aarch64`, `ios-simulator-aarch64`, and `ios-simulator-x86_64`:
 - `OS_PLATFORM == "ios"`
- `android-aarch64`:
 - `OS_PLATFORM == "android"`
- `windows-x86_64` and `windows-aarch64`:
 - `OS_PLATFORM == "windows"`
- `wasm32-unknown-unknown`:
 - `OS_PLATFORM == "unknown"`
- `wasm32-wasi`:
 - `OS_PLATFORM == "wasi"`

#### `OS_ARCH`

A canonical target CPU architecture name string.

Current compiler target set and values:

- `linux-x86_64`, `linux-x86_64-musl`, `macos-x86_64`,
 `ios-simulator-x86_64`, and `windows-x86_64`:
 - `OS_ARCH == "x86_64"`
- `linux-aarch64`, `linux-aarch64-musl`, `macos-aarch64`, `ios-aarch64`,
 `ios-simulator-aarch64`, `android-aarch64`, and `windows-aarch64`:
 - `OS_ARCH == "aarch64"`
 - Formal Silk comparisons also accept the ARM64 aliases `"arm64"` and `"aarch"` in any letter case, including through compile-time string constants, even though the canonical `OS_ARCH` value remains `"aarch64"`.
- `wasm32-unknown-unknown` and `wasm32-wasi`:
 - `OS_ARCH == "wasm32"`

#### `OS_IS_UNIX`

Whether the compilation target is a UNIX family target.

Current compiler target set:

- `linux-x86_64`: `true`
- `linux-x86_64-musl`: `true`
- `linux-aarch64`: `true`
- `linux-aarch64-musl`: `true`
- `macos-x86_64`: `true`
- `macos-aarch64`: `true`
- `ios-aarch64`: `true`
- `ios-simulator-aarch64`: `true`
- `ios-simulator-x86_64`: `true`
- `android-aarch64`: `true`
- `windows-x86_64`: `false`
- `windows-aarch64`: `false`
- `wasm32-unknown-unknown`: `false`
- `wasm32-wasi`: `false`

#### `OS_IS_POSIX`

Whether the compilation target is a POSIX target.

Current compiler target set:

- `linux-x86_64`: `true`
- `linux-x86_64-musl`: `true`
- `linux-aarch64`: `true`
- `linux-aarch64-musl`: `true`
- `macos-x86_64`: `true`
- `macos-aarch64`: `true`
- `ios-aarch64`: `true`
- `ios-simulator-aarch64`: `true`
- `ios-simulator-x86_64`: `true`
- `android-aarch64`: `true`
- `windows-x86_64`: `false`
- `windows-aarch64`: `false`
- `wasm32-unknown-unknown`: `false`
- `wasm32-wasi`: `false`

### Relationship to [`std::os`](/silk/docs/std/os/)

The standard library provides [`std::os`](/silk/docs/std/os/) helpers that expose the same metadata
in a namespaced form and additionally map these strings into enums for use with
[`match`](/silk/wiki/language/flow-match/) (see `the standard library`; targets not covered by the current enum set map
to `Unknown`).

### Examples

#### Target-gated behavior

```silk
import std::os;
import { println } from "std/io";

fn main () -> int {
  if OS_IS_POSIX {
    println("posix");
  }

  match (std::os::platform()) {
    std::os::Platform::Linux => println("linux"),
    std::os::Platform::WASI => println("wasi"),
    std::os::Platform::Unknown => println("unknown"),
  };

  return 0;
}
```

#### Formal Silk requirements

```silk
#require OS_IS_POSIX;
```

## Testing

This document specifies the initial **language-level testing** surface for
Silk.

The goal is a Zig-like authoring experience (tests live next to the code they
exercise) with a simple CLI runner that emits modern TAP output for downstream
consumption.

### `test` declarations

A `test` declaration is a top-level block of statements that the compiler can
compile and execute under `silk test`.

Syntax:

```silk
test "name" {
  // statements...
}
```

The string name is optional:

```silk
test {
  // statements...
}
```

Rules:

- `test` declarations MAY appear:
 - at top level (like `fn` and `let`), and
 - nested inside another `test` block (scoped subtests).
- A `test` block introduces its own scope (like a function body).
- Nested `test` blocks are executed inline, in source order, as part of the
 enclosing test’s execution. They may be used for hierarchical grouping and
 shared setup.
- `test` blocks may use `let`, `var`, control flow, and call functions/methods
 using the same expression subset as normal code.
- Top-level `test` blocks may use `await`. When a test body contains `await`,
 `silk test` runs that generated test wrapper as async and awaits it from the
 generated runner.
- `return;` is allowed inside a `test` block (equivalent to ending the test
 early). `return <expr>;` is not allowed.

`silk test` executable runners use the native host target when Silk has a
host-backed executable backend for it, and otherwise fall back to
`linux-x86_64` (or `linux-x86_64-musl` on musl x86_64 Linux hosts). Formal
Silk target metadata in `silk test` reflects that selected execution target.

Doc comments:

- Doc comments (`/** ... */` and `/// ...`) attach to a `test` declaration the
 same way they attach to other top-level declarations.

### Running tests (`silk test`)

The `silk test` command:

- loads a module set (like `silk check` / `silk build`),
- discovers all `test` declarations in the module set, and
- executes them, emitting TAP output.

#### TAP output

The initial runner uses TAP version 13 formatting:

- `TAP version 13`
- `1..N`
- `ok <n> - <name>`
- `not ok <n> - <name>`

#### Nested test progress output

When a `test` block contains nested `test` blocks, the runner emits subtest
progress lines to stderr as each nested test completes:

- `ok - a/b`
- `not ok - a/b`

The `a/b` path reflects the active nested test name stack (including the
outermost test name) joined with `/`. This keeps TAP output on stdout stable
while making long nested suites easier to follow in an interactive terminal.

#### Assertions inside tests

In `silk test` builds, failed assertions do not abort the process. Instead:

- A failed `assert` records a test failure and execution continues.
- If the assertion has no explicit message, the compiler uses the assertion
 condition text as the message (e.g. `assert value != 123;` uses `value != 123`).
- Failed assertions also emit a one-line detail message to stderr so failures are
 visible in `silk test` output without requiring `--debug`, formatted like:
 - `assertion failed: <message>` when not inside any `test` block, or
 - `assertion failed [test: a]: <message>` when inside a `test` block, or
 - `assertion failed [test: a/b]: <message>` when inside nested `test` blocks.

 The `a/b` path reflects the active nested test name stack (including the
 outermost `test` declaration name).
- The test executable exits non-zero if any failures were recorded so TAP output
 reflects failures.

The current runner still isolates top-level tests in separate processes, but a
single test case may now accumulate multiple failures.

### [`std::test`](/silk/docs/std/test/) (standard test helpers)

The standard library provides [`std::test`](/silk/docs/std/test/) helpers for test-only assertions that
record failures without aborting:

- `expect(ok: bool, message: string? = None);`
- `expect_equal(expected: X, actual: Y) -> bool;`
- `expect_error(err: E?) -> bool;`

See [`std::test`](/silk/docs/std/test/) for the detailed API.

Note: [`std::test`](/silk/docs/std/test/) helpers carry a Formal Silk contract requiring
`BUILD_MODE == "test"` via [`std::test::requires_test_mode()`](/silk/docs/std/test/) so downstream
verification can model them as test-only APIs.

### Notes

- parsing of `test` declarations and `silk test` runner with TAP
 output.
- [`std::test`](/silk/docs/std/test/) helpers and non-aborting assertions in test builds.

## Silkdoc (Documentation Comments)

This document specifies **Silkdoc**, Silk’s documentation-comment format.
Silkdoc comments are intended for tools (documentation generators, editors,
and the language server). They **do not** affect program semantics.

The goal is a familiar JSdoc feel with Silk/TypeScript-style type annotations.

### Comment Forms

Two doc-comment forms are recognized:

- **Block doc comments**: `/** ... */`
- **Line doc comments**: one or more consecutive lines starting with `///`

In both forms, doc comments attach to the *next* declaration when they appear
immediately before it with only whitespace/comments between them.

implementation scope:

- Doc comments attach to **top-level declarations** (`package`, `module`,
 `import`, `fn`, `theory`, `let`, `struct`, `ext`, `interface`, `impl`).
- Doc comments also attach to:
 - methods inside `impl Type { ... }` blocks, and
 - method signatures inside `interface Name { ... }` blocks.
- For function declarations, doc comments attach even when one or more formal
 verification annotations (`#require` / `#assure`) appear between the doc
 comment and the `fn` keyword.
- Doc comments inside function bodies are treated as ordinary comments (not
 attached to anything).
- Attaching doc comments to struct fields, parameters, and locals is future
 work.

### Content Model

A doc comment contains:

- free-form text (Markdown-friendly) describing the declaration, and
- optional **tags** starting with `@`.

The free-form text is everything before the first tag line.

Any non-tag lines that appear after the first tag line are ignored unless they
are part of a multi-line tag body (for example `@example` or `@remarks`).

#### Leading `*` convention

For block doc comments, the conventional leading `*` is ignored:

```silk
/**
 * Hello
 * world
 */
```

Tools strip the leading `*` (and one following space when present) before
parsing.

### Tags

Tags begin at the start of the logical line after stripping comment prefixes.

#### `@param`

Declare a parameter description.

Syntax:

```text
@param <name>: <Type> <description...>
@param <name> <description...>
```

The `<Type>` uses Silk type syntax as defined in `Types`.

Example:

```silk
/**
 * Appends one byte to the vector, growing as needed.
 *
 * @param self: &std::vector::Vector(u8) The receiver.
 * @param value: u8 The byte to append.
 */
```

#### `@returns`

Describe the return value.

Syntax:

```text
@returns <Type> <description...>
@returns <description...>
```

#### `@throws`

Describe an error/exception-like condition.

Syntax:

```text
@throws <Type> <description...>
@throws <description...>
```

Note: the language does not yet have a stable error type; `@throws` is
documentation-only until `Result(T, E)` and error conventions are fully
implemented.

#### `@external`

Indicate that a declaration is an **external FFI binding** (its implementation
is provided outside Silk).

This tag is typically used to document `ext` function declarations.

Syntax:

```text
@external
```

#### `@example`

Provide an example snippet. The tag may optionally declare a language for
Markdown fenced code blocks.

Syntax:

```text
@example
<one or more lines of example text>

@example silk
<one or more lines of code>
```

The example body continues until the next tag line or the end of the doc
comment.

#### Other tags

The initial toolchain may also recognize:

- `@since <text...>`
- `@deprecated <text...>`
- `@remarks <text...>` (may span multiple lines like `@example`)
- `@see <text...>` (repeatable)

Additional tags must be documented here before they are relied on by tooling.

#### Formal Silkdoc tags

Silkdoc can document Formal Silk constructs without affecting verification.
These tags are documentation-only (they do not prove anything and do not
introduce Formal Silk obligations).

##### `@requires`

Document one precondition for a declaration (typically mirroring `#require` on a
function or a theory).

Syntax:

```text
@requires <Expr...>
```

This tag is repeatable.

##### `@assures`

Document one postcondition for a declaration (typically mirroring `#assure` on
a function or a theory).

Syntax:

```text
@assures <Expr...>
```

This tag is repeatable.

##### `@asserts`

Document one internal proof obligation (typically mirroring a `#assert` inside a
function or theory body).

Syntax:

```text
@asserts <Expr...>
```

This tag is repeatable.

##### `@theory`

Document one theory attachment or use (typically mirroring `#theory Name(args...);`).

Syntax:

```text
@theory <Name(args...)...>
```

This tag is repeatable.

#### Manpage-oriented tags

The toolchain uses a small set of optional doc tags to generate `man(7)` pages
from source comments (`silk doc --man` and `silk man`).

These tags are documentation-only and do not affect program semantics.

##### `@misc`

Declare a conceptual documentation block intended for man section 7.

Syntax:

```text
@misc <label> <summary...>
@misc <label>
```

Notes:

- The `<label>` is an opaque identifier used by tooling for discovery (for
 example `silk man <label>`). It should be stable and globally unique within a
 package (recommendation: use a `pkg::topic` label).
- The optional `<summary...>` provides a one-line description for the manpage
 `NAME` section. When omitted, tools may derive a summary from the first line
 of the free-form description text.

##### `@cli`

Declare that a doc comment describes a command-line interface, intended for man
section 1.

Syntax:

```text
@cli <name>
@cli
```

When `<name>` is omitted, tools derive the command name from context (for
example the module name or executable name provided by the build system).

##### `@synopsis`

Provide one or more synopsis lines for a CLI manpage.

Syntax:

```text
@synopsis
<one or more lines of synopsis text>
```

The synopsis body continues until the next tag line or the end of the doc
comment.

##### `@option`

Declare a command-line option for a CLI manpage. This tag is repeatable.

Syntax:

```text
@option <prototype...>
@option `<prototype...>` <description...>
```

Examples:

```text
@option `-h, --help` Show help and exit.
@option `--out <path>` Write output to <path>.
```

##### `@command`

Declare a subcommand for a CLI manpage. This tag is repeatable.

Syntax:

```text
@command <name> <description...>
```

### Markdown Rendering

The documentation generator renders doc comments to Markdown using:

- the free-form text as the leading description (paragraphs preserved),
- `@param` entries as a “Parameters” list,
- `@returns` as a “Returns” section,
- `@throws` as a “Throws” section,
- `@requires`, `@assures`, `@asserts`, and `@theory` as dedicated sections (one
 bullet per tag instance),
- `@example` blocks as fenced code blocks.

The generator must keep formatting stable (deterministic output) so that
documentation diffs are meaningful.

## Expression Statements

Expression statements allow expressions to be used for their side effects.

### Syntax

An expression statement is an expression followed by a semicolon:

```
expr;
```

### Semantics

- The expression is evaluated exactly once.
- The result value (if any) is discarded.

### Current implementation restrictions

For Silk currently, an expression statement is only valid when
the expression is either:

- a call expression (a function call), or
- an assignment / compound assignment expression.
- an increment/decrement expression (`++x`, `x++`, `--x`, `x--`).
- `await p;` where `p: Promise(void)`.
- `await * ps;` where `ps: Promise(T)[]` (the collected results are discarded).
- `yield ...;` statement forms inside `task fn` as described in
 [`Concurrency`](/silk/docs/language/concurrency/).

All other expression statements are rejected.

This restriction will be relaxed as more of the expression language is lowered
and code-generated.

Examples (accepted in the Supported forms):

```silk
fn main () -> int {
  std::io::println("hello");

  let mut x: int = 0;
  x = 1;
  x += 2;
  x++;

  return 0;
}
```

```silk
async fn pause () -> void {}

async fn main () -> int {
  await pause();
  await * [pause()];
  return 0;
}
```

Examples (rejected in the Supported forms):

```silk
fn main () -> int {
  1 + 2; // rejected: non-call/non-assignment expression statement (E2002)
  return 0;
}
```

### Guidance

If you computed a value and you want to keep it, bind it:

```silk
fn main () -> int {
  let x: int = 1 + 2;
  return x;
}
```

If you want a value for control flow, prefer an expression form that produces a
value (for example [`match`](/silk/wiki/language/flow-match/) expressions; see ``match` Expression (and Statement)`).

### Compiler requirements

The compiler must:

- Distinguish between expressions that can appear as statements and those that cannot (if the spec imposes restrictions).
- Preserve evaluation order consistent with the language’s semantics.

## `for` Loop

The `for` loop iterates over a range or iterable and executes a block once per
element.

: integer range iteration (`start..end` and
`start..=end`), array/slice iteration (`for x in xs { ... }` for `T[N]` and
`T[]`), iterator iteration (`for x in it { ... }` when `it.next() -> T?`), and
C-style `for (init; condition; step) { ... }` loops.

### Goals

- Provide a readable, structured loop construct for iteration.
- Avoid “off-by-one” patterns by making range boundaries explicit.
- Integrate with `break` / `continue`.
- Integrate with future iteration protocols (interfaces/generics) without
 introducing hidden allocation.

### Surface Syntax

Supported surface forms:

```silk
for <pattern> in <iterable> {
  ...
}
```

```silk
for let <pattern> in <iterable> {
  ...
}
```

```silk
for (<init>; <condition>; <step>) {
  ...
}
```

Notes:

- `for <pattern> in <iterable> { ... }` currently accepts a single identifier
 binder (and `_`) as the ordinary element-binding form.
- `for let <pattern> in <iterable> { ... }` is the refutable pattern form.
 `for let mut <pattern> in <iterable> { ... }` marks pattern binders mutable
 for the current loop iteration.
 It uses the same currently supported refutable match-pattern subset as
 `if let` / `while let`.
- `<iterable>` is an expression.
- `<init>` is a local binding (`let` / `var` / `const`) with an initializer.
- `<condition>` is a boolean expression.
- `<step>` is a statement-like expression (the same restricted subset as
 expression statements; see [`Expression Statements`](/silk/docs/language/flow-expression-statements/)).

### Semantics

General rules:

- The iterable expression is evaluated once to produce an iteration source.
- The loop body executes once per produced element.
- `break` exits the loop; `continue` advances to the next element.

#### Range iteration

When the iterable is a range expression (for example `start..end` or
`start..=end`), the loop iterates over integer values.

Design intent:

- `start..end` iterates `start, start+1, ..., end-1` (end-exclusive).
- `start..=end` iterates `start, start+1, ..., end` (end-inclusive).

Notes:

- The range bounds are evaluated once, left-to-right (`start` then `end`).
- If the start bound is greater than or equal to the end bound (`start >= end`)
 for an end-exclusive range, the loop executes zero times.
- If the start bound is greater than the end bound (`start > end`) for an
 end-inclusive range, the loop executes zero times.
- `continue` advances to the next element (it performs the increment step, then
 re-checks the range condition).
- The loop binder is in scope only inside the loop body block.
- The binder is immutable in the Supported forms (it behaves like a `let`
 binding that is updated by the loop machinery; user code cannot assign to it).

Type checking (Supported forms):

- Both range bounds must have integer type (`int`, `i8`/`u8`, `i16`/`u16`,
 `i32`/`u32`, `i64`/`u64`).
- The two bound types must match, except that an integer literal bound may be
 coerced to the other bound’s integer type (for example `for i in 0..n_u32`).
- The loop binder (when not `_`) has the bound’s integer type.

Example:

```silk
fn main () -> int {
  let mut sum: int = 0;

  for i in 0..3 {
    // i takes values 0, 1, 2
    sum += i;
  }

  // 0 + 1 + 2 = 3
  return sum;
}
```

#### Array and slice iteration

In Silk currently, `for` also supports iterating over builtin
array and slice types:

- fixed arrays `T[N]`,
- slices `T[]`.

Semantics (Supported forms):

- The iterable expression is evaluated once.
- The loop executes in increasing index order, starting at index `0`.
- The loop binder (when not `_`) is bound to the element value (a copy) for the
 current iteration.
- The binder is in scope only inside the loop body block.
- `break` exits the loop; `continue` advances to the next element.

#### Pattern-filtered iteration with `for let`

Silk also supports a filtered iteration form:

```silk
for let <pattern> in <iterable> {
  ...
}
```

Semantics (Supported forms):

- The iterable expression is evaluated once.
- The loop still advances over every produced element in order.
- Each produced element is matched against `<pattern>`.
- When the pattern matches, any binders introduced by the pattern are in scope
 only for that iteration’s loop body, and the body executes once.
- When the pattern form uses `let mut`, those binders may be reassigned inside
 that iteration's body.
- When the pattern does not match, the current element is skipped and iteration
 continues with the next element.
- `break` and `continue` keep their ordinary `for` meaning.

supported patterns are the same refutable subset already implemented by
`if let` / `while let`, including:

- `Some(x)` / `None`
- `Ok(x)` / `Err(x)`
- typed binders such as `v: T` where that pattern form is already supported
- enum variant payload patterns such as `Enum::Variant(x, _)`

Example:

```silk
import std::result;

type R = std::result::Result(int, int);

fn main () -> int {
  let xs: R[4] = [R.ok(2), R.err(7), R.ok(3), R.err(9)];
  let mut sum: int = 0;

  for let Ok(v) in xs {
    sum += v;
  }

  return if sum == 5 { 0 } else { 1 };
}
```

Limitations:

- Element types are limited to the currently-supported array/slice element
 subset (types that lower to a fixed scalar slot sequence in the current
 back-end, such as primitive scalars, `string`, and supported non-opaque
 structs).
- Iteration is by value; to mutate an element, use indexing (`xs[i] = ...`).

Example:

```silk
fn main () -> int {
  let xs: int[3] = [1, 2, 3];
  let mut sum: int = 0;
  for x in xs {
    sum += x;
  }
  return sum;
}
```

#### Iterator protocol

In addition to builtin arrays and slices, `for` supports iterating over a
stateful iterator value.

An expression `it` is treated as an iterator when it has a `next() -> T?`
instance method (typically by implementing [`std::interfaces::Iterator(T)`](/silk/docs/std/interfaces/)).

Semantics (Supported forms):

- The iterable expression is evaluated once to produce the iterator value.
- The loop repeatedly calls `it.next()`.
 - When the result is `None`, the loop exits.
 - When the result is `Some(value)`, the binder (when not `_`) is bound to
 `value` (a copy) for that iteration and the body executes.
- `continue` advances by calling `next()` again; `break` exits the loop.

### C-style `for` loops

Silk also supports the traditional “C-style” `for` loop:

```silk
fn main () -> int {
  let len: int = 10;
  let mut sum: int = 0;

  for (let i = 0; i < len; ++i) {
    sum += i;
  }

  return sum;
}
```

Semantics (Supported forms):

- `<init>` executes exactly once before the first condition check.
- `<condition>` is checked before each iteration; if it is `false`, the loop
 exits.
- The loop body executes once per iteration when `<condition>` is `true`.
- After the body executes normally, `<step>` executes, then the loop re-checks
 `<condition>`.
- `continue;` skips the remainder of the loop body and jumps to `<step>` (then
 re-checks `<condition>`).
- `break;` exits the loop immediately without executing `<step>` for that
 iteration.
- The init binding’s name is in scope within the entire loop (condition, step,
 and body) but is not visible after the loop.

Init binding mutability (Supported forms):

- For ergonomics, `for (let i = 0; ...; ++i)` is accepted and the init binding
 is treated as mutable (equivalent to `var`) within the loop.
 - `const` init bindings remain immutable.

### Guidance

In Silk currently, `for` supports integer ranges and builtin
array/slice iteration. To write other loops today, use [`while`](/silk/wiki/language/flow-while/):

```silk
fn main () -> int {
  let mut i: int = 0;
  while i < 3 {
    std::io::println("i = {}", i);
    i += 1;
  }
  return 0;
}
```

### Compiler Requirements

- Recognize `for` loop syntax.
- Resolve iteration targets (ranges, collections) according to the language’s
 iteration model.
- Lower `for` into explicit control flow, with correct semantics for `break`
 and `continue`.

Compiler requirements:

- Recognize `for` loop syntax.
- Resolve iteration targets (ranges, collections) according to the language’s iteration model.

## [`loop`](/silk/wiki/language/flow-loop/) Loop

The [`loop`](/silk/wiki/language/flow-loop/) statement executes a block repeatedly until it is terminated by a
`break` or `return`.

: `loop { ... }`, plus `async loop { ... }` and
`task loop { ... }`.

### Surface Syntax

```silk
loop {
  // ...
}
```

```silk
async loop {
  // ...
}
```

```silk
task loop {
  // ...
}
```

Notes:

- `async loop` and `task loop` are still loop statements: they do not end with
 `;`.
- In Silk currently, `async loop` / `task loop` follow the same
 async-context restriction as `async { ... }` / `task { ... }`:
 they are only allowed inside functions declared with `async`
 ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2031`).

### Semantics

- The body block executes repeatedly.
- `break;` exits the nearest enclosing loop and continues execution at the
 statement immediately following the loop (``break``).
- `continue;` skips the remainder of the current iteration’s body and begins the
 next iteration (``continue``).
- `return;` exits the current function (``return``).

### Type Checking Rules

- The loop body is checked in a loop context so `break` / `continue` are valid.
- `break;` outside a loop is rejected ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2007`).
- `continue;` outside a loop is rejected ([`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/), `E2008`).

### Notes

Implemented end-to-end:

- `loop { ... }`, `async loop { ... }`, and `task loop { ... }` parse, type-check,
 and lower with correct `break` / `continue` semantics.


## Type Unions (`T1 | T2 | ...`)

Silk supports **type unions** in type annotations. A union type represents a
value that is exactly one of several **member types**.

This feature exists to model small, explicit “one-of-these-types” outcomes
without requiring a dedicated nominal `enum` declaration for every case.



See also:

- `Typed Errors (`error`, `panic`, and `T | ErrorType...`)` (unparenthesized `|` in function *signatures*
 is reserved for typed-error contracts),
- ``enum` Types` (general tagged unions with named variants),
- ``match` Expression (and Statement)` ([`match`](/silk/wiki/language/flow-match/) over union values).

### Surface Syntax

Union types use `|` between member types:

```silk
let x: Foo | Bar;
struct S { v: u8 | bool }
type U = Foo | Bar | Baz;
```

#### Return types (important disambiguation)

In **function declarations**, unparenthesized `|` after `->` is reserved for
typed errors (`SuccessType | ErrorType...`). To write a union as a function’s
return type, the union must be parenthesized:

```silk
fn f () -> (Foo | Bar);
fn g () -> (Foo | Bar) | SomeTypedError;
```

This disambiguation is required so the parser and checker can treat typed-error
contracts as authoritative.

### Rules

The implementation intentionally supports only unions whose member
types have a safe, well-defined representation in the current compiler/backend
subset.

A union type `T1 | T2 | ... | Tn` is permitted when all member types are in the
supported union-member set:

- **Primitive scalar** types in `{ bool, char, i8, u8, i16, u16, i32, u32, i64,
 u64, int, usize, size, Instant, Duration }` (`isize` is accepted as an alias
 for `size`), and/or
- **Nominal POD structs** (including `error` types) and **nominal POD enums**
 that lower to a scalar-slot representation in the backend (no
 opaque structs).

Unions may freely **mix** primitive and nominal members in this subset.

For primitive members, the current native backend requires that **each member
type be distinguishable at injection sites**. In practice, that means a union
may not contain two primitive types that lower to the same backend scalar
representation (for example `int | i64`, `usize | u64`, `char | u32`,
`Duration | i64`). This restriction is specific to the backend
and may be relaxed once union injection uses full type identity rather than a
backend-scalar heuristic.

Notes:

- Nested unions are flattened: `(A | B) | C` is the same union as `A | B | C`.
- Duplicate member types are rejected.

### Semantics

A value of a union type is a **tagged** value:

- It stores a runtime tag identifying which member type is active.
- It stores the payload value in a uniform representation compatible with all
 members in the backend.

#### Representation

In the current native backend subset, unions are lowered as:

- `(u64 tag, u64 payload_0, ..., u64 payload_(N-1))`

where `N` is the maximum scalar-slot count across the union’s member types
(primitive members contribute `1`).

Member payload values are stored/loaded via raw-bit casts (`cast_raw`) to and
from the `u64` payload slots. Unused payload slots are **zero-filled** on
injection and on widening coercions.

#### Union-to-union coercions (supersets)

When a context expects a union type `U_sup`, a value of a union type `U_sub`
may be used if `U_sub`’s member set is a subset of `U_sup`’s member set. The
compiler remaps the runtime tag to the destination union’s tag numbering when
needed so pattern matches on the destination union remain correct.

If `U_sup`’s payload is larger than `U_sub`’s payload (because `U_sup` contains a
member with a larger scalar-slot representation), the payload is widened by
copying existing payload slots and zero-filling the newly-added slots.

#### Construction (injection)

When a context expects a union type, a value whose type is one of the union’s
member types may be used directly and is injected into the union.

Examples:

```silk
struct A { x: int }
struct B { x: int }
type U = A | B;

fn main () -> int {
  let a: A = A{ x: 1 };
  let u: U = a; // inject `A` into `U`
  return 0;
}
```

### Matching ([`match`](/silk/wiki/language/flow-match/))

Union values are consumed via [`match`](/silk/wiki/language/flow-match/) expressions using **typed binder**
patterns:

```silk
type U = A | B;

let out: int = match u {
  a: A => a.x,
  b: B => b.x,
};
```

Rules (Supported forms):

- When the scrutinee has a union type, patterns are restricted to
 `name: Type` (or `_: Type`) where `Type` is one of the union member types.
- Union matches must be exhaustive: exactly one arm per member type (order does
 not matter).
- The same typed-binder syntax may also be used with a concrete struct
 scrutinee before the value is injected into a union. In that form, exactly one
 typed arm must accept the concrete scrutinee type. Accepted arm types are the
 exact concrete type, a valid base type through `extends`, or an interface that
 the concrete type implements.

## `using` (Aliases and Method Reuse)

`using` introduces a local alias to an existing symbol, and (in `interface` /
`impl` bodies) can import method signatures/implementations under a new name.

This feature is intended to make large module trees ergonomic (short local
names) and to enable explicit, audited method reuse across types.

### Syntax

At any supported scope, `using` has two surface forms:

```silk
using Alias = QualifiedName;
using QualifiedName;
using QualifiedName as Alias;
```

Where `QualifiedName` uses the normal `::`-separated name syntax (including the
global-prefix form `::name`).

### Module / Package Scope

At module scope, `using` introduces a local alias for an in-scope symbol:

- types (`struct` / `enum` / `error` / `interface` / `type` aliases),
- functions (`fn` and `ext` function bindings),
- Formal Silk theories (`theory`).

For type aliases, the target may be a package-qualified generic type
declaration made visible through a namespace/package import, for example
`using Result = std::result::Result;`. This is how a stdlib prelude can expose
canonical type constructors while keeping the stdlib source on namespace
imports.

The alias is transparent: using `Alias` is equivalent to using the target
symbol directly.

Name conflicts are errors, except when the alias already refers to the same
symbol as the target (a redundant alias). In that case the `using` declaration
is accepted as a no-op.

Module-scope aliases may also be exported:

```silk
export using Alias = QualifiedName;
public using Alias = QualifiedName;
```

- `export using` and `public using` are equivalent at module scope.
- Exported aliases participate in the module/package surface just like other
 exported declarations:
 - file imports may name the alias directly,
 - package imports may name the alias directly,
 - package-qualified access may use the alias name,
 - and `export default Alias;` may target a module-scope `using` alias.
- Exported type aliases remain transparent at import sites: importing the alias
 introduces the alias name as a real type name in the importing module.

### `interface` Scope

Inside an `interface { ... }` body, `using` may import method **signatures**
from another interface:

```silk
interface Read {
  fn read() -> u8;
}

interface ReadAndPeek {
  using Read::read;
  fn peek() -> u8;
}
```

- `using Other::name;` is equivalent to copying the corresponding `fn name(...);`
 signature from `Other`.
- `using Other::name as alias;` imports it under the new name `alias`.
- Name conflicts (including conflicts with inherited `extends` members) are
 errors.

Note: interface method signatures omit the receiver parameter. The receiver is
introduced only in `impl` method declarations (see `Interfaces`).

### `impl` Scope

Inside an `impl Type { ... }` body, `using` may import a method implementation
from another impl:

```silk
impl Foo {
  fn id(self: &Foo) -> int { return 1; }
}

impl Bar {
  using Foo::id;
}
```

This makes the imported method available as if it were declared in the target
impl, including as a candidate for interface conformance checking.

#### Visibility

Imported methods inherit the source method’s visibility:

- importing a `public fn` method produces a `public` method in the target impl,
- importing a private method produces a private method in the target impl.

Since `using` does not accept visibility modifiers in the Supported forms, this
inheritance rule is the only way to control whether an imported method is
callable outside the target `impl { ... }` block.

#### `Self` and Layout Compatibility

When the imported method’s signature depends on `Self` (for example
`self: &Self`, parameters of type `Self`, or returning `Self`), importing it
across distinct struct types requires that the underlying layouts are
compatible.

In Silk currently, a pair of non-opaque, non-`error` structs are
considered compatible when they have the same number of fields and the same
field types in the same order (field names do not matter).

If the source and target struct layouts are not compatible, the `using`
declaration is rejected.

This layout rule applies equally to immutable and mutable borrows: importing
methods with `mut self: &Self` (or other `mut &Self` parameters) is permitted
when the source and target layouts are compatible.

### Supported forms Limitations

- Outside module scope, `using` does not accept `public` / `private`
 modifiers yet (imported methods inherit the source method’s visibility).
- Constructor reuse (`constructor`) via `using` is not supported yet.

## Compiler Diagnostics

This document specifies the *human-readable* diagnostic format emitted by the Silk toolchain, including:

- the `silk` CLI (`silk check`, `silk build`),
- the embedding ABI (`libsilk.a` via `silk_compiler_last_error` / `silk_error_format`),
- and tooling that reuses the front-end (for example `silk-lsp`).

The goal is to provide diagnostics that are:

- precise (file + line + column + source span),
- stable (consistent wording and stable error codes for known error kinds),
- consumable by humans (caret snippets, notes/help where appropriate),
- easy to test (deterministic formatting; the canonical text contains no ANSI escapes).

### Diagnostic Policy

The compiler should diagnose the *actual class of failure*, not hide it behind
bring-up terminology.

User-facing diagnostics should distinguish at least these categories:

- **Spec/type-check failure**: the program violates the documented Silk
 language or standard-library contract.
- **Unimplemented language feature**: the program uses a language feature that
 is not yet implemented in the compiler and is not part of the shipped
 documented language contract.
- **Backend/target limitation**: the program parses and type-checks, but a
 target-specific lowering/codegen path cannot yet emit the requested output.
- **Stdlib/module availability gap**: the program refers to a standard-library
 or package surface that is absent or unavailable for the current build/host.
- **Internal compiler error**: the compiler lost required context or reached an
 unexpected internal failure.

The term **subset** should not be the primary explanation in new diagnostics.
It may still appear in historical notes or implementation-status prose, but
user-facing errors should say what is actually wrong: for example "unimplemented
language feature", "unsupported backend target path", or a specific contract
violation.

Rule for feature-vs-rejection wording:

- If a construct is part of the documented language/spec surface, a compiler
 rejection must be described as an implementation gap, not as a language-level
 rejection.
- In particular:
 - `u128` / `f128` are language features, so `E2114` / `E2115` describe
 missing implementation work in some compiler paths rather than forbidden
 types.
 - monomorphized generics are language features, so `E2016` is for generic
 forms the current compiler has not implemented yet, not for generic syntax
 that is outside the language.

### Terminology

- **Source span**: a byte range in the UTF‑8 source buffer (`offset`, `length`).
 - Displayed **line** and **column** numbers are **1-based**.
 - Columns are measured in **UTF‑8 bytes** (matching the lexer’s current `Token.column` behavior).
- **Primary label**: the main span where the error is reported (single span in the implementation).
- **Note / Help**: supplemental lines that explain context or suggest a fix.

### Text Format (CLI and ABI)

The standard human-readable diagnostic format is:

```
error[E<code>]: <message>
 --> <path>:<line>:<column>
  |
<line> | <source line text>
  | <caret underline>
  = note: <note text>        (optional, repeatable)
  = help: <help text>        (optional, repeatable)
```

Rules:

- The `error[...]` line always appears for known error kinds; `<code>` is stable for that error kind.
- For diagnostics with no usable location, the `--> ...` and snippet block may be omitted.
- The snippet block uses the 1-based line number and includes the full line text as it appears in the source.
- The caret underline is placed under the primary span:
 - for a zero-length span, print a single `^`,
 - otherwise print `^` repeated for the span length, clipped to the line end if needed.
- The canonical text format contains no ANSI color escapes.

### Manifest and Config Errors

The CLI uses the same caret diagnostic format for errors in tooling/config inputs,
including the package manifest `silk.toml` and build-module-generated manifests.
These diagnostics may not yet have stable error codes.

Example (missing `=` in `silk.toml`):

```
error: invalid TOML in package manifest
 --> silk.toml:2:6
  |
2 | name "app"
  |      ^ expected `=`
```

### ANSI Color (CLI)

The `silk` CLI may decorate the canonical diagnostic format with ANSI SGR escape codes
when writing to a terminal. The visible text (after stripping ANSI escapes) must still
match the canonical format.

Color is enabled only when:

- stderr is a TTY that supports ANSI escapes,
- `NO_COLOR` is not set,
- `TERM` is not `dumb`.

Color is never used for the embedding ABI (`silk_error_format` / `silk_compiler_last_error`),
and is not used when stderr is not a TTY (for example when piping diagnostics to a file).

### Suggestions and Help Text

Diagnostics may include one or more `= help:` lines that suggest concrete fixes.
These are heuristic and may be omitted when the compiler cannot compute a safe
suggestion.

Examples of help/suggestion content the compiler may emit:

- for unknown imports, a `"did you mean ...?"` suggestion based on nearby names,
- for file imports, a note about the *resolved* import path,
- reminders about enabling or configuring the standard library (`--nostd`,
 `--std-root`, `SILK_STD_ROOT`) when importing `std::...`,
- guidance to include additional modules in the build/module set when an import
 refers to a package or file that is not present.

### Diagnostic Lookup Command

`silk error` is the terminal lookup surface for stable compiler diagnostics:

- `silk error <code>` prints the canonical code, category, short description,
 documentation references, any bundled example for that diagnostic, and a
 `silk guide <code>` follow-up only when the installed guide catalog actually
 links that diagnostic code.
- `silk error --list` and `silk error -l` print every stable compiler error
 code and its short description in deterministic order.
- `<code>` accepts copied forms such as `E2028`, `2028`, `diag:E2028`, and
 `error[E2028]`.
- Examples are syntax-highlighted when stdout is a color-capable TTY; piped
 output, `NO_COLOR`, and `TERM=dumb` remain plain text.

The command is backed by compiler-owned diagnostic metadata rather than scraped
documentation. [`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/) remains the normative prose
catalog for error-code meanings and policy.

`silk-lsp` publishes these stable codes in LSP diagnostics as well. Structured
resolve/type-check diagnostics include a `silk error <code>` help item, and
parse diagnostics publish `E0001` with a `silk error E0001` lookup hint in the
diagnostic data payload.

### Error Codes

The compiler assigns a stable code to each currently supported error kind.

#### Parsing

- `E0001` — unexpected token / invalid top-level ordering.

#### Import and Package Resolution

- `E1001` — unknown imported package.
- `E1002` — cyclic package imports.
- `E1003` — unknown imported file.
- `E1004` — cyclic file imports.
- `E1005` — duplicate exported symbol within a package.
- `E1006` — file imports require a module file path.

#### Type Checking

- `E2001` — type mismatch.
 - The primary message stays stable, but the diagnostic detail should explain
 the exact failed contract when available, for example:
 - `in IntFlag.usage param fs: expected ..., found ...`,
 - `while initializing binding count: expected ..., found ...`,
 - `in assignment to queue.reader: expected ..., found ...`,
 - `in return statement: expected ..., found ...`.
- `E2002` — language feature is not implemented yet.
 - The diagnostic detail should identify the exact rejected construct
 (statement / expression / declaration / type) and why it failed.
 - The public wording should describe an unimplemented feature, not a vague
 "subset" category.
 - Common examples of the required detail quality:
 - field access on an optional value should explain that `opt.field` must be
 rewritten as `opt?.field` or preceded by an unwrap,
 - `yield <task_handle>;` in statement position should explain that statement
 `yield` is the send form and that receiving from a task handle requires
 value position (`let x = yield h`) or `yield * h;` for drain/forward.
- `E2003` — unknown imported name.
- `E2004` — duplicate imported name.
- `E2005` — invalid assignment.
- `E2006` — invalid borrow.
- `E2007` — invalid `break`.
- `E2008` — invalid `continue`.
- `E2009` — invalid `return`.
- `E2010` — missing `return`.
- `E2011` — opaque struct used by value.
- `E2012` — cannot instantiate opaque struct.
- `E2013` — cannot access fields on opaque struct.
- `E2014` — formal Silk declaration used in runtime expression.
- `E2015` — binding requires an initializer.
- `E2016` — generic form is not implemented yet (for example const parameters / const type arguments / generic `impl` methods).
- `E2017` — builtin `map(K, V)` type form was removed (use `std::map::{HashMap, TreeMap}` instead).
- `E2018` — namespace import is not callable.
- `E2019` — duplicate default export in a module.
- `E2020` — invalid `panic` statement.
- `E2021` — unknown error type.
- `E2022` — error not declared in function signature.
- `E2023` — error-producing call must be handled with [`match`](/silk/wiki/language/flow-match/) or `?`.
- `E2024` — match scrutinee is not an error-producing call.
- `E2025` — match is missing an arm.
- `E2026` — typed error-handling match arm must end with a terminal statement.
- `E2027` — heap allocation is disabled (`--noheap`) and heap-backed allocation is rejected (`new` outside `with`, libc allocator `ext`, capturing closures, and concurrency use that declares/forms `Task(...)` / `Promise(...)` handles; imported stdlib async declarations alone do not trigger it).
- `E2028` — unknown name.
- `E2029` — ambiguous implicit coercion.
- `E2030` — `await` requires an `async` function.
- `E2031` — `async { ... }` / `task { ... }` requires an `async` function.
- `E2032` — ambiguous constructor call.
- `E2033` — `await` requires a Promise operand.
- `E2034` — cannot copy a Task/Promise handle.
- `E2035` — Task/Promise handle used after `await`/`yield *`.
- `E2036` — cannot consume an outer Task/Promise handle inside a loop.
- `E2037` — `task fn` uses a non-task-safe type at a task boundary.
- `E2038` — `?` requires an error contract (`-> T | ErrorType...`).
- `E2039` — `?` requires a fallible call operand.
- `E2040` — propagated error is not declared in the function signature.
- `E2041` — `const` initializer is not compile-time evaluable.
- `E2042` — `pure fn` may not have a typed-error contract (`|` in return type).
- `E2043` — `pure fn` may not contain `panic` statements.
- `E2044` — `pure fn` may not have `mut` parameters.
- `E2045` — `pure fn` may not declare mutable locals (`var` or `let mut`).
- `E2046` — `pure fn` may not perform mutation via assignment.
- `E2047` — `pure fn` may not allocate (`new`).
- `E2048` — `pure fn` may not call impure functions.
- `E2049` — `pure fn` may not be combined with `task` or `async`.
- `E2050` — theories are not callable as runtime functions (use `#theory Name(...);`).
- `E2051` — module does not satisfy the declared interface (missing exported function).
- `E2052` — module does not satisfy the declared interface (signature mismatch).
- `E2053` — unknown re-export name.
- `E2054` — duplicate exported name.
- `E2055` — prototype implementation is missing required import of its prototype module.
- `E2056` — function expression may not have non-scalar `&T` parameters.
- `E2057` — duplicate type alias name.
- `E2058` — type alias cycle.
- `E2059` — type alias kind mismatch.
- `E2060` — unknown `extends` base.
- `E2061` — invalid `extends` base.
- `E2062` — cyclic `extends` chain.
- `E2063` — derived struct redeclares an inherited field name.
- `E2064` — derived interface redeclares an inherited method name.
- `E2065` — opaque structs may not use `extends`.
- `E2066` — prototype and implementation signatures do not match.
- `E2067` — capturing closure is not allowed in `pure` code.
- `E2068` — capturing closure uses a capture type that is not implemented yet.
- `E2069` — capturing closure may not capture a mutable binding yet.
- `E2070` — `yield` requires a `task` context.
- `E2071` — `yield` in value position requires a Task operand.
- `E2072` — `yield *` requires a Task operand.
- `E2073` — `yield` as a statement requires an enclosing task function.
 - `yield <value>;` is the send form.
 - Receiving from a `Task(T)` handle is a value-position form: `let x = yield h`.
- `E2074` — `await *` requires a Promise-array operand.
- `E2075` — duplicate type name.
- `E2076` — generic type arguments must be fully specified at the use site (missing a required, non-default type argument).
- `E2077` — invalid `region` declaration.
- `E2078` — `with` requires a region binding.
- `E2079` — invalid `with ... from` region slice.
- `E2080` — reserved (previously: indexing a slice cast from `u64` required an explicit length).
- `E2081` — cast-length suffix requires a `u64`/`usize` pointer operand and a slice/string target.
- `E2082` — `const fn` may not be `task` or `async`.
- `E2083` — `const fn` may not have a typed-error contract (`|` in return type).
- `E2084` — `const fn` parameter types must be compile-time value types.
- `E2085` — `const fn` result type must be a compile-time value type.
- `E2086` — `const fn` may not allocate (`new`).
- `E2087` — `const fn` may not call a non-`const fn`.
- `E2088` — `const fn` may not contain `panic` statements.
- `E2089` — unsupported construct in a `const fn` body (outside the current const-eval subset).
- `E2090` — `const fn` may be called only from compile-time contexts.
- `E2091` — generic function call type arguments could not be inferred at the call site.
- `E2092` — use of moved value.
- `E2093` — `move` requires a local binding name.
- `E2094` — slice borrow escapes its lexical scope.
- `E2095` — reference borrow escapes its lexical scope.
- `E2096` — unknown `using` target.
- `E2097` — `using` alias conflicts with an existing name.
- `E2098` — `using` target is ambiguous.
- `E2099` — `using` cannot import `constructor` yet.
- `E2100` — `using` cannot import methods that require mutable `Self` borrows yet.
- `E2101` — `using` method reuse requires compatible struct layouts.
- `E2102` — cannot move value while it is borrowed.
- `E2103` — invalid regexp flags (unknown or duplicate).
- `E2104` — invalid regexp literal (pattern compile failed).
- `E2105` — method is private to its `impl` block (not visible from the call site).
- `E2106` — interface-required methods may not be declared `private`.
- `E2107` — destructuring requires a non-opaque struct value.
- `E2108` — cannot destructure opaque struct.
- `E2109` — destructuring pattern does not match the struct type (wrong arity, unknown field, or duplicate binder/field).
- `E2110` — array destructuring requires an array/slice value.
- `E2111` — array destructuring pattern does not match the array type (wrong arity for fixed arrays, or duplicate binder).
- `E2112` — enum destructuring requires an enum value.
- `E2113` — enum destructuring pattern does not match the enum type (unknown variant or wrong arity).
- `E2114` — `u128` is not implemented yet in all compiler paths.
- `E2115` — `f128` is not implemented yet in all compiler paths.
- `E2116` — invalid inline assembly (inline asm failed to assemble, or uses unsupported features in the current implementation).
- `E2117` — `let ... else { ... };` requires the `else` block to end with a terminal statement.
- `E2118` — borrowed-view type may not appear in an `async fn` result.
- `E2119` — borrowed-view type may not cross an `ext` / unnamed C-facing `export fn` boundary.
- `E2120` — local borrow may not remain live across `await`.
- `E2121` — cannot mutate local storage while it is borrowed.
- `E2122` — borrowed control-flow expression is ambiguous.
- `E2123` — local borrow may not escape through an async call.
- `E2124` — type does not satisfy the declared interface (missing required method).
- `E2125` — type does not satisfy the declared interface (signature mismatch).
- `E2126` — interface method must omit an explicit receiver parameter; ordinary
 interface methods already have an implicit receiver and must not spell
 `self: &Self` in the interface declaration.
- `E2127` — invalid atomic memory ordering; for example, `load` may not use
 `Release` / `AcqRel`, `store` may not use `Acquire` / `AcqRel`, and
 `compare_exchange` failure ordering may not use `Release` / `AcqRel`.

#### Formal Silk Verification

- `E3001` — loop invariant may not hold.
- `E3002` — loop variant may be negative.
- `E3003` — loop variant may not decrease.
- `E3004` — postcondition may not hold.
- `E3005` — Formal Silk verification failed to initialize or encountered an
 unsupported construct. Unsupported verified-code diagnostics should name the
 exact construct, for example an optional-field, index, nested-field, or
 compound assignment target in a verified method.
- `E3006` — assertion or struct requirement may not hold (`#assert`, theory
 assertions, and struct `#require` checks). Struct requirement failures include
 the rejected predicate plus referenced construction/default field values, and
 direct verified field writes recheck struct requirements after the write.
- `E3007` — call precondition may not hold. Contracted function/method
 preconditions are checked at ordinary call sites as well as inside explicitly
 verified code.
- `E3008` — loop monovariant may not be monotonic.

Notes:

- When `silk build --debug` or `silk test --debug` is used, failed Formal Silk
 checks emit additional Z3 debug output and write an SMT-LIB2 reproduction
 script under `.silk/z3/` in the current working directory (or `$SILK_WORK_DIR/z3`).

#### Code Generation / Backend Lowering

- `E4001` — backend/target limitation prevented code generation for the
 requested program/output.
- `E4002` — code generation failed in the backend (unexpected backend error).

Notes:

- This error is reported when a program successfully parses and type-checks, but
 IR lowering or native code generation cannot yet handle a construct.
- The detail should identify the actual backend-stage blocker:
 - rejected statement/expression/function shape,
 - missing target/output support,
 - or a specific collector/layout/codegen stage failure.
- The diagnostic detail names the rejected construct kind (statement /
 expression / function / declaration) and its surface form tag when
 available.
- Executable entrypoint shape failures should name the rejected form directly
 (for example ``unsupported executable entrypoint form: `async task fn main``` )
 and say which executable entrypoint forms are currently supported.
- When executable lowering fails during runtime-support setup before ordinary
 function-body lowering begins, `E4001` should name the blocked runtime stage
 directly (for example
 ``unsupported executable runtime support: `debug panic runtime support``` )
 instead of falling back to a misleading `unsupported function: main`.
- When lowering cannot isolate a narrower statement / expression site, `E4001`
 falls back to the offending function or declaration collector stage and names
 that function / declaration directly.
- For declaration-stage layout collection failures, the note should carry the
 collector context and, when available, the rejected field or payload type
 shape so users do not have to infer it from a generic carrier such as
 `Result`.

### Tooling Integration Notes

- `silk-lsp` should map the compiler’s primary source span to the LSP diagnostic range directly.
- `silk-lsp` should preserve structured compiler guidance in the published LSP payload:
 - keep the primary `message` short and stable,
 - surface compiler `detail`, `notes`, and `helps` as structured diagnostic metadata,
 - and avoid collapsing all follow-up guidance into one opaque message blob when
 the protocol surface can carry structured fields.
- When the compiler grows multi-span diagnostics (labels and secondary spans), the LSP implementation must be updated to surface them.

## C99 ABI and `libsilk.a`

This document defines the C99 ABI and the interface of the `libsilk.a` static library.

### Goals

- Provide a stable C ABI for embedders.
- Mirror the external-declaration semantics described in `External Declarations (`ext`)`.
- Keep the ABI small, explicit, and well-documented.

### No open-world interface-object ABI

Silk’s ordinary `interface` feature is a language-level conformance mechanism.
In the current compiler, runtime interface values are implemented only through
the closed-world compilation strategy documented in
`Interfaces`:

- the compiler discovers the conformers visible in the current build,
- lowers an interface-typed runtime value to a concrete union of those
 conformers,
- and rewrites interface method calls into ordinary dispatch over that union.

This document does **not** define a generic C ABI for arbitrary Silk interface
values. In particular, `libsilk.a` does not currently promise:

- a public `SilkInterface` object,
- a stable `(data pointer, vtable pointer)` trait-object layout,
- or an open-world ABI where separately compiled libraries can exchange unknown
 future conformers through one stable interface-object representation.

Practical consequence:

- embedders must treat ordinary interface values as an internal compiler
 lowering choice, not as a stable cross-language interchange format,
- exported and imported ABI surfaces should use concrete structs, enums/unions,
 scalars, strings, ranges, handles, and other explicitly documented ABI
 shapes,
- and if an embedding boundary needs dynamic dispatch, that dispatch contract
 must be designed explicitly in the ABI itself, for example as a concrete
 function-table struct chosen by the API author.

### Library & Headers

- Static library: `libsilk.a`.
- Primary header: [`include/silk/silk.h`](https://github.com/oro-computer/silk/blob/master/include/silk/silk.h).
- Legacy compatibility shim: [`include/silk.h`](https://github.com/oro-computer/silk/blob/master/include/silk.h).

Embedders should prefer `#include <silk/silk.h>`. The flat [`include/silk.h`](https://github.com/oro-computer/silk/blob/master/include/silk.h)
wrapper remains available for compatibility during the transition.

#### Linking When Static Z3 Is Bundled

When the host-native [`vendor/lib/<host-layout>/libz3.a`](https://github.com/oro-computer/silk/blob/master/vendor/lib/<host-layout>/libz3.a) archive is present,
`libsilk.a` includes built-in Z3 to support Formal Silk verification without requiring a
runtime Z3 dynamic library. The built-in Z3 static library is built as **C++**,
so downstream embedders linking against `libsilk.a` MUST also link the system
C++ runtime and any required system libraries:

```sh
cc -std=c99 -Wall -Wextra \
   -I/path/to/include your_app.c \
   -L/path/to/lib -lsilk \
   -lstdc++ -lpthread -lm
```

The `silk cc` wrapper adds these flags automatically when linking on
`linux/x86_64`.

If the static host archive is absent, `libsilk.a` still builds. Formal Silk
verification then requires a dynamic Z3 override via
`silk_compiler_set_z3_lib` or `SILK_Z3_LIB`.

The header must define:

- Core bridged types (e.g. `SilkString`, and any other structs or enums used by the ABI).
- Opaque handle types (`SilkCompiler`, `SilkModule`, `SilkError`) and their lifetime rules.
- Entry points for:
 - initializing and shutting down compiler/runtime state,
 - configuring compilation (target triple, stdlib name, optimization level),
 - adding source buffers,
 - compiling Silk source to executables, libraries, or object files,
 - interacting with diagnostics and error reporting.

#### Initial C Header Shape ([`include/silk/silk.h`](https://github.com/oro-computer/silk/blob/master/include/silk/silk.h))

The initial C header provided in the Silk compiler repository defines:

- `SilkString` mirroring the internal Silk `string` layout:
 - Note: `SilkString` is also the C ABI shape for Silk `regexp` values
 (bytecode-backed `{ ptr, len }`), but the bytes are opaque and not required
 to be null-terminated.

  ```c
  typedef struct SilkString {
      char   *ptr;
      int64_t len;
  } SilkString;
  ```

- `SilkBytes` for owned binary buffers returned by in-memory build APIs:

  ```c
  typedef struct SilkBytes {
      uint8_t *ptr;
      int64_t  len;
  } SilkBytes;
  ```

- `SilkRange` mirroring the Silk `range` primitive:

  ```c
  typedef struct SilkRange {
      int64_t  start;
      int64_t  end;
      uint64_t flags;
  } SilkRange;
  ```

 Notes:
 - The current `linux/x86_64` backend subset passes and returns `range` values
 as three 8-byte scalar slots (`start`, `end`, `flags`).
 - `flags` is a bitfield:
 - bit 0: `has_end` (when unset, `end` is ignored),
 - bit 1: `inclusive` (only valid when `has_end` is set).

- 128-bit scalar primitives (`i128` / `u128` / `f128`) used by generated C
 headers for exported Silk interfaces:

  ```c
  typedef struct SilkU128 {
      uint64_t lo;
      uint64_t hi;
  } SilkU128;

  typedef struct SilkI128 {
      uint64_t lo;
      int64_t  hi;
  } SilkI128;

  typedef struct SilkF128 {
      uint64_t lo;
      uint64_t hi;
  } SilkF128;
  ```

 Notes:
 - `SilkF128` stores the IEEE‑754 binary128 bit pattern. It is not C `long double`.
 - These types are passed and returned as two integer-like 8-byte slots in the
 current `linux/x86_64` backend subset.

- Opaque handles:

  ```c
  typedef struct SilkCompiler SilkCompiler;
  typedef struct SilkModule   SilkModule;
  typedef struct SilkError    SilkError;
  ```

- An output-kind enum:

  ```c
  typedef enum SilkOutputKind {
      SILK_OUTPUT_EXECUTABLE = 0,
      SILK_OUTPUT_STATIC_LIBRARY = 1,
      SILK_OUTPUT_SHARED_LIBRARY = 2,
      SILK_OUTPUT_OBJECT = 3,
  } SilkOutputKind;
  ```

- ABI version query:

  ```c
  void silk_abi_get_version(int *out_major,
                            int *out_minor,
                            int *out_patch);
  ```

- Compiler lifecycle:

  ```c
  SilkCompiler *silk_compiler_create(void);
  void          silk_compiler_destroy(SilkCompiler *compiler);
  ```

- Configuration:

  ```c
  bool silk_compiler_set_stdlib(SilkCompiler *compiler, SilkString stdlib_name);
  bool silk_compiler_set_std_root(SilkCompiler *compiler, SilkString std_root);
  bool silk_compiler_set_nostd(SilkCompiler *compiler, bool nostd);
  bool silk_compiler_set_debug(SilkCompiler *compiler, bool debug);
  bool silk_compiler_set_noheap(SilkCompiler *compiler, bool noheap);
  bool silk_compiler_set_target(SilkCompiler *compiler, SilkString target_triple);
  bool silk_compiler_set_z3_lib(SilkCompiler *compiler, SilkString path);
  bool silk_compiler_set_std_archive(SilkCompiler *compiler, SilkString path);
  bool silk_compiler_add_needed_library(SilkCompiler *compiler, SilkString soname);
  bool silk_compiler_add_runpath(SilkCompiler *compiler, SilkString path);
  bool silk_compiler_set_soname(SilkCompiler *compiler, SilkString soname);
  bool silk_compiler_set_optimization_level(SilkCompiler *compiler, int level);
  bool silk_compiler_set_c_header(SilkCompiler *compiler, SilkString path);
  ```

 `silk_compiler_set_std_root` configures the filesystem stdlib root directory used
 to auto-load `std::...` packages when modules contain `import std::...;`. The
 `std_root` string is copied. When set, it overrides `SILK_STD_ROOT` and the
 working-directory/default search behavior described below.

 `silk_compiler_set_nostd` disables this stdlib auto-loading behavior when set
 to `true`. When `nostd` is enabled, `import std::...;` declarations must be
 satisfied by explicitly adding the corresponding std sources as modules (for
 example via `silk_compiler_add_source_buffer`); the compiler will not consult
 `SILK_STD_ROOT` or the filesystem std root search paths.

 `silk_compiler_set_debug` enables the same debug build mode as the CLI
 (`silk --debug`): debug-mode lowering for supported native outputs, and
 additional Z3 debug output plus `.smt2` reproduction scripts on failing Formal
 Silk obligations (written under `.silk/z3/` or `$SILK_WORK_DIR/z3`).

 `silk_compiler_set_noheap` enables the same no-heap mode as the CLI
 (`silk --noheap`): heap-backed allocation is disabled for the supported
 subset. `--noheap` is currently incompatible with `--debug`; the ABI rejects
 configurations that enable both.

 `silk_compiler_set_optimization_level` selects the optimization level (0-3),
 matching the CLI `-O` flag. The default is level 0 unless overridden. Level
 1+ enables lowering-time pruning of unused
 extern symbols before code generation. For IR-backed native executable
 builds, it also prunes unreachable functions from the executable entrypoint
 (function-level dead-code elimination), typically reducing output size and
 over-linking when using the prebuilt `libsilk_std.a` archive to satisfy
 auto-loaded `import std::...;` modules.
 The CLI also exposes `silk build --strip-unused` to force analogous
 reachability-based pruning at `-O0` for executable/static/shared outputs; the
 current C ABI does not yet expose a separate setter for that flag.

 `silk_compiler_set_target` selects the code generation target. The
 `target_triple` string is copied. The implementation recognizes the
 same targets as the CLI (`silk build --list-targets`), including:

 - `linux-x86_64` (default), and common `x86_64-*-linux-gnu` triples such as
 `x86_64-linux-gnu` and `x86_64-unknown-linux-gnu`,
 - `linux-x86_64-musl`, and common `x86_64-*-linux-musl` triples such as
 `x86_64-unknown-linux-musl`,
 - `linux-aarch64`,
 - `linux-aarch64-musl`,
 - `android-aarch64`,
 - `macos-x86_64`,
 - `macos-aarch64`,
 - `ios-aarch64`,
 - `ios-simulator-aarch64`,
 - `ios-simulator-x86_64`,
 - `windows-x86_64`,
 - `windows-aarch64`,
 - `wasm32-unknown-unknown`,
 - `wasm32-wasi` (and other `wasm32` triples containing `wasi`).

 For `wasm32` targets, only `SILK_OUTPUT_EXECUTABLE` is supported. The output
 bytes are a final WebAssembly module (`.wasm`) produced by the IR-backed wasm
 backend (`the implementation`), with a smaller constant-only fallback for
 programs that fit the constant subset.

 The wasm backend is still early-stage, but it is no longer limited
 to single-module constant programs:

 - Multi-module builds (packages + file imports) are supported.
 - `ext foo = fn (...) -> ...;` declarations become imported functions under
 `env.foo` for `wasm32-unknown-unknown`, analogous to `extern` symbols in C.
 - String and other constant data are emitted into wasm data segments.

 Entrypoint conventions:

 - `wasm32-unknown-unknown`:
 - when a valid executable `main` exists, it is exported as `main` for
 embedder use,
 - when no `main` exists, an export-only module is emitted that exports each
 supported `export fn` from the root package.
 - `wasm32-wasi`:
 - requires `fn main () -> int` (the `main(argc, argv)` form is not supported
 yet for WASI),
 - programs that need process arguments must read them from WASI inside
 `main()` (for example via [`std::args::{argc,argv,current}`](/silk/docs/std/args/)),
 - emits an exported `_start () -> void` wrapper that calls `main` and then
 imports/calls WASI `proc_exit`,
 - export-only modules are supported for embedding (export-only modules do
 not include `_start`).

 `silk_compiler_add_needed_library` records a dynamic loader dependency for
 executable and shared library outputs (emitted as `DT_NEEDED`). The `soname`
 string is copied; the function may be called multiple times (duplicates are
 ignored). For static library and object outputs, the value is ignored.
 `DT_NEEDED` entries starting with `libsilk_rt` are rejected: bundled runtime
 helpers are linked statically from `libsilk_rt.a` / `libsilk_rt_noheap.a` and
 must not become runtime loader dependencies.
 On `linux/x86_64`, when an executable or shared library imports any external
 symbols, the compiler automatically adds the selected libc as a `DT_NEEDED`
 dependency (`libc.so.6` for glibc, `libc.so` for musl), so embedders do not
 need to manually add libc when using hosted `std::` modules like [`std::io`](/silk/docs/std/io/)
 and [`std::fs`](/silk/docs/std/fs/). Additional non-libc dependencies must still be declared via
 `silk_compiler_add_needed_library`.

 `silk_compiler_add_runpath` records a dynamic loader search path element for
 executable and shared library outputs (emitted as `DT_RUNPATH`). The `path`
 string is copied; the function may be called multiple times (duplicates are
 ignored) and the final `DT_RUNPATH` string is formed by joining all entries
 with ':'.

 `silk_compiler_set_soname` configures the shared library soname recorded as
 `DT_SONAME` for shared library outputs. The `soname` string is copied; passing
 an empty string clears the configured soname (no `DT_SONAME` entry). For
 executable, static library, and object outputs, the value is ignored.

 `silk_compiler_set_z3_lib` configures a Z3 dynamic library override for Formal
 Silk verification (equivalent to the CLI `--z3-lib <path>`). Passing an empty
 string clears the override and returns to the normal Z3 selection rules
 (including honoring `SILK_Z3_LIB`).

 `silk_compiler_set_std_archive` configures a stdlib archive override
 (equivalent to the CLI `--std-lib <path>`). Passing an empty string clears
 the override and returns to the normal stdlib archive selection rules
 (including honoring `SILK_STD_LIB`).

 `silk_compiler_set_c_header` configures C header generation for non-executable
 outputs (equivalent to the CLI `--c-header <path>`). The header is written
 when `silk_compiler_build` succeeds for `SILK_OUTPUT_OBJECT`,
 `SILK_OUTPUT_STATIC_LIBRARY`, or `SILK_OUTPUT_SHARED_LIBRARY`. C header
 generation is not supported for `silk_compiler_build_to_bytes`.

- Source management:

  ```c
  SilkModule *silk_compiler_add_source_buffer(SilkCompiler *compiler,
                                              SilkString    name,
                                              SilkString    contents);
  ```

- Building artifacts:

  ```c
  bool silk_compiler_build(SilkCompiler   *compiler,
                           SilkOutputKind  kind,
                           SilkString      output_path);
  ```

 For embedders that need filesystem-free compilation (for example sandboxed
 hosts or WASM-like environments), the ABI also provides an in-memory build
 API that returns an owned byte buffer:

  ```c
  bool silk_compiler_build_to_bytes(SilkCompiler   *compiler,
                                    SilkOutputKind  kind,
                                    SilkBytes      *out_bytes);

  void silk_bytes_free(SilkBytes *bytes);
  ```

 The returned bytes are target-specific: for example an ELF64 binary on
 `linux-x86_64`, or a `.wasm` module on `wasm32` targets.

 Ownership rules:

 - On success, `silk_compiler_build_to_bytes` fills `*out_bytes` with a pointer
 and length describing the produced artifact, and returns `true`.
 - The returned `out_bytes->ptr` is owned by `libsilk.a` and must be freed
 by calling `silk_bytes_free(&bytes)`. Callers must not free the pointer with
 `free()` (or any other allocator).
 - `silk_bytes_free` is a no-op when passed `NULL` or when `bytes->ptr` is
 `NULL`; it always clears the struct to `{ NULL, 0 }`.

 Note: the compiler may still consult the filesystem to auto-load `std::...`
 modules unless `silk_compiler_set_nostd(compiler, true)` has been set.

 Current Apple host-backed note:

 - the CLI / driver now supports non-const `ios-aarch64`,
 `ios-simulator-aarch64`, and `ios-simulator-x86_64` executable builds on
 Apple Silicon macOS for the current pure-Silk scalar subset, including
 reachable float-to-int lowering and portable bundled runtime helper
 families
 (number / regex / unicode / filesystem / dns / process / signal / term /
 pty / readline / task-pool / async),
 - `silk_compiler_build(...)` and `silk_compiler_build_to_bytes(...)` now
 support that same iOS host-backed subset on Apple Silicon macOS,
 - the remaining explicit `E4001` iOS limitation is narrower:
 it now applies only to narrower unsupported bundled runtime-internal
 helper families, while portable bundled helpers, hosted async/task
 linkage, and float-to-int now link on this path.

 At the current stage of implementation:

 - `silk_compiler_build` always performs full front‑end validation for all modules
 added via `silk_compiler_add_source_buffer`:
 - it lexes and parses each module into an internal representation,
 - it then type‑checks the *set* of modules as a unit, taking into account
 package/import relationships and exported constants, according to the
 language grammar and semantics documented under this specification,
 - if Formal Silk syntax is present (for example `#require`, `#assure`,
 `#assert`, `#invariant`, `#variant`, `#monovariant`, `#const`), it also runs the Z3-backed verifier
 and fails the build if verification fails (`E3001`..`E3008`),
 - the verifier is currently skipped for stdlib modules (`std::...`),
 - when the host-native archive is present, Z3 is linked from the built-in
 static archive [`vendor/lib/<host-layout>/libz3.a`](https://github.com/oro-computer/silk/blob/master/vendor/lib/<host-layout>/libz3.a),
 - the verifier honors `SILK_Z3_LIB` (environment variable) to override
 the Z3 dynamic library at runtime,
 - it fails fast on the first front‑end error.
 - when packages/imports are present:
 - `import` declarations must refer to packages that exist in the current
 module set (otherwise a resolver error is reported, such as
 `"unknown imported package"`),
 - exported `let` bindings with explicit type annotations in an imported
 package are treated as ordinary, unqualified names in the importing
 modules for type‑checking purposes (for example, `import util;` and
 `export let answer: int = 42;` in `util` allows `let x: int = answer;`
 in `app`),
 - imported exported functions (`export fn`) are callable across packages
 for the current scalar subset (both unqualified `foo()` and qualified
 `pkg::foo()` call forms are accepted initially), and functions in the
 same package share a call namespace across modules in the same module
 set,
 - duplicate exported names within a single package are reported as a
 resolver error (`"duplicate exported symbol"`).
 - standard library import resolution (first slice):
 - when a module contains `import std::...;`, the compiler will attempt to
 auto-load the referenced `std::...` package modules from a configured
 stdlib root so embedders do **not** need to provide std sources
 explicitly in the common case,
 - the stdlib root is selected via:
 - `silk_compiler_set_std_root` when set, otherwise
 - `SILK_STD_ROOT` (environment variable) when set, otherwise
 - a [`std/`](https://github.com/oro-computer/silk/tree/master/std/) directory in the current working directory (development default), otherwise
 - `../share/silk/std` relative to the current executable (installed default).
 - package-to-path mapping is deterministic:
 - `std::foo::bar` resolves to the file `<std_root>/foo/bar.slk`,
 - if the embedder explicitly provides a `std::...` module via
 `silk_compiler_add_source_buffer`, that module is treated as authoritative
 for its package (auto-loading does not replace already-provided packages).
 - standard library archive linking (`linux/x86_64`, current archive layout):
 - the toolchain can build a target-specific stdlib static archive
 (`libsilk_std.a`) containing one ELF object per std module (for example
 via `make stdlib`),
 - for supported executable builds, the compiler treats *auto-loaded*
 `std::...` modules as external during code generation and resolves their
 exported functions from the archive when available (while still
 type-checking the std sources as part of the module set),
 - archive discovery (in order):
 - `SILK_STD_LIB` when set, otherwise
 - `build/lib/silk/std/libsilk_std.a` when using the in-repo [`std/`](https://github.com/oro-computer/silk/tree/master/std/) root, otherwise
 - `../lib/silk/std/libsilk_std.a` relative to the current executable, otherwise
 - `../lib/libsilk_std.a` relative to the current executable (legacy installed layout), otherwise
 - common installed-layout heuristics derived from the selected stdlib root,
 - walk up from the current working directory to find `libsilk_std.a`, `lib/libsilk_std.a`, or `lib/silk/std/libsilk_std.a`,
 - when no suitable archive is found (or on unsupported targets), the
 compiler falls back to compiling the reachable std sources into the
 build as part of module-set code generation.
 - When a front‑end error occurs (e.g. parse error, type mismatch, invalid
 control‑flow such as `break`/`continue`/`return` in the wrong context, or
 other semantic violations), the call returns `false` and
 `silk_compiler_last_error`/`silk_error_format` provide a human‑readable
 description (such as `"unexpected token while parsing module"`,
 `"type mismatch"`, `"invalid break statement"`, `"invalid return statement"`,
 `"missing return statement"`,
 etc.).
 - For executable outputs (`kind == SILK_OUTPUT_EXECUTABLE`), the compiler also
 enforces an entrypoint precondition on the front‑end:
 - there MUST be exactly one top‑level function with one of the forms

      ```silk
      fn main() -> int { ... }

      fn main(argc: int, argv: u64) -> int { ... }
      ```

 with a declared result type of `int`, and either:
 - no parameters, or
 - exactly two parameters whose types are `int` and `u64`,
 - otherwise `silk_compiler_build` fails with an error message such as
 `"no valid main function for executable output"` or
 `"multiple main functions for executable output"`.
 - When all modules pass front‑end validation (including the executable
 entrypoint requirement, where applicable), code generation behavior depends
 on `kind`:
 - for non-executable outputs (`SILK_OUTPUT_OBJECT`, `SILK_OUTPUT_STATIC_LIBRARY`, `SILK_OUTPUT_SHARED_LIBRARY`):
 - `main` is optional, but when more than one valid executable `main` exists in the module set,
 `silk_compiler_build` fails with `"multiple main functions for non-executable output"`,
 - when multiple packages are present in the module set, only exports from the *root package*
 (the package of the first module added to the compiler via `silk_compiler_add_source_buffer`)
 are emitted as globally-visible symbols for that output; other packages are compiled as
 dependencies and their `export` declarations are treated as internal for that output.
 - within the current `linux/x86_64` IR subset, `string` and `regexp` values are supported at ABI boundaries in a C-friendly `SilkString { ptr, len }` layout:
 - `string`/`regexp` parameters lower to two integer-like scalars in order (`u64` pointer, then `i64` byte length) and consume the normal integer argument locations (registers then stack),
 - `string`/`regexp` results return as two integer-like scalars in `rax`/`rdx`,
 - `regexp` values remain opaque runtime-engine bytecode views: downstream C code may forward them, but must not construct them as if the byte layout were a stable public format,
 - regex literals and other borrowed `regexp` views are not caller-owned heap objects; only [`std::regex::RegExp.compile(...)`](/silk/docs/std/regex/) produces runtime-owned regex bytecode,
 - when [`std::regex`](/silk/docs/std/regex/) executes a foreign ABI-supplied `regexp`, the bundled runtime first validates the bytecode header/control-flow shape and reports malformed inputs as `EXEC_ERR_INVALID_INPUT` instead of entering the engine blindly,
 - when Silk code later frees or drops a `regexp` through the regex runtime, only those runtime-owned compiled values are released; borrowed/literal/foreign views are ignored safely,
 - the bundled runtime allocator override used by runtime regex compilation is process-global but internally synchronized; concurrent `silk_rt_set_allocator(...)` calls can affect which hook future runtime allocations use, but any individual allocation returned by `silk_rt_malloc_bytes(...)` keeps the realloc/free hooks that created it for its full lifetime, and foreign, forged, stale pre-`realloc`, or already-freed helper pointers that do not correspond to a live bundled-runtime allocation are ignored instead of steering helper realloc/free calls,
 - within function bodies, the compiler supports a small `string`/`regexp` expression subset:
 - `string`: string literals, `let` bindings of `string`, `return` of a `string` value, direct calls to `string`-returning helpers, and `==`/`!=`/`<`/`<=`/`>`/`>=` comparisons over `string` values (producing `bool`),
 - `regexp`: regex literals (`/pattern/flags`), `let` bindings of `regexp`, `return` of a `regexp` value, and direct calls between helpers that accept/return `regexp`,
 - other string operations (concatenation, indexing, etc.) are not implemented yet; higher-level regex matching lives in [`std::regex`](/silk/docs/std/regex/) and is routed through `ext` calls.
 - within the current `linux/x86_64` IR subset, `i128`/`u128`/`f128` values are supported at ABI boundaries using the stable C99 `{ lo, hi }` struct shapes:
 - parameters lower to two integer-like scalars (`u64 lo`, then `u64`/`i64 hi`) and consume integer argument locations,
 - results return as two integer-like scalars in `rax`/`rdx`,
 - `f128` values are transported as raw IEEE binary128 bits in the two lanes (not via SSE registers).
 - within the current `linux/x86_64` IR subset, a limited `struct` subset is supported at ABI boundaries:
 - within function bodies and internal helper calls, `struct` declarations with 0+ fields of supported value types are supported (scalar primitives, `string`, nested structs, and supported optionals),
 - at ABI boundaries for exported/FFI functions, only ABI-safe structs are currently supported: after slot-flattening, all scalar slots must be `i64`/`u64`/`f64` (until packed ABI mapping for smaller fields is implemented),
 - ordinary borrowed references/slices are rejected up front on `ext` declarations and unnamed C-facing root-package `export fn` signatures; only opaque handle references (`&Handle` where `Handle` is `struct Name;`) may cross the external ABI boundary,
 - named-package Silk object exports may accept slice parameters (`T[]`) in the compiler-owned package ABI; these lower to two integer-like scalars (`u64` pointer, then `i64` element count) and are not emitted through C header generation,
 - at the C ABI surface, exported function *parameters* support 1+ slot ABI-safe structs by lowering the struct to its scalar slots in order; downstream C callers should declare separate parameters for 3+ slot structs (by-value C struct parameters are ABI-compatible only for the 1–2 slot cases), while exported function *returns* support 1+ slot ABI-safe structs (3+ slot returns use the native backend’s sret return path and are ABI-compatible with returning an equivalent C struct by value),
 - in all cases, the compiler lowers a struct value into N scalar slots in field order and assigns argument/result locations according to System V AMD64 integer/SSE classification for those slots.
 - within the current `linux/x86_64` IR subset, optionals (`T?`) are supported at ABI boundaries for the supported payload subset (scalar payloads, `string?`, and optionals of ABI-safe structs):
 - an optional lowers to a `Bool` tag followed by the payload scalar slots: `(tag, payload0, payload1, ...)` with `tag=0` for `None` and `tag=1` for `Some(...)`,
 - nested optionals (`T??`) lower by treating the payload slots as the full inner optional representation (for example `int??` lowers as `(tag0, tag1, i64 payload)`),
 - optional parameters are passed as these scalar slots in order (so downstream C callers should declare separate parameters, treating `tag` as an integer-like 0/1 value),
 - optional results return as the same scalar slots (1–2 slots in registers; 3+ slots via a hidden sret pointer as described above).
 - for object outputs (`SILK_OUTPUT_OBJECT`):
 - on `linux/x86_64`, the compiler can emit an ELF64 relocatable object
 (`ET_REL`) for the supported IR subset, emitting supported functions
 (scalar-returning, `void`-returning, and a limited `string` subset) and supported exported constants
 (`export let`/`export const`; scalar exports require an explicit type annotation and a literal initializer, and string exports may omit `: string` when the initializer is a string literal), and marking `export fn`
 declarations, supported exported constants, and a valid executable `main`
 (when present) as global symbols,
 - when the module set contains no supported globally-visible symbols (no
 supported `export fn`, no supported `export let` constants, and no valid
 executable `main`), `silk_compiler_build` still succeeds and writes a
 valid relocatable object with no globally-visible symbols,
 - for programs outside that subset (or on unsupported targets),
 `silk_compiler_build` returns `false` with an `E4001` / `E4002` formatted diagnostic (via `silk_compiler_last_error` / `silk_error_format`)
 and does not write an output file.
 - when lowering cannot isolate a narrower statement / expression span,
 that `E4001` diagnostic falls back to the offending function
 declaration and names that function directly.
 - for static library outputs (`SILK_OUTPUT_STATIC_LIBRARY`):
 - on `linux/x86_64`, the compiler can emit a static library archive
 (`.a`) containing an object file for the supported IR subset, emitting
 supported functions (scalar-returning, `void`-returning, and a limited `string` subset) and supported
 exported constants (`export let`/`export const`; scalar exports require an explicit type annotation and a literal initializer, and string exports may omit `: string` when the initializer is a string literal), and
 marking `export fn` declarations, supported exported constants, and a
 valid executable `main` (when present) as global symbols,
 - when the module set contains no supported globally-visible symbols (no
 supported `export fn`, no supported `export let` constants, and no valid
 executable `main`), `silk_compiler_build` still succeeds and writes a
 valid archive containing an object file with no globally-visible symbols,
 - for programs outside that subset (or on unsupported targets),
 `silk_compiler_build` returns `false` with an `E4001` / `E4002` formatted diagnostic (via `silk_compiler_last_error` / `silk_error_format`)
 and does not write an output file.
 - for shared library outputs (`SILK_OUTPUT_SHARED_LIBRARY`):
 - on `linux/x86_64`, the compiler can emit an ELF64 shared library
 (`ET_DYN`, typically with a `.so` filename) for the supported IR subset,
 emitting supported functions (scalar-returning, `void`-returning, and a limited `string` subset) and
 supported exported constants (`export let`/`export const`; scalar exports require an explicit type annotation and a literal initializer, and string exports may omit `: string` when the initializer is a string literal), and
 marking `export fn` declarations, supported exported constants, and a
 valid executable `main` (when present) as dynamic global symbols,
 - when the module set contains no supported globally-visible symbols (no
 supported `export fn`, no supported `export let` constants, and no valid
 executable `main`), `silk_compiler_build` still succeeds and writes a
 valid shared library with an empty export set,
 - for programs outside that subset (or on unsupported targets),
 `silk_compiler_build` returns `false` with an `E4001` / `E4002` formatted diagnostic (via `silk_compiler_last_error` / `silk_error_format`)
 and does not write an output file.
 - for executable outputs (`SILK_OUTPUT_EXECUTABLE`):
 - the implementation supports a **minimal constant‑expression backend**:
 - the program must satisfy the entrypoint rule above,
 - the body of `main` must be one of the following shapes:
 - zero or more `let` statements whose initializers are constant
 integer expressions, followed by exactly one `return` statement
 that returns a *constant integer expression* built only from:
 - integer literals,
 - the arithmetic operators `+`, `-`, `*`, `/`, and `%`,
 - and references to immutable `let` bindings (top‑level or local
 to `main`, or imported exported scalar constants from imported
 packages) whose initializers are themselves constant integer
 expressions in this same sense (no side‑effecting operations);
 imported exported constants must be declared as `export let` or
 `export const` with the shape `export <binding> name: <scalar> =
 <literal>;` (explicit scalar type and literal initializer),
 - on `linux/x86_64`, direct calls to simple helper functions of
 the form

              ```silk
              fn helper (x, y) -> int {
                [let ...;]
                return <expr>;
              }
              ```

 where:
 - parameters may be annotated as scalar types (defaulting to
 `int` when unannotated),
 - arguments at each call site are drawn from the same
 scalar expression subset as `<expr>` (including `bool`,
 `char`, `Instant`, `Duration`, fixed-width integers, and
 `f32`/`f64` on `linux/x86_64`), with optionals (`T?`)
 supported for scalar
 payloads, `string?`, and optionals of the POD `struct`
 subset via `None` / `Some(...)` and `??`
 coalescing, and
 - in module-set builds, helper calls may target:
 - functions defined in the same package (across multiple
 modules), and
 - imported exported functions (`export fn`) from any
 packages imported by the module that contains `main`
 (both `foo()` and `pkg::foo()` call forms are accepted
 initially for imported exports),
 - the helper body either:
 - consists only of scalar `let` bindings and a final
 `return`, or
 - ends in a simple `if` / `else` of the form:

                  ```silk
                  if <cond> {
                    [let ...;]
                    return <expr>;
                  } else {
                    [let ...;]
                    return <expr>;
                  }
                  ```

 where `<cond>` is a boolean expression built from comparisons
 over scalar expressions and boolean literals, and both
 branches end in `return`;
 such calls are lowered to IR `Call` instructions and compiled
 to native code together with `main`, using the System V AMD64
 scalar calling convention on `linux/x86_64` (integer-like
 scalars in `rdi`..`r9`, `f32`/`f64` in `xmm0`..`xmm7`, with
 additional arguments spilled to the stack); helpers may have
 more than six integer parameters, and this path is exercised
 in both Zig tests and C tests (see `the C ABI test harness`), or
 - a final `if` statement whose condition is a boolean expression:
 - for the purely constant subset, the condition is a
 **compile‑time boolean literal** (`true` or `false`) and each
 branch body itself satisfies the same “constant lets +
 `return` constant integer expression” rule, and
 - on `linux/x86_64`, a slightly richer branching `main` shape is
 also supported in which the body is exactly:

              ```silk
              fn main () -> int {
                if <cond> {
                  [let ...;]
                  return <expr>;
                } else {
                  [let ...;]
                  return <expr>;
                }
              }
              ```

 where `<cond>` is built from integer comparisons (`==`, `!=`,
 `<`, `<=`, `>`, `>=`) over integer expressions from the same
 constant subset; this shape is lowered to IR using `BrCond` and
 compiled to native code by the IR→ELF backend so that the
 condition is evaluated at runtime, or
 - one or more **trivial constant [`while`](/silk/wiki/language/flow-while/) loops** that appear before
 the final `return`, each of which has:
 - a condition that is a compile‑time boolean literal (`true` or
 `false`),
 - for `while false { ... }`, a body that is ignored by the
 constant backend, and
 - for `while true { ... }`, a body consisting of zero or more
 constant `let` statements followed by a `break;`, with no other
 control‑flow; loop invariants (`#invariant`) and variants
 (`#variant`) may be present but are treated as metadata and do
 not affect constant evaluation,
 - examples of supported forms include:

          ```silk
          fn main() -> int { return 0; }
          fn main() -> int { return 1; }
          fn main() -> int { return 1 + 2 * 3; }

          let answer: int = 21 * 2;

          fn main() -> int {
            return answer;
          }

          // Two-module imported constant example (module-set builds only):
          //
          // util.slk
          package util;
          export let answer: int = 42;
          //
          // app.slk
          package app;
          import util;
          fn main () -> int { return answer; }

          // Two-module imported function example (module-set builds only):
          //
          // util.slk
          package util;
          export fn add (x: int, y: int) -> int { return x + y; }
          //
          // app.slk
          package app;
          import util;
          fn main () -> int { return add(40, 2); }

          fn main () -> int {
            let a: int = 21;
            let b: int = a * 2;
            return b;
          }

          fn main () -> int {
            if true {
              return 0;
            } else {
              return 1;
            }
          }

          fn main () -> int {
            while true {
              break;
            }
            return 0;
          }
          ```

 - when these conditions hold and `output_path` names a valid path,
 `silk_compiler_build`:
 - evaluates the constant integer expression in the body of `main`,
 - emits a tiny native executable image directly using a Silk‑owned
 backend (no C stub, no external C compiler),
 - currently this backend writes a minimal target-specific executable
 that terminates the process with the evaluated `main` value:
 - ELF64 for `linux-x86_64`, `linux-x86_64-musl`,
 `linux-aarch64`, `linux-aarch64-musl`, and `android-aarch64`,
 - Mach-O 64-bit for `macos-x86_64`, `macos-aarch64`, `ios-aarch64`, `ios-simulator-aarch64`, and `ios-simulator-x86_64`,
 - PE32+ for `windows-x86_64` and `windows-aarch64`,
 - returns `true` on success with no last error recorded.
 - when the program is front‑end valid but outside this subset
 (e.g. `main` contains non‑constant expressions, references to
 non‑constant values, or calls that fall outside the simple
 helper‑call subset described above),
 or when the backend cannot produce an executable for the current
 platform or output path, the call returns `false` and records either
 an `E4001` / `E4002` diagnostic (for unsupported constructs or backend failures) or
 a descriptive string for I/O/argument errors as the last error.

- Error reporting:

  ```c
  SilkError *silk_compiler_last_error(SilkCompiler *compiler);

  size_t silk_error_format(const SilkError *error,
                           char            *buffer,
                           size_t           buffer_len);
  ```

 - `silk_error_format` returns a human-readable diagnostic message. When the compiler can associate the error with a source span, the formatted message includes the module name/path plus line/column and a caret snippet.
 - The text format and initial stable error code set are specified in [`Compiler Diagnostics`](/silk/docs/compiler/diagnostics/). Embedders should treat the formatted message as user-facing text (not a stable machine-readable protocol).

Ownership, lifetime, and thread-safety guarantees for these APIs must be clearly documented and kept in sync with the implementation.

ABI rules:

- All exposed functions must be C99-compatible.
- Data layouts must be stable and match the Silk side.
- Ownership and lifetime of any pointers passed across the boundary must be explicitly documented.

In addition, the embedding ABI must clearly distinguish:

- functions that consume Silk‑owned values (e.g. `SilkString` whose storage is owned by the runtime) versus
- functions that take ownership of data supplied by the embedder (and are responsible for freeing it via documented APIs).

Any deviation from the mappings documented in `External Declarations (`ext`)` must be justified here and reflected in tests.

### See Also

- [`libsilk(7)`](/silk/docs/man/libsilk.7/) — C99 ABI manpage for embedders.
- `silk/silk.h` — canonical public C header shipped with the library.
