

# 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.md`](/silk/docs/language/function-disciplines/)).
- `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,
 - GPU placement does not change this rule. Device code may consume a value
 produced by const evaluation, but it may not make a runtime call to a
 `const fn` or `const pure fn`. Use an ordinary `pure fn` for a helper that
 must execute on both CPU and GPU, or `attr(device=gpu)` for a device-only
 helper.

## 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 [`literals-string.md`](/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.
