Compile time
Some of a program's work does not depend on its input: a lookup table, the contents of a data file, a value that follows from a constant. Nexium runs that work when the program is compiled, in the language itself. There is no macro language and no template metalanguage; comptime runs ordinary functions in an interpreter before the C compiler ever sees the program.
/// Built once, by the interpreter, when the program is compiled: the CRC-32
/// table that would otherwise be computed at every start or typed by hand.
fn make_crc_table() -> [256]u32 {
var table: [256]u32 = undefined
for n in 0..256 {
var c = n as u32
for k in 0..8 {
c = if c & 1 == 1 { 0xEDB88320 ^ (c >> 1) } else { c >> 1 }
}
table[n] = c
}
return table
}
const CRC_TABLE: [256]u32 = comptime make_crc_table()
fn crc32(data: []u8) -> u32 {
var c: u32 = 0xFFFFFFFF
for b in data { c = CRC_TABLE[((c ^ b as u32) & 0xFF) as usize] ^ (c >> 8) }
return c ^ 0xFFFFFFFF
}
/// A file embedded at compile time; the bytes are part of the executable.
const ROUTES = @embedFile("data/routes.csv")
/// Counted when the program is compiled, not when it runs.
const ROUTE_COUNT = comptime count_lines(ROUTES) - 1
fn count_lines(text: []u8) -> usize {
var n: usize = 0
for l in text.lines() { if l.len > 0 { n += 1 } }
return n
}
/// Any pure function can run at compile time: here, a lookup checked before the C compiler runs.
fn grade_points(grade: []u8) -> u32 {
if grade == "IV" { return 4 }
if grade == "V" { return 5 }
if grade == "VI" { return 6 }
return 0
}
const HARDEST = comptime grade_points("VI")
fn main() {
println("crc of \"nexium\": {x}", .{crc32("nexium")})
println("{} routes embedded, {} bytes, hardest grade {} points", .{ROUTE_COUNT, ROUTES.len, HARDEST})
for line, i in ROUTES.lines() {
if i == 0 or line.len == 0 { continue }
let cols = line.split(",")
println(" {} ({}): {} m", .{cols[0], cols[1], cols[2]})
}
println("{} {}", .{@typeName(u32), @sizeOf(u64)})
}
comptime test "the table starts the way every CRC-32 table does" {
expect_eq(CRC_TABLE[0], 0)
expect_eq(CRC_TABLE[1], 0x77073096)
expect_eq(CRC_TABLE[255], 0x2D02EF8D)
}
comptime test "the embedded data has a header" {
expect(ROUTES.starts_with("name,grade"))
}
crc of "nexium": 5a28df3e
3 routes embedded, 76 bytes, hardest grade 6 points
Dutch Rib (IV): 2400 m
South Face (VI): 2700 m
North Face (IV): 3000 m
u32 8
comptime and const#
const CRC_TABLE: [256]u32 = comptime make_crc_table()
comptime expr evaluates the expression during compilation and replaces it with its value. make_crc_table is a normal function; it could be called at run time too. Here its 256 entries become a static table in the executable, computed once, by the compiler, and crc32 at run time does two lookups per byte. const declarations are evaluated at compile time whether or not you write comptime, so the keyword is for the places you want to be explicit, or for an expression inside a function (let x = comptime fib(30)).
The interpreter runs the whole language: loops, structs, enums, List, Map, String, calls into the standard library. What it refuses is the outside world: no files, no clock, no randomness, no foreign calls, no mutable globals, so that compiling a program is a pure function of its sources and the same everywhere. A step budget turns an infinite loop into a compile error instead of a hang. (The REPL of chapter 4 runs the same interpreter with the world switched on.)
@embedFile#
const ROUTES = @embedFile("data/routes.csv")
The file's bytes, at compile time, as a []u8; the path is relative to the source file. The data is part of the executable, so the program has no file to find at run time, and comptime code can read it: ROUTE_COUNT is counted by the compiler.
comptime test#
comptime test "the table starts the way every CRC-32 table does" {
expect_eq(CRC_TABLE[1], 0x77073096)
}
A test that runs while the program is being checked. A failure is a compile error pointing at the expectation. It costs nothing at run time and cannot be forgotten, which makes it the right place for the facts a table or a constant must satisfy.
The builtins#
@typeName(T), @sizeOf(T), @truncate(T, x), @errorName(e), @embedFile(path), @weak(x), @refCount(x), @cImport(header), @cstr(literal): the @ names are the operations that need the compiler's knowledge rather than a library. There are nine, and they are all listed in the reference.
What this is for#
Tables (CRC, sine, colour palettes), embedded assets (a font, a shader, a default configuration), configuration checked at compile time (comptime test that the embedded data is well-formed), and any computation whose inputs are all known before the program runs. comptime is also how generics are spelled: fn largest(comptime T: type, ...) is a function whose first argument is known at compile time, and every generic call is a compile-time evaluation of the function's signature with that type.
Next: tests.