Types
primate has a small, fixed set of built-in types. Users compose them
into structures with type constructors (array, tuple, etc.) and
name them with type aliases.
Primitives
Numbers
| Type | Use |
|---|---|
i32 i64 | Signed integers. |
u32 u64 | Unsigned integers. |
i8 i16 | Only as enum backing types in v1. Widened to i32 in IR. |
u8 u16 | Only as enum backing types in v1. Widened to u32 in IR. |
f32 f64 | Floats. |
The “only as enum backing” restriction reflects how rare 8/16-bit
constants are in cross-language code; widening to i32/u32 keeps
generators simple. (When fixed-size arrays of u8 are useful — e.g.
RGB triples — they’re value types, not constants in the bit-twiddling
sense; see fixed-size arrays.)
A note on type fidelity
primate’s numeric types are richer than what most targets natively
distinguish. Each generator preserves what the target supports and
widens the rest — i32 and u32 survive faithfully into Rust, but
land as number in TypeScript and int in Python. The full mapping:
| primate | Rust | TypeScript | Python |
|---|---|---|---|
i8–i64 | i8 / i16 / i32 / i64 | number | int |
u8–u64 | u8 / u16 / u32 / u64 | number (or bigint for u64 opt-in) | int |
f32 / f64 | f32 / f64 | number | float |
duration | std::time::Duration | number (ms) or Temporal.Duration | timedelta |
string | &'static str | string | str |
regex | &'static str | string | str |
url | &'static str | string | str |
This widening only affects the type annotation in generated code;
the values themselves are bounds-checked against the declared primate
type at parse time, not at generation time. u32 X = 5GiB is an
out-of-range error before any generator sees it, even though the
TypeScript output would have been number.
Boolean
bool ENABLED = true
bool DEBUG = false
string
UTF-8. Regular and raw forms:
string GREETING = "Hello, world!"
string LITERAL = "Has \"quotes\" and \\ backslashes."
string RAW = r"no\\escapes\here"
string RAW_QUOTE = r#"with "quotes" inside"#
duration
A length of time. primate normalizes durations to nanoseconds internally.
duration TIMEOUT = 30s
duration RETRY_WAIT = 500ms
duration TICK = 16ms
duration RUN_FOR = 2h
duration LEASE = 1d
duration PRECISION = 1ns
Suffixes: ns, us (or µs), ms, s, min, h, d. Generators
emit per target — std::time::Duration in Rust, milliseconds-as-number
or Temporal.Duration in TypeScript (configurable), timedelta in
Python.
Byte sizes are integer literals
Byte sizes aren’t a separate type — they’re sugar on integer literals.
A literal like 100MiB is just 104_857_600 of whatever integer type
you declared:
u64 MAX_UPLOAD = 100MiB
u32 BLOCK_SIZE = 4KiB
u32 PACKET_SIZE = 1500B
Suffixes: B, KB/MB/GB/TB (decimal, ×1000), and
KiB/MiB/GiB/TiB (binary, ×1024). Allowed on i32, i64,
u32, and u64 literals.
primate bounds-checks the suffix-multiplied result against the
declared type. u32 X = 5GiB is an out-of-range error because
5 GiB exceeds u32::MAX.
regex
A regex pattern stored as a string. Validated at parse time.
regex FILENAME = "(?i)^[a-z][a-z0-9_]*\\.txt$"
Regex values are written as ordinary strings (not /.../ literals).
This keeps / free for a future division operator.
url
A URL string, validated at parse time.
url HOMEPAGE = "https://example.com"
Type constructors
array<T> — variable-length array
array<u32> QUEUE_DEPTHS = [4, 8, 16, 32]
array<string> ALLOWED_HOSTS = ["api.example.com", "cdn.example.com"]
Sugar: T[] is equivalent to array<T>. The formatter prefers the
sugared form.
array<T, N> — fixed-size array
type Pixel = array<u32, 3> // RGB triple
type Matrix = array<Pixel, 3> // 3×3 grid
Length-mismatch is a hard error: array<u32, 3> X = [1, 2] produces a
length-mismatch diagnostic.
In Rust this generates [T; N]; in TypeScript and Python a
homogeneous tuple of N elements. See the
matrices cookbook for a worked example.
optional<T>
optional<duration> RETRY_AFTER = 30s
optional<duration> NEVER = none
Sugar: T?. Values are either a regular T literal or the keyword
none.
map<K, V>
map<string, u32> SERVICE_PORTS = {
"http": 80,
"https": 443,
"ssh": 22,
}
Map keys can be strings, identifiers, or integers; the value type is arbitrary. Trailing comma triggers multi-line formatting (see Values).
tuple<A, B, …>
type RetrySchedule = tuple<u32, duration, duration>
RetrySchedule DEFAULT = [3, 100ms, 30s]
Heterogeneous, fixed-arity. Tuple values use square brackets — see Values for the rationale.
User-defined types
enum and type declarations introduce types you can use anywhere a
primitive type can go. type is structural: type Port = u32 and
u32 are interchangeable.
Enums and aliases live in their declaring file’s namespace.
Cross-namespace references are by qualified path or via use:
core::types::LogLevel DEFAULT_LEVEL = Info
Multi-line type expressions
Inside <>, newlines are insignificant. Long type expressions can wrap:
type ServiceConfig = map<
string,
tuple<duration, u64, optional<url>, regex>,
>
Trailing commas are accepted on the type side but don’t trigger multi-line formatting — type expressions tend to be short enough that the column budget alone suffices.