# zo programming language — full documentation
> Aggregated documentation for LLM ingestion. Generated from `apps/site/src/content/initiation/en/*.md`. For the curated index with link-only entries, see [/llms.txt](https://zo.compilords.house/llms.txt).
# install
A short setup before we open the first page. Two commands, two minutes.
> « Simplicity is a prerequisite for reliability. » — Edsger W. Dijkstra
## get the binary
```sh
curl --proto '=https' --tlsv1.2 -sSf https://zo.compilords.house/install.sh | sh
```
The script downloads and extracts the zo compiler into `bin/zo` and adds it to your `PATH` so zo is reachable from any shell.
## verify
Confirm zo is reachable from your shell.
```sh
zo --version
```
Succesfully it will display `zo x.x.x`. The number depends on the latest release.
## trouble?
Drop into the [discord](https://discord.gg/JaNc4Nk5xw) or open a [GitHub issue](https://github.com/invisageable/zo/issues) — fastest path to a fix.
You're ready. Turn the page.
---
# prologue
[tsh-tsh]
This initiation is for the `zo` programming language.
What are we waiting for to improve our developer experience? Why does having to wait several seconds, or even minutes, to get feedback on the correctness of our program bother absolutely no one?
The quality of our current tools and infrastructure borders on mediocrity. Fortunately, some developers, resistant to this pervasive nonsense, perpetually continue to refine our ecosystem. To accept this mediocrity is to submit to the dictates of executive committees who force their products on us through waves of intensive marketing.
zo is not a revolutionary programming language: its concepts come from languages that have proven themselves. zo does not solve any new problem that another language hasn't already solved. It is a different language, but one that aims to be familiar, so that people from diverse backgrounds can explore low-level programming through a high-level language.
Whether you are back-end, front-end, a hacker, curious, a nerd, a scientist, a Ph.D., or a creative, zo is just another field of possibilities among many. It’s up to you to see if you want to discover more.
Our convictions rest on simplicity, software harmony, and the developer experience. For this, with the compilords, we focused on the minuscule details that matter, but that everyone pretends not to see. The main idea remains to open new dimensions in which our thoughts transform into a series of zeros and ones, without ever sacrificing the joy.
JOiN THE DEVOLUTiON.
---
# language design
## keywords
zo has 53 keywords in total:
- Namespacing
- 4 letters
- pack
- declares the current package
- load
- imports items into scope
- Type Definitions
- misc letters
- abstract
- declares a behavior contract
- struct
- declares a record with named fields
- apply
- attaches behavior to a type
- enum
- declares a tagged union
- type
- declares a type alias
- Member Definitions
- misc letters
- fun
- declares a function
- ffi
- declares a foreign function binding
- val
- declares a compile-time constant
- imu
- declares an immutable binding
- mut
- declares a mutable binding
- fn
- declares a closure
- Control Flow
- misc letters
- continue
- skips to the next iteration
- return
- returns a value from a function
- match
- pattern matches across arms
- while
- loops while a condition holds
- break
- exits the current loop
- else
- alternate branch of an if
- when
- ternary expression
- loop
- infinite loop
- for
- iterates over a range or collection
- if
- conditional branch
- Concurrency
- misc letters
- supervise
- declares a supervised task scope
- nursery
- declares a structured task scope
- select
- multiplexes channels or tasks
- thread
- marks an OS thread spawn (parser-synthetic)
- spawn
- launches a concurrent task
- await
- suspends until a task completes
- Infixes
- misc letters
- and
- ...
- as
- casts a value to a type
- is
- type test (reserved)
- Modifiers
- misc letters
- group
- ...
- wasm
- marks an item for WebAssembly
- pub
- marks an item as public
- Qualifiers
- 4 letters
- Self
- refers to the current type
- self
- refers to the current instance
- Values
- misc letters
- false
- boolean false literal
- true
- boolean true literal
- Integers
- misc letters
- uint
- the default unsigned integer (32-bit)
- int
- the default signed integer (32-bit)
- s16
- 16-bit signed integer
- s32
- 32-bit signed integer
- s64
- 64-bit signed integer
- u16
- 16-bit unsigned integer
- u32
- 32-bit unsigned integer
- u64
- 64-bit unsigned integer
- s8
- 8-bit signed integer
- u8
- 8-bit unsigned integer
- Floats
- misc letters
- float
- the default floating-point (64-bit)
- f32
- 32-bit floating-point
- f64
- 64-bit floating-point
- Primitives
- misc letters
- bytes
- byte buffer
- bool
- boolean type
- char
- Unicode character
- str
- UTF-8 string
- </>
- template fragment type
- Fn
- function type
## operators
### unary
| Precedence | Operator |
| 0 |
+
-
! |
### binary
| Precedence |
Operator |
| 1 |
||
|
| 2 |
&&
..
..=
|
| 3 |
==
!=
|
| 4 |
<
<=
>
>=
|
| 5 |
|
|
| 6 |
^
|
| 7 |
&
|
| 8 |
<<
>>
|
| 9 |
+
++
-
|
| 10 |
*
/
%
|
| 12 |
.
|
### assignments
| Operator |
=
+=
-=
*=
/=
%=
&=
|=
^=
<<=
>>=
:=
::=
|
### others
| Operator |
Name |
->
|
arrow (return type) |
=>
|
fat arrow (match arm) |
=:>
|
template fat arrow (template-body closure) |
|>
|
pipe arrow (reserved) |
::
|
path separator |
?
|
question |
@
|
at |
### delimiters
| Open |
Close |
Name |
(
|
)
|
parentheses |
{
|
}
|
braces |
[
|
]
|
brackets |
<>
|
</>
|
template fragment |
### punctuations
| Symbol |
Name |
,
|
comma (list separator) |
;
|
semicolon (statement terminator) |
:
|
colon (type ascription) |
_
|
underscore (wildcard pattern) |
#
|
hash (reserved) |
$
|
dollar (reserved) |
%%
|
attribute marker |
...
|
ellipsis (spread / variadic) |
---
# introduction
This guide is your initiation to the zo programming language ecosystem. Read sequentially on your first pass, then skip around once you grasp the architectural patterns.
## how to use this guide
Every lesson delivers a high-fidelity snapshot of a functional zo program. Pay close attention to the comments: documents comments (`-!`) leverage raw Markdown formatting to establish systemic guidelines, while line (`--`) and block (`-* *-`) markers isolate execution mechanics.
```zo
-! Sup? I'm a doc comment, I support markdown
-! format. What's good?
```
```zo
-- I'm a line comment, I don't care about markdown.
```
```zo
-*
From my side I'm a block comment, I'm happy to help
for details that matter.
*-
```
Every executable compilation unit inside zo must expose an explicit, non-colored entry block called `main`:
```zo
-- Wassup?! I'm `main`, a function.
-- Use me as a entry point with `fun` keyword.
fun main() {
-- This program does nothing... yet.
}
-! ## the capstone.
-!
-! - every programs must declare a `main` block
-! to serve as the runtime launchpad.
```
---
# hello
Let's spin up your first interactive instance. We trigger text printing operations via an internal optimized wrapper.
```zo
-! Yo! Let's start with a simple program.
-! In this lesson, we learn how to print a message.
fun main() {
-- We call our buddy `showln`.
-- It tells the compiler to display the value
-- passed as argument with a newline at the end.
showln("hello, hacker");
}
-! ## the capstone.
-!
-! - `showln` is an internal compiler builtin. No
-! import or namespace matching required.
```
---
# literals
## numbers
Programming boils down to memory allocation layout and data mutations. Data arrives in primary primitives called literals. You do not need to memorize these constraints instantly, but you must respect their sizes.
> *All contextual code snippets assume code is running inside an active `fun main()` execution block.*
### integers
```zo
-! Let me introduce the gang members.
-!
-! ## the integer family.
-!
-! signed: s8, s16, s32 (int), s64
-! unsigned: u8, u16, u32 (uint), u64
-- Ya, I'm the `int` chief — a signed 32-bit integer
-- by default for any bare number you write.
-- I scale up to `s64` if you need more room.
42
-- I support large values natively — `600851475143`
-- allocates down without complex object types.
600851475143
```
### floats
```zo
-! And that's the rival clan.
-!
-! ## the float family.
-!
-! f32: 32-bit
-! f64: 64-bit (float)
-- Heyo, I'm `float` — a 64-bit double. Just add a
-- `.` and you get me.
14.0
3.14159
-- I support scientific notation natively. No
-- overhead, just quick compilation values.
1.0e10
2.5e-3
```
### bases
```zo
-! Mask-on integer prefixes change internal notation
-! views.
0b11110000 -- Binary notation evaluates to 240
0o77 -- Octal notation evaluates to 63
0xff -- Hexadecimal notation evaluates to 255
```
### parse modifiers
```zo
-! A `#` prefix sets the display base. The digits stay
-! decimal; only how the value prints changes.
b#30 -- value 30, shown in binary
o#75 -- value 75, shown in octal
x#76 -- value 76, shown in hexadecimal
```
## booleans
```zo
-- Wordup, we're `bool` — only `true` and `false`.
-- No "truthy" or "falsy" mind games here."
true
false
```
## strings
```zo
-- Look out! I'm `str` — a string literal. I live in
-- the binary's read-only data section hood, so I
-- cost nothing at runtime. Skuuuuuu!"
"JOiN THE DEVOLUTiON."
```
## chars
```zo
-- Call me `char` — a single Unicode scalar wrapped
-- in single quotes.
'z'
```
## bytes
```zo
-- Call me `bytes` — a multi-byte buffer wrapped in
-- backticks. Same layout as `str`, but without the
-- UTF-8 safety validation promise. Every raw byte
-- is preserved.
`hello`
`¥orld`
```
---
# variables
Data bindings demand structured tracking. zo enforces variable clarity using three dedicated allocation keywords: `val` for global or local compile-time constants, `imu` for unalterable execution parameters, and `mut` for active stack transformations.
## constants
```zo
-- Hi, I'm `val` — your compile-time constant.
val VERSION: str = "1.0.0";
val MAX_HEALTH: int = 100;
```
## locals
```zo
-- Hey, I'm `imu` — your immutable local. Set me
-- once, read me many.
imu name: str = "johndoe";
-- And me, I'm `mut` — your mutable local. I can be
-- reassigned.
mut health: int = 22;
health = 50; -- Legal modification mutation.
```
## shadowing
```zo
-- Variable shadowing isolates structural scopes.
-- Each statement allocates a fresh slot, shadowing
-- the predecessor safely.
imu x: int = 40;
imu x: int = x + 2; -- x now safely evaluates to 42.
```
---
# interpolation
Drop variables into any string with `{variable_name}`. The compiler resolves each hole at compile time — no runtime parsing, no format functions.
```zo
imu name: str = "johndoe";
imu hp: int = 100;
imu attack: int = 15;
showln("hero: {name}, hp: {hp}");
```
Interpolation works in every string context — assignments, arguments, return values.
```zo
imu power: int = hp + attack;
imu status: str = "power level: {power}";
showln(status);
```
All scalar types resolve automatically: `str`, `int`, `float`, `bool`, `char`.
```zo
imu pi: float = 3.14;
imu active: bool = true;
imu label: str = "pi={pi}, ok={active}";
showln(label);
```
## how it works
Interpolated strings compile into a single allocation that concatenates all segments. Each non-`str` variable converts to its string representation first, then everything merges in one pass.
```zo
showln("hp: {hp}");
-- Desugars to:
-- show("hp: ");
-- showln(hp);
```
```zo
imu msg: str = "hp: {hp}";
-- Compiles to:
-- to_str(hp) → "100"
-- multi_concat("hp: ", "100") → "hp: 100"
```
Direct output through `showln` skips the heap entirely — each segment writes straight to the file descriptor Assigned strings allocate once regardless of how many `{}` holes they contain.
```zo
-! ## the capstone.
-!
-! - `{variable}` inside any `"string"` resolves the variable.
-! - `\{` produces a literal brace, not interpolation.
-! - direct output (`showln`) allocates nothing.
-! - assigned strings allocate once, not per segment.
-! - all scalar types (`str`, `int`, `float`, `bool`, `char`) supported.
```
---
# operators
Operators are how you transform values. zo keeps them small and predictable — the same five arithmetic operators you've used everywhere, plus reassignment and a handful of shorthands.
## arithmetic
```zo
imu power: int = hp + attack; -- + - * / %
mut current_hp: int = 100;
current_hp -= 25; -- += -= *= /=
```
---
# strings
All strings adhere to valid UTF-8 formats, supporting raw character arrays, unicode hex variants, layout escapes, and structural symbols seamlessly.
```zo
-- Custom strings are completely immutable. The
-- internal data bytes never change.
"\e[32mHello!\v\e[34mWorld\e[0m\n"
"\x48\x65\x6c\x6c\x6f" -- hex char
"\u{e9}\u{e8}\u{ea}" -- latin char
"\u{1F600} \u{1F680} \u{1F4A9}" -- decoded emoji
"🙈🙉🙊" -- raw unicode literals
```
## concatenation
The `++` operator acts as your layout welding torch. Merging literals fuses values immediately inside the binary compilation phase, yielding zero performance penalties at execution time.
```zo
imu greeting: str = "hello";
imu name: str = "johndoe";
imu full: str = greeting ++ ", " ++ name ++ "!";
-- Direct character index extraction evaluates
-- efficiently.
showln(greeting[0]); -- Evaluates to 'h'
```
```zo
-! ## the capstone.
-!
-! - `str` is immutable.
-! - `++` concatenates.
-! - `s[i]` for string indexing.
```
---
# tuples
A tuple organizes items into a fixed-length, ordered sequence. Unlike arrays, a single tuple can house distinct
data types within the same memory footprint.
```zo
imu point: (int, str, int) = (100, "john", 3);
-- Extract parameters via positional indices.
showln(point.0);
-- Deconstruct the memory layer instantly via
-- structured binding patterns.
imu (x, y, z): (int, int, int) = point; -- Structured destructuring binding
-- Declare structural type aliases for fast shape
-- replication.
type Point = (int, int); -- Type shaping alias
```
---
# arrays
Arrays host homogeneous components where every single block matches a identical data type. They come in static and dynamic variants.
## static array
The notation format `[N]T` fuses the length constraint straight into the data classification layer. The memory block is determined at compile-time and guarantees safe bounds-checking verification parameters during constant array access tasks.
```zo
imu nums: [3]int = [10, 20, 30];
imu zeros: [5]int = [0...]; -- [0, 0, 0, 0, 0]
imu grid: [2][3]int = [[1, 2, 3], [4, 5, 6]];
-- Unpack items instantly using assignment sequences.
imu [a, b, c]: [int, int, int] = nums;
```
## dynamic array
The designation `[]T` offloads length calculations to execution headers rather than structural types. Coupling
arrays with a `mut` binding permits array extensions.
```zo
mut arr: []int = [];
arr.push(10);
imu last: int = arr.pop(); -- Safely extracts 10
-- The `[value...count]` expression triggers explicit
-- array expansion routines.
imu sevens: []int = [7...4]; -- [7, 7, 7, 7]
```
---
# blocks
...
---
# functions
```zo
-- I'm a function accepting two arguments of type
-- int and returning a value of type int.
fun sum(a: int, b: int) -> int {
a + b -- implicit return
}
-- Then you can call me, like this:
sum(39, 3);
```
---
# closures
```zo
-- closure:block.
imu square: Fn(int) -> int = fn(x: int) -> int {
return x * x;
};
-- closure:line.
imu square: Fn(int) -> int = fn(x: int) -> int => x * x;
-- Then you can call me, like this:
square(7);
```
---
# structures
Custom structures package operations into meaningful domain boundaries. The `struct` keyword models complex custom fields, while the `apply` keyword assigns functional behavior logic to those types.
## struct
A field can declare a default value with `=`. Fields without one are set when you construct the value.
```zo
struct Point {
x: int,
y: int,
}
struct Rect {
x: int = 10,
y: int = 20,
w: int = 100,
h: int = 200,
}
struct Counter {
x: int = 0,
}
```
## methods
Use the apply statement block to bind custom functions to a target structure definition.
```zo
apply Counter {
-- Static instantiation function block.
fun new() -> Self {
Self { x = 0 }
}
-- Mutable state tracking modifier function.
fun incr(mut self) {
self.x += 1;
}
}
```
---
# enums
Enums manage disjoint data states safely, supporting direct discriminants along with tuple-wrapped inline data tracking blocks.
```zo
-- ...
enum Foo {
Bar,
Oof(str), -- Structural data tuple binding
Rab = 42, -- Assigned explicit discriminant value
}
-- Instance consumption example.
imu foo: Foo = Foo::Bar;
imu foo: Foo = Foo::Oof("What's crackin'?");
```
---
# abstracts
Abstracts establish zo's architecture for ad-hoc polymorphism. You define a structural contract once, then
declare custom implementation tracks across varying types via explicit implementation mappings: `apply Abstract for TargetType`.
```zo
abstract Display {
fun display(self) -> str;
}
struct Point {
x: int,
y: int,
}
apply Display for Point {
fun display(self) -> str {
return self.x.to_str() ++ ", " ++ self.y.to_str();
}
}
```
## using abstracts as parameters.
Functions that accept elements under abstract contract bounds choose between three compilation techniques balancing raw execution speed against binary sizing limits.
### form 1 — implicit-mono.
```zo
fun render(item: Display) -> str {
item.display()
}
```
The compiler manages the heavy lifting under the hood: it automatically generates a hidden type variable, extracts parameters straight from the call site, and generates a dedicated, static monomorphized copy of the function block per unique type. Zero runtime cost, zero vtable tracking lookups. Violating limits triggers error diagnostics immediately.
### form 2 — explicit-mono.
```zo
fun render<$T: Display>(item: $T) -> str {
item.display()
}
```
This follows the exact same performance-optimal static compilation track as Form 1. Use this explicit syntax strategy whenever you must reuse the type constraint identifier across signature bounds—such as enforcing matched input types or coordinating return paths.
### form 3 — dynamic dispatch.
```zo
fun render(item: any Display) -> str {
item.display()
}
```
Prepend the type parameter with any to box the instances safely behind a uniform vtable layout pointer. The compiler generates exactly one execution block in the final binary, executing code paths via runtime lookup addresses. This trade-off incurs slight call overhead but permits heterogeneous data grouping inside shared array vectors.
```zo
mut widgets: []any Drawable = [];
widgets.push(Button { label = "ok" });
widgets.push(Slider { value = 42 });
for w := widgets {
showln(w.draw());
}
```
| Engineering Requirement | Optimal Architectural Choice |
| :------------------------------------------------------------------ | :---------------------------------- |
| Maximum dispatch velocity; isolated single types per call site. | `item: Abstract (Form 1)` |
| Shared type bounds enforcement across arguments or return paths. | `<$T: Abstract>(item: $T) (Form 2)` |
| Mixed collection handling; minimal compiled binary space footprint. | `item: any Abstract (Form 3)` |
---
# type alias
```zo
-- ...
type Bar = int;
-- ...
imu z: Bar = 42;
```
```zo
group type Idx = int
and Pair = (int, int)
;
```
---
# generics
Generics are zo's form of **parametric polymorphism** — one body of code that works across many types via type parameters
(`<$T>`). The other form, **ad-hoc polymorphism**, is covered by [abstracts](#017-abstracts).
---
# type inference
- hindley milner.
---
# control flow
## if else
```zo
-- same type inside
if 1 == 2 {
false
} else if 2 == 3 {
false;
} else if 3 == 4 {
false
} else {
true
}
```
## ternary
```zo
-- only as expression
when true ? 1 : 2;
```
## pattern matching
```zo
-- ...
match 5 {
10 => check(false),
_ => check(true), -- wildcard
}
-- ...
match "z" ++ "o" {
"ivs" => showln(false),
"zo" => showln(true),
_ => showln("default"),
}
```
## jumps (terminators)
```zo
for i := 1..10 {
if i == 3 { continue; }
if i == 7 { break; }
show(i);
}
-- 12456
```
---
# loops
## while loop
```zo
-- while:block
mut z: int = 0;
while z < 1_000_000_000 {
z += 1;
}
-- while:line
mut z: int = 0;
while z < 1_000_000_000 => z += 1;
```
## for loop
```zo
-- for:block.
for x := 0..3 {
showln("{x}");
}
-- for:block:mut.
for mut x := 0..3 {
x += 1
}
-- for:line.
for x := 0..3 => showln("{x}");
-- for:line:mut
-- mutable iterator, body reassigns it.
for mut n := 0..3 => n += 1;
```
## infinite loop
```zo
mut x: int = 0;
loop {
if x == 1_000_000 {
showln(x);
break;
}
x += 1;
}
```
---
# concurrency
The zo runtime ignores standard state-machine `async`/`await` transforms entirely, eliminating function coloring bugs across your system. Execution runs inside native runtime-managed green threads tracking execution scope blocks called nurseries. Blocking a task triggers immediate context frame swaps inside the scheduler.
## nursery
A `nursery` container sets strict lexical boundaries for concurrent task Lifecycles. The execution block cannot exit until every spawned green thread unwinds completely.
```zo
fun worker(id: int, tx: Tx) {
showln("worker: {id}");
tx.send(id * 10);
}
fun main() {
imu (tx, rx) := channel();
-- The nursery handles concurrent task tracking
-- smechanics cleanly.
nursery {
spawn worker(1, tx);
spawn worker(2, tx);
} -- exical boundary block: execution holds here
-- until both tasks complete.
imu res1 := rx.recv();
imu res2 := rx.recv();
showln("collected results: {res1}, {res2}");
}
```
- **True Stackful Green Threads**: Every concurrent execution task allocates a lightweight runtime execution stack. Deep nested calls compile natively without restructuring code into complex async state loops.
- **Unified Runtime Scheduler**: The internal task coordinator monitors state mutations directly. Invoking `rx.recv()` on an empty channel yields execution, swapping out active thread contexts immediately.
- **Structural Cancellation**: Nurseries form distinct isolation islands. Triggering task cancellations propagates downstream through children stacks because the underlying runtime owns the stack handles.
## supervise
```zo
```
## select
Coordinate communication states across multiple channel references using the non-blocking select block format:
```zo
select {
rx1 => fn(value: int) => showln("chan1: {value}"),
rx2 => fn(value: int) => showln("chan2: {value}"),
}
```
## thread
```zo
```
## await
```zo
```
---
# tests
---
# zsx
The compiler builds an inline UI interface engine called zsx (zo Syntax Extension). It compiles interface markup nodes into static target rendering definitions at build-time, completely freeing the stack from heavy application bundles or runtime framework assets.
```zo
fun user_account(username: str, score: int) {
imu view: > ::=
User: {username}
Current Score: {score}
;
}
```
## @events
`@click`, `@input`, event handlers.
```zo
```
## components
...
## style
Target style behaviors directly inside your source file using scoped lexical style assignment scopes (`$: {}`). The declarations remain completely isolated to the compilation package module boundary, guaranteeing style isolation.
```zo
-- Isolated module styles stay bound to local markup
-- components.
$: {
card {
background: #161b22;
border-radius: 4px;
padding: 16px;
}
button {
background-color: #ff79c6;
color: #0d1117;
font-weight: bold;
}
}
-- Use the public prefix modifier to pass declaration
-- metrics globally.
pub $: {}
```
---
# directives
`#render` — ...
`#html` — ...
---
# foreign function interfaces
zo calls C-ABI libraries directly. You declare the foreign function once, tell the linker where its symbol lives, and call it like any zo function — no wrapper layer, no runtime cost.
## declaring a foreign function
A `pub ffi` declaration names a function that lives in a C library. It has no body; the symbol resolves at link time.
```zo
-- A declaration ends in `;` — no body.
pub ffi sqrt(x: f64) -> f64;
-- A void function omits the return.
pub ffi close_window();
```
zo drives the call straight from this signature: it places arguments in registers per the platform ABI, narrows or widens scalars, and passes structs by value. Adding a function costs one line.
## linking a library
A `#link` block tells the linker which dylib owns the symbols in the file. One block covers every `pub ffi` in the same pack.
```zo
#link {
macos: "@executable_path/libzo_provider_sqlite.dylib",
linux: "@executable_path/libzo_provider_sqlite.so",
}
pub ffi zo_sqlite_open(path: CStr) -> int;
pub ffi zo_sqlite_close(handle: int);
```
C strings cross the boundary as `CStr` (from `core::c`), never `str` — a zo `str` carries a length header that a C function would misread.
## calling a library
Foreign libraries ship as opt-in providers. Load one and call it:
```zo
load core::c::*;
load provider::sqlite::*;
fun main() {
imu db: int = zo_sqlite_open(CStr::new("scores.db"));
zo_sqlite_exec(db, CStr::new("CREATE TABLE s (score int)"));
zo_sqlite_close(db);
}
```
## the binding generator
Hand-writing a `pub ffi` line per function — and keeping each one in sync with the library — is the tedious part. `zo-binder` writes those declarations for you, from one of two sources.
### from a rust library
Wrap any Rust crate in a small shim that exports plain C functions:
```rust
#[unsafe(no_mangle)]
pub extern "C" fn undo_stack_new() -> i64 { /* ... */ }
```
Then generate the bindings:
```sh
just bind undoredo
```
zo-binder reads the shim's signatures and writes `provider/undoredo/undoredo.zo` — the `#link` block plus one `pub ffi` per function, ready to `load`.
### from a c header
For a C library, feed zo-binder the header's machine-readable API. raylib emits one through its `rlparser` tool:
```sh
zo-binder --json raylib_api.json --lib raylib \
--macos /opt/homebrew/lib/libraylib.dylib \
--linux /usr/lib/x86_64-linux-gnu/libraylib.so
```
zo-binder maps each C type to its zo equivalent, generates the structs, renames `InitWindow` to `init_window`, and skips what it cannot map — callbacks, variadics — reporting each one.
The generated file is committed and reviewed like any other source. Nothing runs during `zo run`: you regenerate bindings deliberately, the way you would run a formatter.
---
# core
## options
```zo
imu some: Option = Option::Some("...");
imu none: Option = Option::None;
```
## results
```zo
imu pass: Result = Result::Pass("value");
imu fail: Result = Result::Fail("error");
```
## errors
### the `?` operator
- short-circuit Result inside a Result-returning function
- desugaring: `expr?` ≡ `match expr { Pass(v) => v, Fail(e) => return Fail(e) }`
### error propagation
- chaining: `read_file(p)?.parse()?.validate()?`
- composing helpers that bubble up domain errors
### errors vs panics
- Result for *expected* failure modes (file not found, parse error)
- panics for *bugs* (invariant broken, indexing past length)
- never use Result to signal logic errors; never panic on user input
## ranges
```zo
for i := 0..5 { -- iteration
showln(i); -- 0 1 2 3 4
}
imu slice: []int = xs[2..5]; -- slicing
```
## collection types
### arrays
```zo
imu scores: []int = [1, 2, 3, 4, 5];
imu empty: []int = [];
scores.sum(); -- 15
empty.sum(); -- 0
scores.contains(3); -- true
empty.contains(5); -- false
scores.find(3); -- 2
empty.find(99); -- -1
scores.min_of() -- 1
scores.max_of() -- 5
empty.min_of(); -- 0
```
### vectors
```zo
mut numbers: Vec = Vec::new();
numbers.len(); -- 0
numbers.is_empty(); -- true
numbers.push(10);
numbers.push(20);
numbers.push(30);
numbers.get(0); -- Option::Some(10)
numbers.get(99); -- Option::None
numbers.set(1, 42); -- set in-bounds.
!numbers.set(7, 0); -- set out-of-bounds returns false.
numbers.pop(); -- Option::Some(30)
numbers.remove(1); -- Option::Some(42)
```
### sets
```zo
mut ids: HashSet = HashSet::new();
ids.is_empty(); -- true
ids.insert(10); -- true (new key)
ids.insert(20);
ids.insert(10); -- false (already present)
ids.contains(10); -- true
ids.contains(99); -- false
ids.remove(20); -- true
ids.len(); -- 1
```
### maps
```zo
mut counts: HashMap = HashMap::new();
counts.is_empty(); -- true
counts.insert("a", 1);
counts.insert("b", 2);
counts.get("a"); -- Option::Some(1)
counts.get("z"); -- Option::None
counts.contains_key("b"); -- true
counts.remove("a"); -- Option::Some(1)
counts.len(); -- 1
```
### file system
```zo
imu path: str = "/path/to/file";
match write_file(path, "hi") {
Result::Pass(_) => {},
Result::Fail(_) => showln("write-err"),
}
match read_file(path) {
Result::Pass(text) => showln(text),
Result::Fail(_) => showln("read-err"),
}
exists(path); -- true
remove_file(path); -- true
imu names: []str = read_dir("/some/dir");
```
### directories
```zo
load core::io;
io::is_dir("/some/dir"); -- true
io::copy("a.txt", "b.txt"); -- Result::Pass(bytes copied)
io::remove_dir("/empty/dir"); -- Result::Pass(0)
io::remove_dir_all("/whole/tree"); -- recursive teardown
```
Each returns `Result::Fail(errno)` on failure.
### terminal
```zo
load core::io;
-- fd 0 stdin, 1 stdout, 2 stderr.
when io::isatty(1) ? showln("interactive") : showln("piped");
```
## environment
```zo
load core::env;
env::current_dir(); -- "/work/dir"
env::set_current_dir("/tmp"); -- true
env::temp_dir(); -- the OS temp directory
env::get("HOME"); -- "/Users/me" ("" on miss)
env::set("KEY", "value"); -- true
env::remove("KEY"); -- true
imu all: []str = env::vars(); -- ["KEY=VALUE", ...]
```
## command-line
```zo
load core::cli;
load core::io;
imu app: Cli = Cli::new("greet", "say hello")
.flag("v", "verbose", "print more")
.option("n", "name", "who to greet");
imu parsed: Parsed = app.parse(io::args());
parsed.has("verbose"); -- true when -v / --verbose given
parsed.value("name"); -- Option::Some("zo")
parsed.positionals(); -- bare arguments, in order
```
- `--name=zo`, `--name zo`, and `-n zo` all parse the same
- unknown dashed tokens fall through to positionals
- `app.help()` renders usage text from the registered specs
---
# module system
## pack
```zo
pack say {
fun hello() {
showln("hello, modular world");
}
}
```
## load
```zo
load core::math::pow_i;
load core::math::(pow_i, abs);
```
---
# error messages
When a program does not compile, zo tells you what went wrong, where, and how to fix it. Pick the shape of that report with `--format`: a colored snippet for you, or structured data for a tool.
```zo
fun main() {
imu s: str = "hello" ++ 42;
}
```
By default the compiler renders a human snippet to stderr — the offending line, a caret under each span, and the conflicting types in color.
```sh
zo build greeting.zo
```
```text
[E0304] Error • Type mismatch
╭─[ greeting.zo:2:25 ]
│
2 │ imu s: str = "hello" ++ 42;
│ ───┬─── ─┬
│ ╰─────────── conflicts with this type `str`
│ ╰── incompatible type `int` here
```
## warnings
Not every diagnostic stops the build. Warnings point at code that compiles but breaks a convention — an unused variable, unreachable code, or a name that does not follow zo's naming rules:
- `struct`, `enum`, `type`, and generic names are PascalCase.
- `val` constants are SCREAMING_SNAKE_CASE.
- everything else — `imu`/`mut` bindings, `fun` names and arguments, struct fields, `abstract` functions — is snake_case.
Each naming warning carries the convention-correct rename as its help, so the fix is always one copy-paste away:
```text
[E0355] Warning • Name is not snake_case
╭─[ counter.zo:2:7 ]
│
2 │ imu MyCount := 1;
│ ───┬───
│ ╰───── expected a snake_case name
│
│ Help • rename it to `my_count`
```
A leading underscore opts a binding out (`_unused`), and digits never need a separator (`r0`, `grid2`, `MAX2` are all fine). The program builds and runs regardless — warnings inform, errors stop.
## machine formats
An agent reads text differently than you do — it never skims and it is never overwhelmed by length. So zo offers two machine formats that carry the *full* diagnostic, not a terse summary. Both stream to stdout, leaving stderr for you.
`--format=json` emits one JSON object per diagnostic, one per line (NDJSON), flushed as each error is found.
```sh
zo build greeting.zo --format=json
```
```json
{"$schema":1,"id":"type-mismatch","code":"E0304","severity":"error","phase":"analyzer","message":"Type mismatch","fixes":[],"notes":["The types of both operands must be compatible"],"snippet":{"before":["fun main() {"],"lines":[" imu s: str = \"hello\" ++ 42;"],"after":["}"]},"span":{"file":"greeting.zo","byte_start":35,"byte_end":37,"line_start":2,"line_end":2,"col_start":25,"col_end":27},"secondary":{"file":"greeting.zo","byte_start":24,"byte_end":31,"line_start":2,"line_end":2,"col_start":14,"col_end":21},"primary_type":"int","secondary_type":"str"}
```
`--format=xml` emits one well-formed `` document. The tag boundaries read as explicit structure — clean to drop straight into a prompt.
```sh
zo build greeting.zo --format=xml
```
```xml
Type mismatch
The types of both operands must be compatible
fun main() {
imu s: str = "hello" ++ 42;
}
int
str
```
The two machine formats are **isomorphic**: the same fields under the same names. A JSON key maps 1:1 onto the XML element or attribute of the same name, so a tool that reads one reads the other.
## the schema
Every diagnostic carries a stable identity and the data needed to act on it without re-parsing your source.
- `id` — a frozen, kebab-case name (`type-mismatch`). Match on this, not the prose.
- `code` — the display alias (`E0304`), derived from `id`.
- `severity` — `error` or `warning`.
- `phase` — where it surfaced: `tokenizer`, `parser`, `analyzer`, `codegen`, `runtime`.
- `message` — the one-line headline.
- `span` — the primary location: `file`, byte offsets, and 1-indexed `line`/`col` (columns count characters, so `é` advances one).
- `secondary` — the conflicting location, when a diagnostic carries two spans.
- `fixes` — machine-applicable edits, always an array. Each fix names a `kind` (`insert` / `replace` / `delete`), the replacement `text`, a `description`, and the exact span to edit. A tool auto-applying picks the first.
- `notes` — attached context, always an array.
- `snippet` — the source lines around the span (`before` / `lines` / `after`). Tune the radius with `--snippet-context N`; `0` turns it off.
`fixes` and `notes` are always present — empty rather than absent — so a consumer never needs a presence check. The same source over the same input renders byte-identical output, so a tool can diff two builds.
```zo
-! ## the capstone.
-!
-! - default `--format=human` paints a colored snippet to stderr.
-! - `--format=json` streams one NDJSON object per diagnostic to stdout.
-! - `--format=xml` emits one well-formed document to stdout.
-! - both machine formats share one frozen, isomorphic schema.
-! - match on the stable `id`, never on the prose `message`.
-! - `fixes` carry exact edits; `--snippet-context N` sets the source radius.
```
---
# epilogue
Your initiation is complete. Now that you have mastered the fundamentals of the `zo` language — it is up to you to get creative and show the world your talent. We have put together a collection of programs for you to browse, sample, replicate, and modify however you like.
Click the following link to access them: [@how-to](/how-to)
TRiLU!
---
## further reading
- [Grammar (EBNF)](https://github.com/invisageable/zo/blob/main/crates/compiler/zo-notes/public/grammar/zo.ebnf)
- [guidelines](https://github.com/invisageable/zo/tree/main/crates/compiler/zo-notes/public/guidelines)
- [Discord](https://discord.gg/JaNc4Nk5xw)
- [GitHub Issues](https://github.com/invisageable/zo/issues)