Compile-Time Evaluation

Part of every Rux program is settled while it is being compiled, before any of it runs. Constants are folded to values, sizeof and alignof to numbers, and a when keeps one branch and throws the others away unread. The compiler also describes the build itself — the target machine, the build profile, the compiler, the source location and user defines — as ordinary values a program can read. None of this costs anything at run time: by then a compile-time value is a plain constant, and a discarded branch does not exist.

There is no preprocessor and no separate build language. Everything in this chapter is written in Rux and checked by the same compiler.

What is known at compile time

ConstructKnown while compilingPage
literals and const declarationsthe value, folded from literals, operators, casts and other constantsConstants
sizeof(T), alignof(T)a type's size and alignment in bytes, as uintLayout
primitive constants such as int32::Max, uint::Bitstheir valuesPrimitive types
#target, #build, #compiler, #sourcefacts about the build and the source locationContext
#configuser-defined values from Rux.toml or --defineConfig
whenwhich branch is compiledConditional compilation
#Error, #Warndiagnostics that stop or annotate the buildDiagnostics
intrinsic declarationsthe values and functions the compiler itself suppliesIntrinsics

A const initializer may use literals, operators, casts, struct literals, other constants, sizeof/alignof, primitive constants and the scalar fields of the # values. A function call is never a compile-time value, even when it could be computed:

func Square(x: int) -> int { return x * x; }
const Nine = Square(3);
error: call to 'Square' is not a compile-time value
  help: initialize a constant from literals, operators, casts, and other constants

The # values come from Core

#target, #build, #compiler, #source and #config, and the directives #Error and #Warn, are declared in the Core package and imported like any other name:

import Core::{ #target, #build, #Error };

They are not keywords. A package that declares them itself — see Intrinsics — can stand in for Core.

Compile time and run time

Rux keeps the two kinds of decision visibly apart. A chain is if/else if throughout or when/else when throughout, never a mixture, so a reader always knows which branches the compiler has already settled:

ifwhen
Decidedwhile the program runswhile the program is compiled
Conditionany boolan expression the compiler can fold
Untaken branchcompiled and type-checkedparsed, then discarded before name resolution
Scope of a branchits ownnone: declarations and bindings stay after it
Where it can appearinside a function bodybetween declarations and inside bodies
flowchart LR
    src["source"] --> parse["parse"]
    parse --> fold["fold `when`<br/>keep one branch"]
    fold --> resolve["resolve names,<br/>check types"]
    resolve --> code["generate code"]
    fold -. "untaken branches<br/>never reach here" .-> resolve

A # value read in ordinary code is just as free: #build.debugAssertions in an expression compiles to the constant true or false.

See also