Current Language Surface

Normative 0.0.0-dev syntax and semantic boundaries for current source.

This page summarizes the current 0.0.0-dev source surface. Zynx is unreleased, so this is a normative reference for current documented behavior, not a compatibility promise for future commits. If the compiler disagrees with this page, treat that as a compiler bug or documentation bug until resolved. Focused reference pages give examples, but they should not contradict this page.

Source And Tokens

The current keyword set is closed:

async break case const continue else enum fn for if import in let loop match
return select struct union while defer await asm as interface test from error
catch try throw assert throws type distinct export extern foreign intrinsic unsafe with

Built-in type-name tokens are:

i8 i16 i32 i64 i128
u8 u16 u32 u64 u128
f16 f32 f64 f128
usize isize
str bool null void

Copy and Unique are reserved language identifiers with special contracts, but they are not keyword tokens.

Double-quoted string literals process escapes such as \n, \t, \r, \e, \\, \", \', \xNN, and \u{...}. Single-quoted string literals are raw text: they do not process escapes or interpolation and cannot contain a single quote. Hash raw strings such as r#"..."# remain available when the raw text itself contains a single quote. After escape processing, every string literal must be valid UTF-8 text and must not contain an embedded NUL byte.

Modules And Packages

Normal source loads modules with import, project manifests, exact package dependencies, lockfiles, and source or static .zxm bundles. Dynamic require(...) is rejected in normal source.

import std.os;                    // binds os and std.os
import std.os as sys;             // binds only sys
import { write } from std.os;     // binds write
import { write as out } from std.os;

Project dependencies are exact git dependencies in zynx.json, selected by zynx.lock, and restored with zynx fetch. Package registries, semver ranges, publishing, signing, mirrors, and automatic vendor archives are deferred.

Top-level declarations are module-private by default. A declaration is part of the module interface only when marked with export, @export, or named in an export { ... }; list. Import aliases do not bypass privacy, and generated .zxm interfaces expose exported declarations without re-exporting private helpers.

Attributes

The current source attribute table is closed. Unknown source attributes are rejected, and user-defined decorators are not part of 0.0.0-dev. Attributes must appear before declaration modifiers such as export, extern, and intrinsic.

The recognized table includes export/extern/intrinsic attributes, the foreign declaration form, codegen hints such as @inline, interop naming/layout attributes such as @symbol, @unmangled, @c_name, @native, @packed, @strict, and @bits, member attributes such as @private and @readonly, typed JSON decode helpers @derive(json), @json_allow_unknown, @json_key, @json_optional, and @json_skip, target filters @abi, @arch, and @platform, and diagnostic helpers @deprecated, @discardable, @suppress, and @unused.

Names beginning with __zynx_ are compiler-private generated names, not a public extension mechanism.

Types

Current type syntax includes built-ins, named types, generic instantiations, references, raw pointers, nullable types, error unions, arrays, tuple types, callable types, fixed-size numeric vectors, and Unique<T>.

throws(...) belongs after the nullable suffix on value types:

let maybe: i32? throws(ParseError);

Function declarations and callable types put the thrown error set before the return arrow:

fn parse(text: str) throws(ParseError) -> i32
let f: (str) throws(ParseError) -> i32;

void is valid as a function return type, in void throws(ErrorSet), as *void, and in current async APIs such as future.Future<void>. Other by-value uses of void are rejected.

const on types creates a read-only view only for checked references, raw pointers, str, and array or slice views:

const &T
const *T
const str
const [T]

Plain value types such as const i32, const String, const vector<T, N>, and const Future<T> are invalid type forms. Use a const declaration for an immutable binding.

Text, Unicode, And Bytes

str is borrowed UTF-8 text and String is owned UTF-8 text. Both reject embedded NUL bytes. str.size and String.size() are byte counts; str.length and String.length() count Unicode scalar values, not grapheme clusters.

Text indexing and text slicing are not 0.0.0-dev operations. Use std.bytes.Bytes when byte indexing or slicing is intended, then validate with std.unicode before treating bytes as text.

std.bytes.Bytes is the arbitrary byte boundary. It may contain NUL bytes and does not promise UTF-8 validity. String(bytes.Bytes) validates strict UTF-8 and rejects NUL bytes before creating owned text.

