Skip to content

Compile-Time Evaluation

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.