Compile-time branches
A match, an if or an if const whose subject is a compile-time constant keeps the one arm its value selects - OperatingSystem.current is one - while every arm is still type checked on every machine and exhaustiveness is judged by the type.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
A branch on the operating system is an ordinary match on OperatingSystem.current. The compiler knows the value,
so it compiles only the arm the value selects - and it still type checks every arm, on every machine, so a mistake in
the Windows arm is found on Linux.
Example#
use OperatingSystem from "std/core"
fn searchPathSeparator(): String {
match OperatingSystem.current {
.Windows => ";"
.Linux | .MacOs | .FreeBsd | .Browser => ":"
}
}
print searchPathSeparator().byteLength()
// prints 1Syntax#
match <compile-time constant> {
<pattern> => <expression> // only the arm the value selects is compiled
...
}
if <compile-time constant Bool> { ... } else { ... }
if const <pattern> = <compile-time constant> { ... } else { ... }Rules#
OperatingSystem.current,Architecture.currentandByteOrder.currentare compile-time constants. The three types are instd/coreand not in the prelude, so a file that branches on where it runs says so with an import. Their value is the target the program is compiled for: the--targetoftorb build, the machinetorbruns on by default, and in the VM the machine the VM runs on, because the VM compiles a program where it runs it.A
matchwhose subject is a compile-time constant keeps one arm. Only the arm the value selects is compiled; the others produce no code, and a function only they call is not part of the program. A compile-time constant is a literal (true,3,'a', a text without interpolation), a case without fields, a tuple of constants,!,&&,||, the comparisons and+,-,*of integers,==and!=of those and of two cases whoseEqualsis derived, aconstof a module or astaticof a type whose initializer is one, and the three target constants. A tuple decides two dimensions at once:use OperatingSystem, Architecture from "std/core" fn knownPageSize(): Int? { match (OperatingSystem.current, Architecture.current) { (.MacOs, .Arm64) => 16384 (.Windows, _) => 4096 (_, _) => None } } print(knownPageSize() != 0) // prints trueEvery arm is still type checked, on every machine, for every target. An arm another target takes is checked exactly like the one this build takes, so a mistake in it is an error here:
Kompiliert nichtuse OperatingSystem from "std/core" fn systemName(): String { match OperatingSystem.current { .Windows => windowsName() .Linux | .MacOs | .FreeBsd => "posix" .Browser => "browser" } } print systemName() // error: Cannot find `windowsName` hereExhaustiveness and reachability are judged by the type, never by the value. A
match OperatingSystem.currenthandles every operating system or writes_, and no arm is unreachable because the value selects another one. So adding an operating system toOperatingSystemmakes every suchmatchan error that names the new case - the list of places that have to learn about it.Kompiliert nichtuse OperatingSystem from "std/core" fn systemName(): String { match OperatingSystem.current { .Windows => "windows" .Linux => "linux" } } print systemName() // error: `match` does not handle `.MacOs`, `.FreeBsd` and `.Browser`An
ifwhose condition is a compile-time constantBool, and anif constwhose subject is a compile-time constant, are decided the same way. They are not exhaustive, so a branch on the target is written as amatchwherever a new operating system should be a compile error.use OperatingSystem from "std/core" fn lineEnding(): String { if OperatingSystem.current == .Windows { return "\r\n" } "\n" } print lineEnding().endsWith("\n") // prints trueA local
constis not a compile-time constant, and neither is a top-levelconstof an entry file or a script, which is a local of the code the file runs. Afterconst system = OperatingSystem.current,match systemis an ordinarymatch, and every arm is compiled. Whether an arm is compiled depends on the line thematchis written on, never on data flow a reader has to follow.The patterns decide, and a guard is code. Where the value reaches an arm with an
ifguard first, thematchis an ordinary one from there, and every arm is compiled.A native that exists on some targets only may be reached only through arms those targets take. Reaching it on another target is a compile error of that target, at the call:
error: `Windows.tickCount` exists only on Windows, and this program is built for Linux --> src/main.trb:4:7 | 4 | print Windows.tickCount() | ^^^^^^^^^^^^^^^^^ = Only the arm of `match OperatingSystem.current` that the target selects is compiled = Reach it only from a `.Windows` arm, or check every target with `torb check --every-target`torb check --every-targetlowers every program and test once per target of the toolchain's list, without a C compiler, so this error is reported on any machine for a mistake only another system's build would meet.
What this is not#
Not a preprocessor. Every arm is TorbScript that has been parsed, resolved and type checked, and no declaration
exists on one target and not on another: a module of Windows code is checked in every build on every machine, and it
drops out of a Linux binary because nothing a Linux build compiles reaches it. #ifdef, #[cfg] and build tags
remove text before it is checked, which is how a change on one machine breaks the build of another unseen.
Not a cost at run time. No comparison is executed and no code for another system is in the binary. The C compiler is not asked to remove the other arms: the lowering never writes them, because an arm that is written references its natives, and a native of another system has no C function on this one.
Related#
- Exhaustiveness - the rules a
matchon the target is checked by. - Bindings - why a local
constis not a compile-time constant. - std/core -
OperatingSystem,ArchitectureandByteOrder. - torb check -
--every-target. - torb build -
--target.