Conditional Compilation
when is the compile-time counterpart of if. Its condition is folded while compiling, the branch it selects is kept, and every other branch is discarded before names are resolved — so an untaken branch may call functions, name types or link libraries that do not exist in this build. That is what makes one source tree serve several targets and configurations.
when-chain = "when" condition "{" items "}"
{ "else" "when" condition "{" items "}" }
[ "else" "{" items "}" ]
when-match = "when" subject "{" arm { [ "," ] arm } [ "," ] "}"
arm = ( pattern { "," pattern } | "else" ) "=>" arm-body
items are declarations or statements, depending on where the when stands.
Chains
A chain is tested from top to bottom; the first condition that holds selects its branch, and a final bare else is taken when none does. Without an else, a chain whose conditions all fail compiles to nothing.
import Core::{ #compiler, SemanticVersion };
const Rux040 = SemanticVersion { major: 0, minor: 4, patch: 0 };
const Rux100 = SemanticVersion { major: 1, minor: 0, patch: 0 };
when #compiler.version >= Rux100 {
func Channel() -> char8[..] { return "stable"; }
} else when #compiler.version >= Rux040 {
func Channel() -> char8[..] { return "preview"; }
} else {
// Never resolved by this compiler, so a function nobody wrote is not an error.
func Channel() -> char8[..] { return LegacyChannel(); }
}
A chain keeps the keyword it opened with. else if inside a when chain — or else when inside an if chain — is an error:
error: expected 'when' after 'else' in a compile-time 'when' chain; 'if' is the run-time conditional
Match form
When the condition picks between several values of one subject, the match form is shorter than a chain of ==. Each arm lists one or more patterns before =>; the first arm with a matching pattern is kept. Patterns are compile-time values compared for equality — enum members (the .Linux shorthand takes its enum from the subject), integers and strings:
import Core::{ #target, #Error };
when #target.os {
.Linux, .FreeBSD => const Family: char8[..] = "unix";
.macOS => const Family: char8[..] = "darwin";
.Windows => const Family: char8[..] = "windows";
else => #Error("unsupported operating system")
}
The else arm must be last (error: the 'else' arm must be last in a 'when' match). Without one, a build that no arm matches stops:
error: no arm of this 'when' matches .FreeBSD
That is the right outcome when the code truly cannot work elsewhere; when it can, add an else arm. Commas between arms are optional, except after an arm whose body is a bare expression: there the comma is what ends the expression, so .Windows => Setup(), .Linux => … needs it.
Arm bodies
| Position | An arm body may be |
|---|---|
| between declarations | one declaration, a { … } block of declarations, an import, or an #Error/#Warn directive |
| inside a body | one expression — usually a call — or a { … } block of statements |
A statement written bare as an arm body is refused, because it is not an expression:
error: a 'when' arm body that is a statement must be written as a block, as in '.Windows => { return 1; }'
Where when can appear
- Between declarations — at the package root, inside a
module, and inside anotherwhenbranch. The taken branch's declarations are spliced into the enclosing list, as if written there. - Inside an
extendblock — selecting methods and associated constants. - Inside a function body — selecting statements.
when cannot stand inside a struct's field list, an extern block or an expression.
No new scope
A taken branch opens no scope. A let in it, or a declaration, is still there after the when:
import Core::{ #target };
import Io::PrintLine;
func Main() -> int {
when #target.os {
.Windows => { let separator = "\\"; },
else => { let separator = "/"; }
}
PrintLine("{}", separator);
return 0;
}
The same code with if would fail twice: each separator would end with its branch, and both branches would have to compile.
Untaken branches
An untaken branch is parsed — it must be well-formed Rux — but never resolved or type-checked. Calls to missing functions, unknown types, a library absent on this system, assembly for another architecture: none of them is an error there. The flip side is that a mistake in an untaken branch stays hidden until a build takes it. Build for each target that matters (rux build --target linux-x86_64, for instance) to check every branch.
Conditions
A condition — or a match subject — must be something the compiler can fold before name resolution:
| Operand | Example |
|---|---|
bool, integer, floating-point, character and string literals | true, 64, "windows-x86_64" |
| constants with foldable initializers | when Chosen == 20 |
primitive constants (with the extension imported from Core) | int32::Bits == 32 |
sizeof and alignof of a primitive type | sizeof(int) == 8 |
fields of #target, #build, #compiler, #source | #target.pointerBits, #build.mode |
the queries #target.HasFeature, #compiler.HasFeature, #config.Has, #config.Get | #config.Has("Verbose") |
#compiler.version and SemanticVersion constants | #compiler.version >= Rux040 |
They combine with == != on any of these, < <= > >= on numbers, characters and versions, the SemanticVersion methods (IsAtLeast, IsLessThan, …), && || !, and arithmetic. Floating-point values retain their declared width and use exact compiler arithmetic. Conditions can read the intrinsic finite limits, infinities and NaNs at every width, including float512::Max, and compare values beyond the float64 range. Integer casts retain or truncate all bits at the requested width. A cast to a character width never truncates: the value is checked against the width, and one it cannot hold makes the condition invalid. sizeof and alignof of a non-primitive type, and function calls, are not available:
error: 'when' condition is not a valid compile-time expression
A let binding is never a compile-time value (error: 'minor' is not a compile-time constant). Enum-valued fields compare only with == and !=; ordering #target.os < .Windows is the same "not a valid compile-time expression" error. A misspelt member lists the real ones:
error: '.MacOS' is not a variant of 'OperatingSystem'; the variants are: .FreeBSD, .Linux, .macOS, .Windows