Call arguments support borrowed, zero-copy view conversions for the common text/byte boundary: String and const &String to str, and str, String, const &String, interpolated strings, and [u8] slices to bytes.Bytes. Stored or returned views remain explicit with text.str(), text.utf8(), or bytes.Bytes(...).

Numeric Semantics

Normal integer arithmetic is checked in debug and release builds. +, -, *, unary -, and compound assignment forms trap on overflow or underflow. Division and remainder trap on zero divisors and signed minimum divided by -1.

Integer shifts require a shift count smaller than the bit width. Out-of-range runtime shifts trap, and invalid constant shifts are rejected.

Runtime traps abort immediately. They are not throws(...) errors and do not run defer, automatic drops, with cleanup, or async frame cleanup.

Explicit stdlib namespaces provide alternate integer policies: std.arith.checked, std.arith.wrapping, std.arith.saturating, and std.arith.overflowing.

Control Flow And Operators

if and while conditions may use name := expression as the whole condition expression. The assigned value is tested by the ordinary condition rules, and the binding lives in the surrounding block scope. while condition bindings currently require copyable values.

Structs and interfaces can overload the closed operator set: unary +, unary -, !, ~, binary arithmetic/bitwise/shift operators, comparisons, subscript [], and plain indexed assignment []=. Compound indexed assignment such as container[index] += rhs does not call []=.

Methods without a first self parameter are static methods. The old @static method attribute is not part of the current source surface.

References, Unsafe, And Volatile

Checked references use &T and are tracked by the analyzer. Raw pointers use *T and are lower-level interop values.

Reference creation is explicit for bindings, returns, ordinary function arguments, callable values, and non-receiver method arguments. Write &value for a &T or const &T parameter, or pass an expression that is already reference-valued. Method receivers keep receiver auto-borrowing for self: &T, and operator overload operands keep receiver-style borrowing.

Unsafe operations require an unsafe context:

unsafe {
    *ptr = 1;
}

Unsafe context is lexical. It does not disable ordinary bounds, cast, lifetime, or send-safety checks. unsafe fn means calls require unsafe context; the body is not implicitly unsafe.

Current volatile access is limited to:

@zynx.volatile.read<T>(ptr)
@zynx.volatile.write<T>(ptr, value)

Volatile requires unsafe context. It is not atomic and is not a synchronization primitive. Atomics are not part of 0.0.0-dev: there is no public atomic {} block and no public @zynx.atomic.* intrinsic.

FFI

foreign "C" fn and foreign "C" let declare platform C ABI symbols. foreign library "name" { abi "C"; ... } groups declarations that share a link-time library name. Foreign calls require unsafe context.

extern fn and extern let are reserved for external Zynx-linkage declarations. They are not the C FFI spelling.

The current foreign type surface allows integer and float scalars, raw pointers, and void returns. It rejects managed text, slices, fixed arrays by value, vectors, tuples, nullable references, error unions, futures, groups, closures, interfaces, generic foreign functions, foreign methods, async foreign functions, throwing foreign functions, variadics, and by-value aggregate ABI.

C strings and buffers cross FFI as raw pointers with explicit ownership and length or terminator contracts. str and String do not cross FFI directly.

Async

Async calls return cold std.future.Future<T> values. Calling an async fn evaluates arguments immediately and captures them into a future frame, but the body starts only when the future is consumed by await or a structured future API.

The public async surface is std.future: direct await, future.all, future.race, future.timeout, and future.Group<T, E>.

Futures and scoped tasks are same-runtime values. They cannot be sent through channels or other thread-capable boundaries without a future send-safety contract.

Cleanup And Traps

Language exits unwind source scopes. For every exited scope, defer bodies run first, then automatic drops for locals whose lifetime ends at that scope.

drop(self) is the language destructor form. std.drop(value) consumes one local owner early; the lower-level @drop(value) spelling is legacy/internal. Automatic drops may also run at liveness drop points after a value's last use.

Runtime traps are different from language exits. Bounds failures, checked arithmetic traps, invalid casts, null dereference, vector lane traps, assertion failures, and explicit trap builtins abort immediately and do not run cleanup.

Async cancellation is cooperative task completion, not source-level unwinding. Owned future frames are still destroyed exactly once by their owning runtime, but defer does not serve as a cancellation hook across await.