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:
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 fnis simply aconst fnthat also opts into thepurerules (seefunction-disciplines.md).const fnis a compile-time-only function:- it may be called only from compile-time contexts (for example
constinitializers 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 fnorconst pure fn. Use an ordinarypure fnfor a helper that must execute on both CPU and GPU, orattr(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
structvalues): -
a non-opaque
structtype, -
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):
stringvalues (string literals are supported directly inconstbindings),- 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
taskorasync, - must not declare a typed-error contract (
-> T | ErrorType...), - must have a non-
voidreturn 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
panicstatements, - must not declare
constlocal bindings, - may call only other
const fndeclarations, - is restricted to a small expression subset over scalar values:
- literals and local names (parameters and
letbindings; no globalconstreads in the Supported forms), ascasts between supported scalar types,- unary operators:
-,~,!, - binary operators:
- arithmetic:
+,-,*(division/modulo are not part of the const-eval subset), - bitwise:
&,|,^,<<,>>, - comparisons:
==,!=,<,<=,>,>=, ifexpressions (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, ... }) whenTis 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/elsestatements,whileloops with boolean conditions,break/continue,returnstatements.
Calling Const Functions#
The initial intended compile-time use site is const bindings:
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), 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.
Source repository · Edit this page · View Markdown