Constants
Basic constants
const MAX_SIZE: i64 = 1024;
const PI: f64 = 3.14159265358979;
const APP_NAME: string = "MyApp";Constants are evaluated at compile time: the initializer never runs again at a use.
The value must be of the declared type, as for a let: const K: i64 = "s"; is a compile error ('K' is i64, given string), and so is each element of an array literal that isn’t (const T: [2]f32 = [K, 1.0]; with K an f64 needs K as f32). An integer literal into a narrower integer wraps, and a float literal, or arithmetic on them, is an f32 where one is declared (const THIRD: f32 = 1.0 / 3.0;).
A constant may be computed from other constants, its own module’s or an imported one’s:
import keys from "keys";
const SLOTS: i64 = 4;
const BYTES: i64 = SLOTS * 16;
const KEY_A: u16 = keys.KEY_A;
var buf: [BYTES]u8; // 64 bytesAn integer constant computed this way is a compile-time constant like a literal, so it can size an array. An array size is an integer literal or the name of an integer constant, this module’s ([N]u8) or an imported module’s ([keys.COUNT]u8).
The type can be left out. The constant then has its initializer’s type, and an array literal’s element type is its first element’s:
const STEPS = [0.5, 1.5]; // [2]f64
const PORTS = [KEY_A, 7]; // [2]u16, as KEY_A is a u16An initializer whose type can’t be written, such as const NONE = []; (an empty literal has no element type), is a compile error: write the type.
A constant is used like its value: ORIGIN.x of a struct constant, TABLE[i].name and TABLE.len of a constant table, NAME.len of a string.
Computed once
The compiler computes a constant’s value once; every use reads that value. Strings joined with + or built by interpolation are one literal:
const VERSION: i64 = 2;
const NAME: string = "tape";
const BANNER: string = "{NAME} v{VERSION}"; // "tape v2"
const CURSOR: []const u8 = "0011" + "0110" + "1100"; // one 12-byte literal
for (i in 0..CURSOR.len) { draw(CURSOR[i]); } // reads it, builds nothingThe same holds for numbers and bools computed from constants (const HALF: f64 = W / 2.0;) and for the strings in a constant table or struct (const T: [2]string = [PRE + "a", PRE + "b"];). A string the compiler can’t compute, such as one formatting a float, is a compile error in a constant: build it at run time into a let or var.
Computed by a function
A constant’s initializer may call a function. The compiler runs the call once, at compile time, and the constant holds the result:
import math from "math";
fn sq(x: i64) -> i64 { return x * x; }
const AREA: i64 = sq(4); // 16
const ROOT: f64 = math.sqrt_f64(2.0); // 1.4142...The call follows the rules of compile-time code:
your own functions and those of pure modules (math, str, json, …)
run; a function that reads or writes a module var, or reaches io,
fs, net or another capability module, is a compile error at the
constant. An initializer without a call is computed by the compiler
directly, without running any code.
Constant tables
const PRIMES: [8]i64 = [2, 3, 5, 7, 11, 13, 17, 19];
const GAMMA: [256]u8 = [ /* ... 256 values ... */ ];A constant array of literals is stored once, read-only, in the program (its
.rodata, aligned to its elements; one shared copy under tape run). Using
it reads that copy: nothing is rebuilt per function or per call, however
large the table. Array literals have no element limit. PRIMES.len is the
element count. Tables may be nested:
const SBOX: [2][4]u8 = [[1, 2, 3, 4], [5, 6, 7, 8]];
let x = SBOX[row][col];Constants inside functions
fn weekday_len(d: i64) -> i64 {
const NAMES_LEN: [7]i64 = [6, 7, 9, 8, 6, 8, 6];
return NAMES_LEN[d];
}A const in a function body is scoped to its block like a let. A table of
literals there is the same read-only copy as at module level: the statement
binds its address and builds nothing, however often it runs. A const can’t
be assigned, and neither can a const table’s elements.
A function-body const may also use the function’s parameters and locals; it
is then computed when its statement runs, like a let. One made of literals
and module constants is computed by the compiler: const P: string = ROOT + "/bin"; binds the one literal each time.
Computed constants with @tape
@tape const CRC_TABLE: [256]u32 = {
var t: [256]u32;
for (i in 0..256) {
var c: u32 = i as u32;
for (j in 0..8) {
if (c & 1 != 0) {
c = 0xEDB88320 ^ (c >> 1);
} else {
c = c >> 1;
}
}
t[i] = c;
}
return t;
};The @tape prefix means the value is computed by running tape code at compile time.
The block can declare arrays, assign elements (t[i] = c, t[i] += 1), loop
over ranges and cast with as; the table it returns is stored like any other
constant array.
Embedded file constants
@tape const SHADER_SRC: string = @embed("shaders/blur.glsl");
@tape const ICON_DATA: []const u8 = @embed("assets/icon.png");@embed reads a file at compile time and stores its contents as a constant.
A path starting with ./ or ../ is relative to the source file, any
other to the working directory; @tape pub const exports the constant to
importers (see @embed).
Last modified: