Janus Language Reference
Janus Language Reference
Section titled “Janus Language Reference”Version: 2026-04-22 (v2026.4.8)
Profiles covered: :core (complete), :service (complete), :script (shipping), :cluster (draft)
Purpose: Single-source reference for humans and AI agents writing Janus code
1. Lexical Structure
Section titled “1. Lexical Structure”1.1 Identifiers
Section titled “1.1 Identifiers”IDENT <- [_A-Za-z][_A-Za-z0-9]*1.2 Literals
Section titled “1.2 Literals”// Integers42 // decimal0xFF // hexadecimal0b1010 // binary0o77 // octal1_000_000 // underscores for readability
// Floats3.142.5e101.0e-3
// Strings"hello" // UTF-8"line\nnewline" // escape sequences$"Value: {x}" // string interpolation$"Pi is {pi:.2f}" // with format spec
// Booleantruefalse1.3 Comments
Section titled “1.3 Comments”// Line comment//! Module-level doc comment (describes the file)/// Item-level doc comment (describes the next declaration)1.4 The Meta-Sigil (§)
Section titled “1.4 The Meta-Sigil (§)”All compiler builtins use § prefix. User functions MUST NOT use §.
§size_of(T) // compiler-phase operation§compile_error(msg) // halts compilation2. The Two Structural Laws
Section titled “2. The Two Structural Laws”Law 1: do...end for control flow (imperative, vertical)
if cond do ... endwhile cond do ... endfor iter do |x| ... endfunc name() do ... endcomptime do ... endtest "name" do ... endLaw 2: { } for data structures (declarative, containers)
struct { field: type }enum { Variant }match expr { pattern => value }extern struct { field: type }These two laws are non-negotiable. No exceptions.
3. Types
Section titled “3. Types”3.1 Primitive Types
Section titled “3.1 Primitive Types”| Type | Size | Description |
|---|---|---|
i8, i16, i32, i64 | 1-8 bytes | Signed integers |
u8, u16, u32, u64 | 1-8 bytes | Unsigned integers |
usize | platform | Pointer-sized unsigned |
f32, f64 | 4-8 bytes | IEEE 754 floats |
bool | 1 byte | true or false |
void | 0 bytes | No value |
:core universal integer model: i64 is the default integer type.
3.2 Compound Types
Section titled “3.2 Compound Types”// Arrays (fixed-size, comptime-known length)var buf: [32]u8 = undefined;const zeros: [4]i64 = .{ 0, 0, 0, 0 };
// Slices (runtime-length view into array)name: []const u8 // immutable byte slice (string)data: []u8 // mutable byte slice
// Pointersptr: *T // single-item pointerptr: *const T // const pointerptr: *[N]u8 // pointer to fixed-size arrayptr: [*]const u8 // many-item pointer (C interop)
// Borrow expressionsparseConsume[i32](* input) // borrow mutable valueformat[i32](42, * buf[..]) // borrow mutable slice view
// Optionalvalue: ?T // T or nullfield: ?u64 = null // optional with defaultJanus borrows existing values with prefix * at expression sites. It does
not use &mut, and it does not take references to literals inline. Bind the
value first, then borrow or slice it.
3.3 Struct
Section titled “3.3 Struct”// Compiler-native syntax (what `janus build` accepts):struct TreeEntry { name: []const u8, entry_cid: [32]u8, mode: u8,}
// Optional fields with defaultsstruct Change { message: []const u8, timestamp: i64, sequence: ?u64 = null, // optional, defaults to null lamport: ?u64 = null,}
// NOTE: `pub const Name = struct { ... };` is Zig heritage migration input.// The canonical Janus form is `struct Name { ... }` (no pub const =, no trailing ;).// Treat the heritage form as refactor debt, not target syntax for normal .jan code.3.4 Extern Struct (Wire-Compatible Layout)
Section titled “3.4 Extern Struct (Wire-Compatible Layout)”// Fixed memory layout, matches C ABI. Used for SBI, FFI, binary protocols.extern struct RequestFrame { version: u8, msg_type: u8, _pad: [2]u8 = .{ 0, 0 }, payload_len: u32, seq: u64,}extern struct guarantees field order, alignment, and size match C ABI rules. Use §size_of, §align_of, §offset_of for comptime layout verification.
3.5 Enum
Section titled “3.5 Enum”// Compiler-native syntax (what `janus build` accepts):enum HexError { InvalidChar, InvalidLength,}
// Enum with explicit backing type (planned — currently all enums are i64-backed)enum ObjectKind { blob, tree, change, checkpoint,}
// Non-exhaustive enum (catch-all for unknown values)enum MsgType { submit, ping, shutdown,}
// NOTE: `pub const Name = enum { ... };` is Zig heritage migration input.// The canonical Janus form is `enum Name { ... }` (no pub const =, no trailing ;).// Treat the heritage form as refactor debt, not target syntax for normal .jan code.3.6 Error Type
Section titled “3.6 Error Type”// Compiler-native syntax:error DiffError { DiffFailed, InvalidTree, StorageError,}
// NOTE: `pub const Name = enum { ... }` also works for error sets,// but the dedicated `error Name { ... }` keyword is preferred// as it communicates intent (these are failure modes, not data variants).3.7 Error Union
Section titled “3.7 Error Union”// A function that can fail returns Error!Successpub fn readRef(dir: Dir, io: Io, name: []const u8) RefError![32]u8
// The error union type: left is error, right is successHexError!u8 // returns u8 on success, HexError on failure![]u8 // inferred error set, returns []u8 on success4. Declarations
Section titled “4. Declarations”4.1 Variables
Section titled “4.1 Variables”// Immutable (preferred)const x = 42;const name: []const u8 = "graf";let result = compute(); // let also works for immutable
// Mutablevar counter: usize = 0;var buf: [64]u8 = undefined; // uninitialized fixed buffer4.2 Constants
Section titled “4.2 Constants”const CID_LEN: usize = 32;const PROTOCOL_VERSION: u8 = 1;const HEX_CHARS = "0123456789abcdef";pub const GTP_CAP_SBI: u8 = 0x08;4.3 Functions
Section titled “4.3 Functions”// Compiler-native syntax (what `janus build` accepts):// Uses `func` keyword, `do...end` blocks, `-> ReturnType` arrow syntax.func add(a: i64, b: i64) -> i64 do return a + bend
// Function with error returnfunc decodeNibble(c: u8) -> HexError!u8 do if c >= '0' and c <= '9' do return c - '0' end return HexError.InvalidCharend
// Function with allocator parameterfunc diffTrees( store: *Store, old_cid: ?[32]u8, new_cid: [32]u8, allocator: std.mem.Allocator,) -> ![]Transition do // ...end
// Method (self parameter)func deinit(self: *GrafRuntime) do self.scheduler.stop()end
// Comptime parameterfunc encodeFix(comptime N: usize, bytes: *const [N]u8) -> [N * 2]u8 do // N is known at compile time; array sizes are comptime-determinedend
// NOTE: `pub fn name() { ... }` is Zig heritage syntax still accepted by the compiler// for Zig-interop code. The canonical Janus form is `func name() do ... end`.4.4 Extern Functions (Zig Bridge)
Section titled “4.4 Extern Functions (Zig Bridge)”extern fn blake3_hash(data_ptr: i64, data_len: i64) -> i64;extern fn blake3_to_hex() -> i64;4.5 Test Blocks
Section titled “4.5 Test Blocks”// Test blocks use do...end (imperative logic, Law 2)test "writeRef + readRef round-trip" do var tmp = testing.tmpDir(.{}) defer tmp.cleanup()
try writeRef(tmp.dir, testing.io, "main", test_cid) const read_cid = try readRef(tmp.dir, testing.io, "main") try testing.expectEqualSlices(u8, &test_cid, &read_cid)end
test "basic addition" do assert(1 + 1 == 2)end5. Control Flow
Section titled “5. Control Flow”5.1 If-Else
Section titled “5.1 If-Else”// No parentheses around condition. Uses do...end.if count > 0 do process()else skip()end
// Single-lineif c >= '0' and c <= '9' do return c - '0'; end
// Optional unwrap (if-do with capture)if old_tree_cid do |otc| old_tree = store.getTree(otc) catch return DiffError.DiffFailed;end5.2 While
Section titled “5.2 While”var i: usize = 0;while i < N do out[i] = compute(i); i = i + 1;end5.3 For
Section titled “5.3 For”// Iterate with capturefor entries do |entry| process(entry.name, entry.cid);end
// Range iterationfor 0..N do |i| buf[i] = 0;end
// Exclusive rangefor 0..<10 do |i| // i = 0, 1, ..., 9end5.4 Match
Section titled “5.4 Match”// Exhaustive pattern matching (uses { } not do...end)match kind { .blob => decode_blob(data), .tree => decode_tree(data), .checkpoint => decode_checkpoint(data), else => return error.UnknownKind,}
// With guardsmatch request { .Create(data) when data.is_valid => process(data), .Delete(id) when id > 0 => delete(id), else => reject("invalid"),}Doctrine: else, _, and discard — one symbol, one meaning
| Symbol | Level | Purpose | Valid context |
|---|---|---|---|
else | Expression | Match fallback (catch-all) | match x { else => ... } |
_ | Pattern | Discard binding | let _ = expr, catch |_|, (_, 0, _) in destructure |
discard | Statement | Discard expression result | discard some_function() |
- Use
elsefor match fallbacks (catch-all arm) - Use
_when you don’t need a value in a pattern context (variable binding, catch, destructuring) - Use
discardwhen calling a function for side effects and ignoring its return value at statement level
.Variant patterns (e.g., .blob, .Create(data)) are type-inferred from the match subject and encouraged — the compiler already knows the enum type, so repeating it is noise.
5.5 Labeled Blocks and Break
Section titled “5.5 Labeled Blocks and Break”const value = blk: { if condition do break :blk 42; end break :blk 0;};6. Error Handling
Section titled “6. Error Handling”6.1 Error Propagation (try and ?)
Section titled “6.1 Error Propagation (try and ?)”// try: propagate error to callerconst data = try store.read(cid);const hi = try decodeNibble(hex[0]);
// ? suffix: same as try (alternative syntax)const result = fallible()?6.2 Error Handling (catch)
Section titled “6.2 Error Handling (catch)”// catch with fallback valueconst cid = fromHex(content) catch return RefError.InvalidRef;
// catch with error captureconst sched = Scheduler.init(alloc, 4) catch |err| { log.error($"init failed: {err}") return error.RuntimeInitFailed;}
// catch unreachable (assert success)var rt = GrafRuntime.init(alloc, 2) catch unreachable;6.3 Error Return
Section titled “6.3 Error Return”// Return an errorreturn HexError.InvalidChar;return error.DiffFailed;
// fail keyword (alternative)fail DivisionError.DivisionByZero6.4 Explicit Discard (discard)
Section titled “6.4 Explicit Discard (discard)”The discard keyword explicitly discards the result of an expression. This is used when calling a function for its side effects but ignoring its return value.
// Discard a return valuediscard some_function()
// Discard with error handlingdiscard may_fail() catch |err| do log_error(err)end
// Common use case: loggingdiscard logger.write("message")
// Multiple discards in sequencediscard posix.close(fd)discard allocator.free(buf)Note: Unlike Zig’s _ = expr syntax, Janus uses discard expr for explicit clarity. The discard keyword makes the intent obvious: “I am intentionally ignoring this result.”
6.5 Defer and Errdefer
Section titled “6.5 Defer and Errdefer”// defer: runs on scope exit (success or error)defer allocator.free(matched);defer rt.deinit();
// errdefer: runs ONLY on error exitvar out: std.ArrayListUnmanaged(u8) = .empty;errdefer out.deinit(allocator);try encodeValue(allocator, &out, value);return out.toOwnedSlice(allocator);
// Conditional deferdefer if owns_old do freeTree(allocator, old_tree); end7. Generics (SPEC-026)
Section titled “7. Generics (SPEC-026)”7.1 Generic Functions
Section titled “7.1 Generic Functions”Type parameters in square brackets. Always explicit at call site (no type inference).
// Definitionfunc identity[T](x: T) -> T do return xend
// Call site (type always explicit)let n = identity[i64](42)let s = identity[[]const u8]("hello")7.2 Trait Bounds
Section titled “7.2 Trait Bounds”func min[T: Ord](a: T, b: T) -> T do if a <= b do return a end return bend
// Multiple boundsfunc clamp[T: Ord + Numeric](value: T, lo: T, hi: T) -> T do if value < lo do return lo end if value > hi do return hi end return valueend7.3 Core Traits
Section titled “7.3 Core Traits”trait Eq { func eq(self, other: Self) -> bool func ne(self, other: Self) -> bool}
trait Ord: Eq { // Ord implies Eq (supertrait) func lt(self, other: Self) -> bool func le(self, other: Self) -> bool func gt(self, other: Self) -> bool func ge(self, other: Self) -> bool}
trait Numeric: Ord + Eq { // Numeric implies Ord and Eq func add(self, other: Self) -> Self func sub(self, other: Self) -> Self func mul(self, other: Self) -> Self func div(self, other: Self) -> Self}All integer and float primitives satisfy all three traits intrinsically. bool satisfies Eq only.
7.4 Monomorphization
Section titled “7.4 Monomorphization”Each unique (function, type_args) pair compiles to a separate specialized function:
min[i32](3, 5) // emits: min_i32min[f64](3.14, 2.7) // emits: min_f64Trait bounds are verified once at definition, not per instantiation.
8. Comptime (SPEC-027)
Section titled “8. Comptime (SPEC-027)”Compile-time evaluation. Code runs in the compiler, vanishes before the binary exists.
8.1 Comptime Blocks
Section titled “8.1 Comptime Blocks”// Module scope: evaluated once during compilationcomptime do assert(PROTOCOL_VERSION >= 1, "requires protocol v1+")end
// Expression form: single comptime valueconst SIZE = §{ calculate_optimal_size(PAGE_SIZE) }8.2 Comptime Parameters
Section titled “8.2 Comptime Parameters”func hex_encode(comptime N: usize, input: [N]u8) -> [N * 2]u8 do var output: [N * 2]u8 = undefined inline for 0..N |i| do output[i * 2] = charset[input[i] >> 4] output[i * 2 + 1] = charset[input[i] & 0x0F] end return outputend8.3 Comptime Type Parameters
Section titled “8.3 Comptime Type Parameters”// comptime T: type gives full introspective accessfunc decode(comptime T: type, bytes: []u8) -> ?T do const info = §type_info(T) // requires :service // ...end8.4 Builtin Table
Section titled “8.4 Builtin Table”:core+ builtins (available everywhere):
| Builtin | Input | Output | Purpose |
|---|---|---|---|
§size_of(T) | type | usize | Byte size of type |
§align_of(T) | type | usize | Alignment requirement |
§offset_of(T, field) | type, string | usize | Field byte offset in struct |
§is_integral(T) | type | bool | True for integer types |
§is_float(T) | type | bool | True for float types |
§fmt(args...) | variadic | string | Comptime string construction |
§compile_error(msg) | string | never | Halt compilation with message |
:service+ builtins (type introspection):
| Builtin | Input | Output | Purpose |
|---|---|---|---|
§type_info(T) | type | TypeInfo | Full type metadata |
§type_name(T) | type | string | Fully qualified name |
§type_id(T) | type | u64 | Stable hash identifier |
§fields(T) | type | []FieldInfo | Struct field list |
§has_field(T, name) | type, string | bool | Field existence check |
§has_decl(T, name) | type, string | bool | Declaration existence |
§field(val, name) | any, string | field type | Access field by name |
§field_type(T, name) | type, string | type | Type of named field |
8.5 Inline Control Flow
Section titled “8.5 Inline Control Flow”// inline for: unrolls at compile timeinline for §fields(T) |field| do const field_value = §field(value, field.name) process_field(field_value)end
// inline switch: eliminates dead branchesinline switch §type_info(T) do .Struct => |s| encode_struct(value, s) .Int => |i| encode_int(value, i) else => comptime do §compile_error("unsupported") endend
// if comptime: conditional compilationif comptime §is_integral(T) do optimized_int_path()else generic_path()end8.6 Wire Layout Verification Pattern
Section titled “8.6 Wire Layout Verification Pattern”extern struct ResponseFrame do seq: u64 status: u8 has_tip: u8 _pad: [2]u8 detail_len: u32 final_tip: [32]u8end
comptime do assert(§size_of(ResponseFrame) == 48, "frame must be 48 bytes") assert(§offset_of(ResponseFrame, "seq") == 0, "seq at offset 0") assert(§offset_of(ResponseFrame, "status") == 8, "status at offset 8") assert(§offset_of(ResponseFrame, "detail_len") == 12, "detail_len at offset 12") assert(§offset_of(ResponseFrame, "final_tip") == 16, "final_tip at offset 16")end9. Type Conversion (std.core.conv)
Section titled “9. Type Conversion (std.core.conv)”All conversions are explicit. Janus never silently coerces between types.
9.1 Checked Narrowing
Section titled “9.1 Checked Narrowing”func toInt[T](value: i64) !TReturns error if value doesn’t fit in T.
let narrow = toInt[u8](200)? // Ok: 200 fits in u8let fail = toInt[u8](256) // Err: Overflowlet neg = toInt[u8](-1) // Err: Overflow9.2 Unchecked Truncation
Section titled “9.2 Unchecked Truncation”func truncate[T](value: i64) -> TDiscards high bits. Always succeeds.
let byte = truncate[u8](0x1FF) // 0xFF (low 8 bits)let zero = truncate[u8](256) // 0x009.3 Widening Cast
Section titled “9.3 Widening Cast”func as[T](value: i64) -> TZero-cost identity or sign/zero-extend.
let wide = as[i64](narrow_i32) // safe widening9.4 Bit Reinterpretation
Section titled “9.4 Bit Reinterpretation”func bitcast[T](value: auto) -> TSame bits, different type. Source and target must have identical size and alignment.
let f = bitcast[f32](0x42280000) // 42.09.5 Enum Conversion
Section titled “9.5 Enum Conversion”// Integer to enum (checked)func toEnum[T](value: auto.Underlying) -> !T
let color = toEnum[Color](1)? // Color.Greenlet bad = toEnum[Color](99) // Err: Overflow
// Enum to integer (zero-cost)func fromEnum(value: auto) -> auto.Underlying
let n = fromEnum(Color.Green) // 1 (type is u8 if Color: u8)fromEnum returns the enum’s declared backing type, not i64. No generic parameter needed.
10. Memory Operations (std.mem.raw and bridge helpers)
Section titled “10. Memory Operations (std.mem.raw and bridge helpers)”// Raw byte operationsfunc copyBytes(dst: *u8, src: *const u8, count: usize)func moveBytes(dst: *u8, src: *const u8, count: usize)func fillBytes(dst: *u8, value: u8, count: usize)func zeroBytes(dst: *u8, count: usize)func compareBytes(a: *const u8, b: *const u8, count: usize) -> i32
// Raw pointer/address verbsfunc reinterpret[T](p: *auto) -> :sovereign *Tfunc realign[T](p: *auto) -> !*Tfunc pointerFrom[T](addr: usize) -> :sovereign *Tfunc addressOf[T](p: *auto) -> usize
// FFI boundary adaptersfunc constBytesPtr(buf: []const u8) -> *const u8func bytesPtr(buf: []u8) -> *u8func cStringPtr(z: []const u8) -> *const u8func opaqueFrom[T](p: *T) -> *anyopaquefunc opaqueInto[T](p: *anyopaque) -> ?*Tfunc constViewAt[T](buf: []const u8, offset: usize) -> ?*const TRaw ptrCast / alignCast syntax is not canonical Janus source. Normal .jan files should use named memory verbs or a bridge helper that owns the foreign boundary explicitly.
11. Imports and Module System
Section titled “11. Imports and Module System”11.1 The use Doctrine
Section titled “11.1 The use Doctrine”use is the Janus-native module import mechanism. It is the ONLY way to import
Janus modules. @import("path") is Zig’s private plumbing and MUST NOT appear in
Janus source code outside of use zig graft declarations.
The use statement creates a namespace binding named after the last path component.
The compiler resolves the path by joining identifiers with / and appending .jan,
relative to the source file’s directory.
// Import a module — binds the name "types" to the module's exportsuse identity.types
// Access symbols through the namespacelet did = types.parse_did("did:key:z6Mk...")let err = types.ResolveError.NotFound
// Import from same directoryuse resolveruse envelope
// Import from subdirectoryuse method.keyuse vc.normalize
// Import from Janus stdlibuse std.encoding.cboruse std.crypto.mldsa65Resolution algorithm (pipeline Stage 2.5):
- Join path components with
/, append.jan→ e.g.,identity/types.jan - Resolve relative to source file’s directory
- Fallback: resolve relative to CWD
- Future (SPEC-041): resolve by CID via content-addressed registry
11.2 Zig Grafting (use zig)
Section titled “11.2 Zig Grafting (use zig)”Use use zig only for explicit bridge modules or compiler-generated wrappers.
Do not treat it as a general-purpose import mechanism for normal Janus
application code. This is the ONLY context where file paths appear in import
statements.
// Import narrow Zig bridge filesuse zig "bridge/blake3_bridge.zig"use zig "bridge/net_bridge.zig"11.3 C Grafting (graft c)
Section titled “11.3 C Grafting (graft c)”For C libraries (per Grafting Doctrine GD-1: explicit, contained, replaceable):
graft c "oqs/oqs.h" as oqs link "oqs"graft c "argon2.h" as argon2 link "argon2"
// Use via the aliaslet rc = oqs.OQS_SIG_ml_dsa_65_verify(...)11.4 Re-exports (Sovereign Index Pattern)
Section titled “11.4 Re-exports (Sovereign Index Pattern)”Sovereign Index files re-export sub-module symbols for consumers:
// identity.jan — Sovereign Indexpub use identity.typespub use identity.resolverpub use identity.registry
// With alias (for name conflicts)pub use gateway.jsonrpc_handler as jsonrpcpub use gateway.sbi_handler as sbi11.5 What NOT to Use
Section titled “11.5 What NOT to Use”// ❌ WRONG — @import is Zig plumbing, not Janus syntaxconst types = @import("../identity/types.jan");
// ✅ CORRECT — use is Janus-native module loadinguse identity.types
// ❌ WRONG — const assignment from useconst types = use identity.types;
// ✅ CORRECT — use binds the namespace directlyuse identity.types// Access as: types.DID, types.parse_did(), etc.
// ❌ WRONG — Zig borrow syntaxfunc format[T](value: T, buf: &mut [u8]) -> !usize
// ✅ CORRECT — Janus borrows with prefix *func format[T](value: T, buf: *[]u8) -> !usizelet written = format[i32](42, * buf[..])
// ❌ WRONG — inline borrowed literalconst empty_bytes = &[0]u8{}
// ✅ CORRECT — bind the value, then slice itconst empty_bytes: [0]u8 = .{}const empty_slice = empty_bytes[0..]12. SBI (Sovereign Binary Interface)
Section titled “12. SBI (Sovereign Binary Interface)”Zero-encoding binary serialization. Wire bytes = memory bytes on same architecture.
12.1 API
Section titled “12.1 API”use std.sbi as sbi;
// Encodevar buf: [sbi.encodedSize(Sensor)]u8 = undefined;const frame = try sbi.encode(Sensor, * reading, * buf);
// Decode (zero-copy, same arch)const decoded = try sbi.decode(Sensor, * buf);
// Decode (copy, any alignment)const value = try sbi.decodeCopy(Sensor, * buf);
// Decode (cross-architecture, in-place endian fixup)const fixed = try sbi.decodeMut(Sensor, * mutable_buf);
// Schema fingerprint (comptime)const fp = §{ sbi.schemaFingerprint(Sensor) }
// Encoded frame size (comptime)const size = sbi.encodedSize(Sensor); // 24 (preamble) + §size_of(T)
// Layout analysis (comptime)const layout = §{ sbi.fieldLayout(Sensor) }const padding = §{ sbi.totalPadding(Sensor) }12.2 Wire Format
Section titled “12.2 Wire Format”[0..4) Magic: "SBI\x00"[4] Version: 0x01[5] ArchFlags (endianness, pointer width)[6..8) CapFlags (pure, deterministic)[8..24) Schema fingerprint (16-byte truncated BLAKE3)[24..) Raw extern struct bytes13. Structured Concurrency (:service Profile)
Section titled “13. Structured Concurrency (:service Profile)”13.1 Scheduler and Nursery
Section titled “13.1 Scheduler and Nursery”pub const Scheduler = std.Scheduler;pub const Nursery = std.Nursery;pub const Budget = std.Budget;pub const SpawnOpts = std.SpawnOpts;
// Initialize runtimevar rt = GrafRuntime.init(allocator, worker_count) catch unreachable;defer rt.deinit();
// Create nursery (structured scope)var nursery = rt.createNursery(Budget.serviceDefault());defer nursery.deinit();
// Spawn tasks (all must complete before nursery exits)discard nursery.spawn(task_fn, task_arg)discard nursery.spawn(other_fn, other_arg)13.2 Fiber Stack Sizes
Section titled “13.2 Fiber Stack Sizes”| Profile | Default Stack |
|---|---|
:core | 64 KB |
:service | 256 KB |
:sovereign | 512 KB |
Override via SpawnOpts.stack_size.
13.3 Cancellation
Section titled “13.3 Cancellation”Nurseries support cooperative cancellation via CancelToken. Cancellation propagates transitively through nested nurseries.
14. Common Patterns
Section titled “14. Common Patterns”14.1 Allocator Threading
Section titled “14.1 Allocator Threading”Nearly every allocation-using function takes an explicit allocator:
pub fn encode(allocator: std.mem.Allocator, value: anytype) ![]u8 { var out: std.ArrayListUnmanaged(u8) = .empty; errdefer out.deinit(allocator); try encodeValue(allocator, &out, value); return out.toOwnedSlice(allocator);}14.2 ArrayList Linear Scan
Section titled “14.2 ArrayList Linear Scan”For small collections, ArrayList with linear scan replaces HashMap:
var list = std.ArrayListUnmanaged(Entry).empty;defer list.deinit(allocator);try list.append(allocator, entry);
// Searchfor list.items do |item| if std.mem.eql(u8, item.name, target) do return item; endend14.3 Test Fixtures
Section titled “14.3 Test Fixtures”test "round-trip" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup();
// ... use tmp.dir for file operations}14.4 Struct Initialization
Section titled “14.4 Struct Initialization”// Compiler-native syntax — colon separators, no dot prefix, no trailing semicolons:let rt = GrafRuntime { scheduler: sched, allocator: allocator }
// All fields listed — no defaults neededlet frame = RequestFrame { version: PROTOCOL_VERSION, msg_type: MsgType.submit, payload_len: 1024, seq: 42,}
// Explicit leniency — accept defaults for unlisted fieldslet cfg = Config { host: "prod.example.com", ..defaults }
// NOTE: Zig-style `.field = value` with dot prefix is NOT Janus syntax.// Janus uses `field: value` (colon, not equals, no dot).Struct Initialization Completeness (Law 10):
Struct initializers MUST list all fields unless ..defaults is present. This eliminates silent bugs when fields are added to structs — every callsite is forced to either provide the new field or explicitly opt into defaults.
struct Server { host: str = "localhost", port: u16 = 8080, tls: bool = false }
let s1 = Server { host: "prod", port: 443, tls: true } // all fields — OKlet s2 = Server { host: "prod", ..defaults } // explicit leniency — OKlet s3 = Server { host: "prod", port: 443 } // ERROR: missing 'tls'14.5 Optional Handling
Section titled “14.5 Optional Handling”// Unwrap with if-captureif optional_value do |val| use(val);end
// Null coalescingconst result = maybe_value ?? default_value;
// Optional chainingconst name = user?.profile?.name;
// catch null (error to optional)const val = fallible() catch null;15. Operators
Section titled “15. Operators”15.1 Arithmetic
Section titled “15.1 Arithmetic”+, -, *, /, %
15.2 Comparison
Section titled “15.2 Comparison”==, !=, <, <=, >, >=
15.3 Logical
Section titled “15.3 Logical”and, or, not (NOT &&, ||, !)
15.4 Bitwise
Section titled “15.4 Bitwise”& (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (right shift)
15.5 Special
Section titled “15.5 Special”| Operator | Purpose |
|---|---|
? | Error propagation (suffix) |
?? | Null coalescing |
?. | Optional chaining |
|> | Pipeline |
.. | Inclusive range |
..< | Exclusive range |
16. Naming Conventions
Section titled “16. Naming Conventions”| Context | Convention | Example |
|---|---|---|
| Modules, Structs, Enums | PascalCase | TreeEntry, ObjectKind |
| Variables, Functions | snake_case | read_ref, payload_len |
| Constants | UPPER_SNAKE or snake_case | CID_LEN, PROTOCOL_VERSION |
| Type parameters | Single uppercase | T, N |
| Comptime builtins | §snake_case | §size_of, §type_info |
| Private fields/padding | _prefix | _pad, _pad2 |
17. Profile Summary
Section titled “17. Profile Summary”:core — Available
Section titled “:core — Available”Functions, variables, control flow, structs, enums, error handling, pattern matching, generics with trait bounds, comptime blocks/params, §size_of/§align_of/§offset_of/§is_integral/§is_float/§fmt/§compile_error, use zig, extern functions, SBI (extern struct encode/decode), tests.
:service — Available (adds to :core)
Section titled “:service — Available (adds to :core)”All of :core plus: §type_info, §type_name, §type_id, §fields, §has_field, §has_decl, §field, §field_type, nurseries, fibers, channels, spawn, M:N scheduler, structured concurrency, cancellation tokens, budgets.
:cluster — Draft (adds to :service)
Section titled “:cluster — Draft (adds to :service)”All of :service plus:
Actors and Grains:
actor Name(msg: T) do ... end— node-pinned actor with typed mailboxgrain Name(msg: T) do ... end— migratable actor with memory sovereigntymessage Name { ... }— sealed algebraic message protocolspawn Name(args) with { ... }— spawn with optionsspawn Name(args) on { ... }— cluster-aware placementsend ref, msg— send message to actor/grainreceive do ... end— blocking message receive with pattern matchingreceive ... after N => ...— receive with timeout
Supervision:
supervisor Name, strategy: .one_for_one do ... end— restart strategychild Type, args: [...], restart: .permanent— supervised child
Memory Sovereignty:
alloc[Local.Exclusive](value)— owned memory, serialized on migratealloc[Session.Replicated](value)— async-replicated to cluster peersalloc[Session.Consistent](value)— sync-replicated via consensusalloc[Volatile.Ephemeral](value)— dropped on migrate, reconstructed
Capability Gating:
requires CapX, CapY— function requires capabilities from caller (between return type anddo)@requires(cap: [...])— grain declares required hardware/software capabilities@mailbox(capacity: N, overflow: .drop_oldest)— mailbox policy@replicate(scope: .wing)— replication scope annotation
Cluster (runtime – see NexusOS SPEC-030–034 for implementation):
Cluster.join(config)— join cluster mesh (NexusOS runtime)cluster.advertise(manifest)— broadcast node capabilities (NexusOS runtime)cluster.migrate(ref, target, strategy)— migrate grain (NexusOS runtime)cluster.locate[T](name)— location-transparent grain lookup (NexusOS runtime)
Not Yet Available
Section titled “Not Yet Available”:compute (GPU/NPU, tensors), :sovereign (raw pointers, unsafe, comptime allocation), :script (REPL, JIT).
18. :cluster Profile — Actors, Grains, and Distribution (Draft)
Section titled “18. :cluster Profile — Actors, Grains, and Distribution (Draft)”18.1 Overview
Section titled “18.1 Overview”The :cluster profile provides distributed systems primitives: typed actors, migratable grains, supervision trees, and capability-gated placement. Every construct desugars to :service primitives. No hidden runtime.
18.2 Typed Message Protocols
Section titled “18.2 Typed Message Protocols”Every actor and grain declares its messages as a sealed algebraic type:
message StorageMsg { Read { sector: u64, len: usize, reply: Reply[ReadResult] }, Write { sector: u64, data: []const u8, reply: Reply[WriteResult] }, Flush { reply: Reply[FlushResult] }, Eject,}Desugars to enum + compile-time Serialize check.
18.3 The Reply[T] Channel
Section titled “18.3 The Reply[T] Channel”Synchronous request-response without blocking the actor:
let reply = Reply[ReadResult].new()storage_ref.send(StorageMsg.Read { sector: 0, len: 512, reply: reply })let result = reply.await(timeout: 5000)Desugars to oneshot channel + send + recv.
18.4 Actors
Section titled “18.4 Actors”Node-pinned, never migrates. Use for boot-critical singletons or non-serializable state.
message BootMsg { MountRoot { device: []const u8 }, StartInit { runlevel: u8 },}
actor BootLoader(msg: BootMsg) do var state: BootState = BootState.pre_init
receive do BootMsg.MountRoot { device } => do state = try mount_rootfs(device) end BootMsg.StartInit { runlevel } => do state = try start_init_sequence(runlevel) end endend18.5 Grains
Section titled “18.5 Grains”Location-transparent, migratable actors. State must be serializable.
@requires(cap: [.network])grain StorageService(msg: StorageMsg) do var index = alloc[Session.Replicated](StorageIndex.new()) var write_cache = alloc[Volatile.Ephemeral](WriteCache.new(capacity: 4096))
receive do StorageMsg.Read { sector, len, reply } => do let data = try read_sectors(index, sector, len) reply.send(ReadResult.Ok { data }) end StorageMsg.Flush { reply } => do let result = try write_cache.commit(index) reply.send(result) end end
reconstruct() do write_cache = WriteCache.new(capacity: 4096) endend18.6 Capability-Gated Functions (requires)
Section titled “18.6 Capability-Gated Functions (requires)”Functions that demand capabilities use requires clause — same position as where:
func read_config(ctx: *Context) !Config requires CapFsReaddo return ctx.fs.read("config.toml")end
func sync_backup(ctx: *Context) !void requires CapFsRead, CapFsWrite, CapNetHttpdo // ...endConjunction semantics: requires CapFsRead, CapFsWrite means caller must grant both.
18.7 Supervision Trees
Section titled “18.7 Supervision Trees”supervisor DeviceTree, strategy: .one_for_one, max_restarts: 5, max_seconds: 10 do
child NetworkDriver, args: [net_config], restart: .permanent child StorageService, args: [disk_config], restart: .permanent child DisplayDriver, args: [gpu_config], restart: .transient child TempLogger, args: [], restart: .temporaryendRestart strategies: .one_for_one, .one_for_all, .rest_for_one
Restart policies: .permanent (always), .transient (abnormal only), .temporary (never)
18.8 Symbols (Atoms)
Section titled “18.8 Symbols (Atoms)”Lightweight interned strings for tags and status codes:
:ok, :error, :timeout, :normal, :shutdownDesugars to Symbol.intern("string") — GC-managed, safe unlike BEAM atoms.
18.9 Memory Tags
Section titled “18.9 Memory Tags”| Tag | Migration | Replication |
|---|---|---|
Local.Exclusive | Serialized + moved | Never |
Session.Replicated | Async to peers | Continuous |
Session.Consistent | Sync via consensus | Raft/PBFT |
Volatile.Ephemeral | Dropped | Never |
18.10 Honest Desugaring
Section titled “18.10 Honest Desugaring”:cluster Surface | Desugars to |
|---|---|
message Foo { ... } | enum Foo { ... } + Serialize check |
actor Name(msg: T) do ... end | Message loop + typed mailbox |
grain Name(msg: T) do ... end | Actor + migratable + Serialize |
func f() !T requires CapX do ... end | Capability gate on call graph |
supervisor Name ... end | supervisors.start_link(ctx, Spec) |
receive do ... end | actors.receive().match(...) |
Reply[T].new() | channel.oneshot[T]() |
alloc[Tag](v) | actors.alloc(v, ReplicationPolicy { tag: ... }) |
:symbol | Symbol.intern("symbol") |
“The fastest serialization is no serialization. The clearest code needs no comments. The best compiler is the one that catches your mistakes before they exist.”