Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Values

primate value literals are deliberately compact and unambiguous. There are no expressions; every constant is a literal of its declared type.

Numeric literals

Integers can be decimal, hex, binary, or octal, with _ as a digit separator:

i32 SMALL    = 8
i32 BIG      = 1_000_000
i32 NEGATIVE = -5
i32 HEX      = 0xFF
i32 BINARY   = 0b1010
i32 OCTAL    = 0o755

Floats:

f64 PI       = 3.141_592
f64 SCIENT   = 1.5e10
f64 NEGATIVE = -0.5

Hex, binary, and octal literals do not accept unit suffixes (the 30s form). Unit suffixes only apply to decimal literals.

Booleans

bool ON  = true
bool OFF = false

Strings

Regular strings allow standard escapes (\n, \r, \t, \0, \\, \"):

string GREETING = "Hello, world!"
string PATH     = "C:\\Users\\val"

Raw strings have no escapes and can include unescaped quotes by adding #s:

string PATTERN = r"raw\nstring"
string SQL     = r#"SELECT * FROM users WHERE name = "alice""#

duration literals

duration FAST   = 50ms
duration SHORT  = 5s
duration MED    = 5min
duration LONG   = 2h
duration LEASE  = 1d
duration BACKUP = 1w
duration TINY   = 1us
duration TINIER = 100ns

Suffixes: ns, us, ms, s, min, h, d, w. (m is also accepted as an alias for min.)

Negative durations are allowed via -:

duration BACKDATED = -1d

Byte-size literals

Byte-size suffixes are sugar on integer literals (no separate type):

u32 SMALL  = 512B
u32 PACKET = 1500B
u64 UPLOAD = 100MiB
u64 DISK   = 1TB

Suffixes: B, KB/MB/GB/TB (decimal, ×1000), and KiB/MiB/GiB/TiB (binary, ×1024). Allowed on i32, i64, u32, u64. The suffix-multiplied result is bounds-checked against the declared type — u32 X = 5GiB is out-of-range.

Percentage literals

% divides by 100 and is allowed on any float literal (integer or decimal) assigned to an f32/f64:

f64 ROLLOUT     = 5%       // 0.05
f64 OPACITY     = 12.5%    // 0.125
f64 SAMPLE_RATE = 100%     // 1.0

This is purely sugar — the generated value is a plain float in the target language; the percentage notation only exists at the source level for readability.

none

For any optional<T>, none represents “absent”:

optional<duration> RETRY_AFTER = none
optional<string>   FALLBACK    = "v1"

Array and tuple literals — […]

Both arrays (homogeneous) and tuples (heterogeneous) use square brackets. The parser produces a single shape; the lower pass picks array vs tuple based on the declared LHS type.

type V3 = array<u32, 3>
V3 RGB_RED = [255, 0, 0]

type RetrySchedule = tuple<u32, duration>
RetrySchedule DEFAULT = [3, 100ms]

Why […] for both? Visually consistent: ordered collections all use []. Matches TypeScript’s tuple syntax.

Map literals — {…}

map<string, u32> PORTS = {
    "http":  80,
    "https": 443,
}

Map keys can be strings, bare identifiers (treated as strings), or integers.

Magic trailing comma

A trailing comma on the last element of a collection literal is a signal to the formatter: keep this multi-line, even if it would fit on one line.

type Mat3 = array<array<u32, 3>, 3>

// Compact: fits on one line, formatter keeps it inline.
Mat3 SMALL = [[1, 0], [0, 1]]

// Trailing comma → formatter keeps it expanded as written.
Mat3 IDENTITY = [
    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1],
]

This is the rule Prettier popularized: the user opts into multi-line layout by typing one extra character. Round-trip is stable: the formatter always emits a trailing comma when wrapping multi-line.

The rule applies to value-side […] and {…} literals. Type-side generic arguments (tuple<A, B,>) accept a trailing comma but don’t trigger multi-line formatting — types are usually short enough that the column budget alone suffices.

Enum-variant values

When the LHS type is an enum, the value is a variant:

enum LogLevel: u8 {
    Debug = 0,
    Info  = 1,
    Warn  = 2,
    Error = 3,
}

LogLevel DEFAULT_LEVEL = Info
LogLevel STRICT_LEVEL  = LogLevel::Warn

Both forms are accepted: bare (when the enum is in scope) or qualified. Cross-namespace references use the full path: core::types::LogLevel::Info.

Type-checking

primate checks every value against its declared type at lower time. Common diagnostics you’ll hit:

  • type-mismatchu32 X = "foo" (string for an integer)
  • length-mismatcharray<u32, 3> X = [1, 2] (wrong arity)
  • invalid-enum-variantLogLevel L = Bogus (variant not in enum)

See Diagnostics for the full list.