Skip to content

Compile-Time Evaluation

Updated for v2026.4.8. Compile-time builtins now use the canonical § sigil; legacy $-prefixed forms warn with W5001.

Janus provides first-class compile-time evaluation primitives. Code marked comptime executes during compilation — the results are baked into the binary as constants. No runtime cost.

Design principle: The § meta-sigil makes compiler-phase operations visually distinct from runtime functions. You always know what runs when.

Execute a block of statements at compile time:

comptime do
let table = build_lookup_table(256)
end

For single expressions:

const SIZE = comptime { calculate_optimal_size(PAGE_SIZE) }

All compiler builtins use the § prefix. This is not decoration — it signals “this executes in the compiler, not in your binary.”

BuiltinSignaturePurpose
§size_of§size_of(Type) -> i64Size of a type in bytes
§align_of§align_of(Type) -> i64Alignment requirement
§is_integral§is_integral(Type) -> boolTrue for integer types
§is_float§is_float(Type) -> boolTrue for floating-point types
§fmt§fmt(args...) -> stringComptime string construction
§compile_error§compile_error(msg) -> neverHalt compilation with message
BuiltinSignaturePurpose
§type_info§type_info(Type) -> TypeInfoFull type metadata
§type_name§type_name(Type) -> stringHuman-readable type name
§type_id§type_id(Type) -> u64Stable hash identifier
§fields§fields(Type) -> []stringList of field names
§has_field§has_field(Type, name) -> boolCheck field existence
§has_decl§has_decl(Type, name) -> boolCheck declaration existence
§field_type§field_type(Type, name) -> stringType of a named field
§field§field(value, name) -> TAccess field by name

Functions can require arguments to be known at compile time:

func make_array(comptime N: usize, fill: i64) [N]i64 do
var result: [N]i64 = undefined
inline for 0..N |i| do
result[i] = fill
end
return result
end
// N is resolved at compile time -- the loop is fully unrolled
const arr = make_array(4, 0)

Unrolls the loop body at compile time. Each iteration becomes straight-line code in the binary:

inline for fields |name| do
println(name)
end

Evaluates the discriminant at compile time and emits only the matching branch:

inline switch §type_info(T).kind {
.Int => handle_int(),
.Float => handle_float(),
_ => §compile_error("unsupported type"),
}

Conditional compilation — dead branches are eliminated entirely:

if comptime §is_integral(T) do
// This code only exists in the binary when T is an integer type
optimized_int_path()
else
generic_path()
end

Comptime features are progressively disclosed through profiles:

Capability:core:service:sovereign
comptime do...end blocksYesYesYes
comptime parametersYesYesYes
§size_of, §align_ofYesYesYes
§is_integral, §is_floatYesYesYes
§fmt, §compile_errorYesYesYes
§type_info, §type_nameYesYes
§type_id, §fieldsYesYes
§has_field, §has_declYesYes
Comptime allocationYes

The comptime evaluator enforces hard limits to prevent runaway compilation:

ResourceLimit
Evaluation steps1,000,000
Memory256 MB
Recursion depth1,000

Exceeding any limit produces a clear compile-time error — not a hang or crash.

Comptime arithmetic uses checked operations. Integer overflow, negative shifts, and out-of-range shift amounts produce compile-time errors — not silent wrapping. This aligns with Janus’s Syntactic Honesty doctrine: if the math is wrong, the compiler tells you.

// Compile error: IntegerOverflow
const bad = comptime { 9223372036854775807 + 1 }
// Compile error: IntegerOverflow (shift amount out of range)
const also_bad = comptime { 1 << -1 }

Practical Example: Lookup Table Generation

Section titled “Practical Example: Lookup Table Generation”
func hex_char(nibble: u4) u8 do
const table = comptime do
var t: [16]u8 = undefined
inline for 0..10 |i| do
t[i] = '0' + i
end
inline for 0..6 |i| do
t[10 + i] = 'a' + i
end
t
end
return table[nibble]
end

The lookup table is computed once during compilation. At runtime, hex_char is a single array index.


Next: Profiles System — understand how comptime capabilities scale across profiles.