Runnable language cookbook
Learn Zolo through examples
Read complete programs alongside their explanations. Start with values and functions, then explore data, errors, effects and integrations.
Start from an outcome
Use a short route when you want a working mental model without reading the entire catalog.
Learn the core language
Bindings, operators, control flow and functions.
Start with the essentialsModel reliable data
Structs, stored-field accessors, enums and Result.
Start with the essentialsBuild services
HTTP routes, JSON, database queries and workers.
Start with the essentialsExplore advanced topics
Comptime, reflection, state machines and effects.
Start with the essentialsThe complete recipe library
Open only the act you need. Search scans titles, descriptions, chapters and syntax across the whole library.
01 Act 1 Fundamentals
Hello & Comments
2 recipes
Primitives & Numbers
5 recipes
- Integers & Floats int and float, arithmetic, and int → float coercion.
- Number Bases Hex, binary, octal literals and the _ digit separator.
- Numeric Types Fixed-width aliases (u8, i32, f32, usize) as annotations and suffixes.
- Booleans true, false, and the logical operators.
- Nil & Optional nil, the absence of a value, and optional types.
Variables and Bindings
7 recipes
- Overview How Zolo names values: a tour of the binding forms.
- Bindings: let, let mut and var Creating immutable bindings with let, mutable ones with let mut, and the var shorthand.
- Constants and Override const for fixed compile-time values; override for constants that can be replaced at load time.
- Type Annotations and Optionals Automatic inference vs explicit annotation; the T? type for values that may be nil.
- Shadowing and Scope Redeclaring a name with let (shadowing) and how lexical scope defines the lifetime of a binding.
- Destructuring Extracting multiple values from arrays, structs, and enums in a single pattern.
- Storage Classes and Discard var<lazy>, var<persistent>, var<atomic>: initialization control and lifecycle; and _ to discard values.
Operators
7 recipes
- Arithmetic Arithmetic operators: +, -, *, /, %, **, unary -, floor division ~/ and compound assignment.
- Comparison and Logic Relational operators (==, !=, <, <=, >, >=) and logical operators (&&, ||, !) with short-circuit evaluation.
- Chained comparisons Write lower <= value < upper as one comparison chain. Each shared operand is evaluated once, and a failed comparison stops the chain. This keeps interval checks readable without repeating a potentially expensive expression.
- Ranges and Spread Range operators (.. exclusive, ..= inclusive), array slicing and the spread ... in literals.
- Pipeline Pipeline operators |> (data-first chaining) and &. (side-effect tap without breaking the flow).
- Nil Safety Operators ?. (safe navigation), ?? (nil coalescing) and ? (nil propagation).
- Fallible Chaining The ?> operator: chain calls that may fail, short-circuiting on error.
Control Flow
6 recipes
- if / else Conditionals as a statement and as an expression, if-let and let-else.
- Conditions with bindings Comma-separated conditions run lazily from left to right. Successful pattern bindings are visible in subsequent clauses and the success body. A guard introducing bindings must exit on every failure path.
- Loops while, while-let, value-producing loops, for over ranges and collections, labelled break/continue.
- Filtering iteration with for case for case pattern in values skips values that do not match, binding only the successful body. A plain refutable for loop retains its mismatch panic. The iterable is evaluated once and continue targets the filtering loop normally.
- Match Pattern matching: literals, or, ranges, guards, structs, enums and nesting.
- defer Schedule cleanup for scope exit, in LIFO order.
02 Act 2 Functions & Logic
Functions
14 recipes
- Named Functions Declaration with fn, explicit or inferred types, and local helper functions.
- Expression function bodies Use = expression for a small function or method. The expression supplies the return value, while return, Result propagation, generics and defaults keep the same function semantics as a block body.
- Lambdas and Closures Anonymous functions with |args| expr and lexical scope captures.
- Predicate short lambdas Accesses rooted in underscore refer to the same implicit parameter. Combine fields, arithmetic and boolean tests in a callback; use an explicit lambda when the scope would be ambiguous.
- Complete unary short lambdas With a proven one-argument callback signature, underscore can stand for the whole parameter, including arithmetic and ordinary nested value calls. Nested callback slots get their own parameter. Blocks, explicit lambdas, collections and pipes remain scope boundaries.
- Named trailing blocks Several callback arguments can follow a call as labelled blocks. Labels resolve to callable parameters, so blocks can appear in another order. Creating a callback does not execute its body; mutable captures keep their existing cells.
- Flexible Parameters Default values with p: T = expr and varargs with args: ...T.
- External argument labels Separate a public call label from the local parameter name: from source: int. The caller uses from:, while the function reads source. Labels are part of callable interfaces and remain available when passing a function as a value.
- Named argument shorthand Calling with name: forwards the caller's name binding. Explicit arguments and shorthand can be mixed and reordered by label. A missing local binding is an error; the formatter preserves the shorthand.
- Required named arguments Parameters after * must be supplied by their external labels. Defaults can still be omitted. This contract follows methods, exported declarations and typed function values; passing the function as a callback does not remove the requirement.
- Multiple Returns and Recursion Returning multiple values via [T] with destructuring, and functions that call themselves.
- Pattern function clauses Consecutive declarations of the same function select a body by parameter patterns and optional guards, in written order. Clauses form one callable and must be exhaustive; guarded clauses alone do not establish coverage. Contracts and public labels must agree.
- Higher-Order Functions Functions as argument or return, composition, map/filter/reduce, and closures with mutable state.
- Generators and Coroutines fn* and yield for lazy sequences; async fn for cooperative tasks.
Pattern Matching
9 recipes
- Basic Patterns: Literals, Wildcard and Binding The three fundamental building blocks of every pattern: match by exact value, ignore with `_`, or capture the value into a name.
- Or Patterns, Ranges and Guards Group alternatives with `|`, classify numeric intervals with `..=`, and refine patterns with extra `if` conditions.
- Binding with @ The `@` operator captures the value that satisfies a sub-pattern, combining verification and binding in a single step.
- Destructuring: Tuples, Arrays, Structs and Enums Extract fields and elements from composite structures directly in the pattern — in `let`, `match` and `for`.
- Nested Patterns Combine patterns inside patterns to extract data from deep structures in a single match arm.
- Patterns in Statements: if let, while let, let else and match-expression Beyond `match`, patterns appear in `if let`, `while let`, `let else` and as expressions that return a value.
- Pattern bindings with is Use is with a value pattern to test and bind in one condition. Captures are visible only along the successful path, including subsequent condition clauses. Existing is Type and is not Type tests keep their meanings.
- Typed string captures A pat literal matches the entire string and converts its captures before exposing bindings. A failed scalar conversion or validated constructor is a non-match. Matching uses the first delimiter without backtracking; adjacent captures without a delimiter are rejected.
- First-class capture patterns Store a pat literal as a reusable value. Its parse method returns Result with an exact typed capture record. Input mismatch and failed conversions return structured errors. Construction retains conversion functions without running their validation predicates.
Error Handling
10 recipes
- Result: success or error as a value Create and inspect Result.Ok / Result.Err; use is_ok, unwrap, unwrap_or and exhaustive match.
- The ? Operator Propagate Result and Option automatically with ? — no nested match boilerplate.
- Local Result boundaries attempt evaluates a block and wraps its normal tail in Ok. A failing Result question mark exits the nearest attempt as Err, so the caller can continue. A Result returned as the tail remains nested. Panics are not caught; optional propagation needs an explicit error conversion.
- Result Combinators Transform and chain results with map, map_err, and_then and or_else without unwrapping.
- Expression Result recovery recover evaluates its Result once and unwraps Ok. Its patterns match the Err payload and must cover that payload type. Continuing branches return the same type as the successful value. Returns, propagation and cleanup still belong to the surrounding scope.
- Optionals T?, nil, ??, ?. and if let — represent absent values without an error.
- panic, try/catch and catch_panic Signal broken invariants with panic; capture them with try/catch/finally and catch_panic.
- defer, defer_ok and defer_err Guarantee resource cleanup on scope exit; filter by exit path with defer_ok/defer_err.
- Visible resource scopes using resource = acquisition { body } installs cleanup after a successful acquisition. The type must implement core::cleanup::Cleanup. Cleanup follows lexical exits and owned transfers disarm it. A method merely named close does not establish the protocol.
- Guards and early return Validate preconditions at the top of a function and simplify the happy path with ? in pipelines.
03 Act 3 Data & Types
Data Structures
21 recipes
- Arrays Ordered collection with indexing, slicing, spread, and functional operations.
- Conditional array entries Use if condition => value and for item in items => value inside an array. Entries are emitted eagerly in source order. False conditions omit entries; a nil value still occupies a slot. Nested arrays remain values unless explicitly spread.
- Maps and Sets Key/value maps with the `#{}` literal and sets for membership without duplicates.
- Explicit map types Write a map type as #{K: V}. Braces with named fields always describe a record type, including fields named int or String. Empty braces are an empty record. Map<K, V> remains supported.
- Map comprehensions Build maps with for and if entries inside #{ }. Computed keys use [expression]: value. Iterables, keys and values evaluate once in source order; duplicate keys use ordinary map replacement behavior. Loop and filter bindings stay within their entry.
- Tuples Positional grouping with destructuring — multiple return values and pairs.
- Standalone anonymous records Without a nominal target, .{ fields } infers an anonymous record with exact field types and ordinary field access. An exact expected struct still selects contextual nominal construction. An invalid supplied target is diagnosed instead of being silently replaced by a structural type.
- Structs Records with named fields, nesting, mutation, and methods via `impl`.
- Contextual construction When an exact struct type is expected, .{ fields } constructs that struct without repeating its name. Field defaults, privacy and completeness still apply. The compiler never guesses a nominal identity from field names.
- Nested immutable updates The with update evaluates its base once and copies the root and changed record prefixes. Unchanged children remain shared. The result preserves its nominal type; conflicting paths and linear resource copies are rejected.
- Computed immutable updates A with update accepts compound operators such as value += 1. It reads the old field once and computes a replacement without mutating the original. Existing visibility, type, conflicting path and linear-copy restrictions still apply.
- Typed record projection user.{ id, name } evaluates user once and creates a fresh anonymous record with only the selected visible physical fields. Exact types survive projection. Duplicate selections, inaccessible or computed fields and forbidden linear copies are rejected.
- Reusable typed field paths field User::address.city produces a FieldPath<User, str> without reading an instance. get reads the leaf; set returns an immutable update and shares untouched children. Paths preserve privacy and exact leaf types; computed members and linear storage are rejected.
- Enums Unit variants, variants with positional data, named-field variants, and generic enums.
- Newtypes Opaque wrappers to distinguish types that share the same physical format in the domain.
- Validated scalar newtypes A newtype with where makes its predicate part of construction. Its new constructor returns Result; inspect failure or propagate it. Derived operations retain validation. An ordinary newtype without where keeps its direct constructor.
- Type Aliases type X = T — a transparent name for an existing type.
- When to Use Each Type Decision guide: array vs set vs map vs struct vs enum vs newtype.
- Memory Layout `@repr`, `@align`, and `@size` for layout control in FFI and GPU.
- Field Embedding with `using` Promote an embedded struct''s fields into the outer struct''s dot-access with `using`.
- Custom Iterators Make any type pluggable into `for x in` by implementing Iterator<T> or IntoIterator<T>.
Strings
9 recipes
- Literals and Escapes Double quotes, escape sequences (\n \t \" \\) and the char type.
- Validated nominal string literals A literal such as Email\"ana@empresa.com\" is admitted only when the real validated string newtype's predicate accepts its static contents at compile time. Dynamic strings still use the fallible constructor. This syntax also preserves existing callable tagged templates.
- Concatenation and Interpolation The + operator for joining strings and the {expr} syntax for embedding values.
- Format Specifiers Float precision (:.Nf), zero-padding (:0Nd) and hexadecimal (:#x) in interpolation.
- Case and Trim .upper, .lower, .trim, .trim_start and .trim_end for normalizing text.
- Search and Test .contains, .starts_with and .ends_with to check substrings without regex.
- Split and Replace .split(sep) splits into an array; .replace(old, new) substitutes all occurrences.
- Length and Character Iteration .len() returns bytes; .chars() returns a string array for iterating character by character.
- String Module String.upper, String.sub, String.reverse — the functional style equivalent to the methods.
Iterators and Pipes
6 recipes
- Pipe Operator `|>` chains calls data-first, making transformations readable from left to right.
- Tap Operator `&.` runs a side effect and returns the original value — the pipeline "spy".
- Iter Sources and Transformations `Iter::range`, `Iter::from`, `map` and `filter` build and transform sequences lazily.
- Reduction and Slicing `fold` collapses a sequence into a value; `take` and `skip` limit and paginate iterators.
- Combining Iterators `zip` pairs two iterators; `chain` concatenates them; `collect` and `each` are the terminal consumers.
- Generators and Infinite Sequences `fn*` + `yield` create lazy on-demand sequences; infinite ranges (`0..`) compose with `take` in pipelines.
04 Act 4 Abstraction
Object-Oriented Programming
7 recipes
- impl Blocks and Instance Methods How to group methods on a type with impl, use self, and mutate fields directly.
- Associated Functions and Multiple impl Blocks Constructors and utilities without self, called via Type::name(), and how to split methods across separate impl blocks.
- Traits Define interfaces with trait, implement them on concrete types with impl Trait for Type, use default methods, and combine multiple traits.
- Generics Parametrize functions, structs, and enums with <T> to write code that works with any type.
- Trait Bounds Constrain a generic with inline bounds or a where clause so the body can call trait methods on it.
- Implementing Std Traits Implement core::cmp::Ord on your type so it works with comparison operators and generic Ord bounds.
- Operator Overloading, to_string, and Chaining Bind operators to methods via name convention or @op, control text representation with to_string, and build fluent APIs with method chaining.
05 Act 5 Metaprogramming
Decorators
7 recipes
- Result Caching Automatic memoization with @memoize and expiring cache with @cached(ttl).
- Performance and Diagnostics Automatic instrumentation with @benchmark, @log, and fault tolerance with @retry.
- Structs: Derive and Validation Automatic generation of Eq, Clone, and Debug with @derive, and field validation with @validate.
- Uniform deriving Add a deriving suffix to structs and enums to request existing derive producers. The suffix uses the same validation and generated implementations as derive decorators. Unsupported traits or incompatible field types are diagnosed rather than synthesizing an unchecked implementation.
- Tests and Benchmarks with the Runner Functions automatically discovered by the runner: @test for unit tests and @bench for performance measurement.
- HTTP Routes and API Quality Declarative routes with @get/@post, deprecated API with @deprecated, mandatory return value with @must_use, and lint control with @diagnostic.
- Composition and Custom Decorators Stacking decorators, @const compile-time parameters, and user-defined mixins with super().
Macros
5 recipes
- Definition and Invocation How to declare a macro with `macro name(p) { ... }` and call it with `name!(arg)`.
- stringify! and assert! Inspect source code at expansion time and create rich assertions with `stringify!`.
- Hygiene, Recursion, and Block Arguments Internal variables do not leak to the caller; macros can call themselves; `{}` blocks as arguments.
- Logging DSL Small macros compose a mini logging language with standardized levels.
- macro_rules! Rust-style macros with multiple arms, `$(...),*` repetition, and capture kinds.
Compile-Time Evaluation
5 recipes
- comptime block comptime { ... } evaluates a block inline in the compiler and replaces the result with a literal.
- comptime functions Declare reusable helpers with @comptime fn and invoke with comptime f(x) — the call-site becomes a literal.
- Recursion, loops, and tables Recursive comptime functions, for loops inside comptime blocks, and compile-time generation of literal arrays.
- stdlib and file embedding String methods, math.* and comptime fs.read() work in a comptime context — normalize text and embed files as literals.
- const_assert Fail the build if a compile-time invariant is not satisfied — zero cost in production.
Reflection & Metaprogramming
14 recipes
- Compile-Time Field Names Read a type's field names and type kinds at compile time using typeinfo(T).
- Runtime Reflection Opt a struct into runtime type metadata with @reflect, then query it via typeinfo(value).
- Decorators as Inert Data Field decorators are surfaced through typeinfo so comptime routines can read them as data.
- Compile-Time Serialization Template Walk typeinfo fields at compile time to generate a JSON-shape template as a constant string.
- ORM: Generate CREATE TABLE Derive a SQL CREATE TABLE statement from a struct at compile time using typeinfo and field decorators.
- Derive Validation Rules Use typeinfo on a schema to generate a human-readable list of field constraints at compile time.
- UI Form Generation Derive a form spec from a struct: decorators supply labels, type.kind selects the input widget.
- OpenAPI / JSON Schema Generation Generate an OpenAPI properties fragment from a struct at compile time by mapping type.kind to JSON Schema types.
- Generic Derive Function Write one @comptime fn that accepts typeinfo and works for any struct or enum — the substrate for derive macros.
- Runtime Debug Introspection With @reflect, a generic debug dumper can report any value's type shape at runtime — no per-type code needed.
- Built-In Derives Use @derive(Debug, Eq, Clone) to auto-generate standard trait implementations for a struct.
- Custom Derive Register your own derive with @derive_for: a comptime fn receives typeinfo and returns Zolo source to splice.
- Derive: SQL Column List A derive that uses closures and list.join to build a SQL column list from typeinfo fields.
- Derive: Full DB Table DDL A derive that builds a complete CREATE TABLE statement, mapping field types and reference relations to SQL.
Schemas
5 recipes
- Default Values Declare fields with `= value` and get a ready-made instance with `Schema.default()`.
- Field Introspection `Schema.fields()` returns the field names in declaration order, enabling generic rendering and migrations.
- Parse and Serialize `Schema.parse(map)` validates input returning a `Result`; `instance.to_map()` converts it back to a map.
- `where` Constraints Add domain invariants to fields with `where |v| <bool>` — checked automatically during `parse`.
- Cross-field schema invariants Attach where predicates after a schema declaration to validate the assembled value. Field conversion and defaults happen first. Invariants return bool and run in order; failure becomes a structured DecodeError at the object path. Field constraints keep their own field scope.
06 Act 6 Project Structure
Modules
23 recipes
- mod and use Register a module with `mod` and bring names into scope with `use`.
- Visibility and pub `pub` exports an item; without `pub` it is private to the module.
- Import styles Qualified, list, or multiple modules: when to use each style.
- Native plugins and stdlib `use plugin` for native plugins; qualified `std::` paths need no `use`.
- Inline modules and granular visibility Modules declared inside a file, `use ... as`, and `pub(mod)` / `pub(crate)` modifiers.
- Multi-file project How `mod` and `use` work in a program with multiple files.
- C# with extern Typed functions, closures and .NET objects called like Zolo code.
- Python with extern Typed Python functions, native packages and async calls through CPython.
- JavaScript and TypeScript with Node Typed functions, Node modules and asynchronous callbacks with extern.
- Libraries from other languages Declare versions once, install with Zolo and keep native imports.
- Java with extern Java 8 and later, live objects and Maven libraries through the JVM provider.
- Kotlin with extern Kotlin functions, suspend APIs, live objects and Maven libraries.
- C and C++ with extern Native functions, C++ objects and vcpkg packages with the native provider.
- Rust with extern Typed Rust functions, callbacks and locked Cargo dependencies.
- Go with extern Go functions, module dependencies and callbacks from Zolo.
- C# · .NET Embed typed C# functions, CLR objects, callbacks and asynchronous calls. Prepare with the .NET tooling and retain the matching runtime when deploying.
- Python · CPython Run Python in process with native imports and typed conversion. Windows with CPython 3.12 is verified; a compatible interpreter and native package dependencies are required.
- JavaScript / TypeScript · Node Use JavaScript and TypeScript through embedded Node, including native modules and Promises. The shim, runtime and addon ABI must match. Zolo int uses bigint in inline signatures.
- Java / Kotlin · JVM Share one JVM for Java and Kotlin, with native imports, live objects and Maven dependencies. Source and editor preparation need the matching tools; prepared applications need a compatible JRE. JVM 8, 11 and 23 are verified on Windows x64.
- C / C++ · Clang · C ABI Prepare C17 and C++17 libraries with Clang. C++ exposes supported concrete classes; native ownership and unsupported pointers or templates require explicit adapters. Prepared code runs without the source compiler.
- Rust · rustc · Cargo Compile typed Rust functions and copied records into a target-specific library. Cargo dependencies join the locked workflow. Deployed artifacts do not need rustc or source files; retained receiver objects and open generics need adapters.
- Go · Go · cgo Prepare Go exports with cgo and a compatible C compiler. Typed functions, copied public structs and modules use the common bridge. Prepared artifacts need neither Go nor the module cache; retained receivers and variadics require adapters.
- Foreign dependencies and locks Declare dependencies once at file scope. Provider names must be explicitly imported; aliases select the same environment as their original provider. Native imports still choose the symbols used inside each fragment.
Documentation
4 recipes
- Comments Line comments (//), block (/* */), nesting, and TODO/FIXME/NOTE conventions.
- Doc Comments Doc comments /// and /** */ on functions, structs, enums, and traits — shown in editor hovers.
- Doc Tags Tags @param, @returns, @throws, @example, @see, @since, and @deprecated in doc comments.
- Self-Documenting Code Descriptive names, explicit types, aliases, named constants, and structs/enums as living documentation.
Module Directives
2 recipes
Lifecycle Hooks
3 recipes
- Shutdown Hook Register cleanup code with `on shutdown` — runs on normal exit or via `process.exit`.
- Multiple Hooks Composing `on shutdown`, `on panic`, and `on signal` in the same program; firing order and LIFO.
- Panic and Signals Intercept fatal errors with `on panic` and OS signals with `on signal`.
07 Act 7 Concurrency & Reactivity
Temporal Expressions
8 recipes
- Duration Literals Unit suffixes on numbers: ns, us, ms, s, min, h, d, w — all become i64 in milliseconds.
- Duration Arithmetic Addition, subtraction, multiplication and division between durations; direct comparisons.
- Unit Casting Extract clock components with `as h`, `as min`, `as s`, `as ms` to format durations.
- Declared unit suffixes Attach @unit to a scalar newtype to give literals a typed suffix. The suffix preserves the declared nominal owner and scalar precision. A suffix alone does not define a conversion scale: declare dimensions and scales when automatic conversion is needed.
- Dimensions and unit scales Declare base units, derived dimensions and exact scales with @unit. Multiplication and division derive dimensions; as converts only compatible dimensions and checks representability. Duration suffixes retain ordinary millisecond behavior outside a dimensional context.
- Sleep Pause execution with `sleep <dur>` or `sleep(<dur>)` — cooperative, yields control to the scheduler.
- Scheduling: every, after, and timeout Periodic loop with `every`, one-shot firing with `after`, and deadline cancellation with `timeout`.
- Practical Patterns Heartbeat, TTL, exponential backoff, alert bands, and warm-up combined with a periodic loop.
Concurrency
9 recipes
- Coroutines and Generators The core concurrency primitive in Zolo: coroutine.create/resume/yield and generator functions fn*.
- Lazy generator delegation yield ... child delegates one yielded value at a time and retains the child cursor between resumes. It does not materialize an array. Closing the outer generator closes the active child and runs its cleanup; ordinary yield is unchanged.
- Async / Await and Fetch async fn, await, Promise.all/race/any and the fetch API for concurrent HTTP requests.
- Await deadlines await task within duration returns a typed timeout Result. On expiry the scheduler requests cancellation and drains the task's cooperative cleanup. A task returning Result produces a nested Result; choose explicitly which layer to propagate. Native work without cancellation checkpoints cannot be forcibly interrupted.
- Spawn, Timing, and Cancellation spawn launches cooperative tasks; every/after schedule work over time; cancellation uses shared flags.
- Pipelines and State Machines Generators chained as pull-based stages, and state machines expressed as sequences of yield.
- Channels CSP-style message passing: channel(N), send/recv, for-in, bounded channels, and backpressure.
- Structured Concurrency, Select, and Worker Pool scope waits for all children; select waits on multiple channels; worker pool implements fan-out + fan-in.
- Named parallel results Named tasks in parallel produce a typed result record after the structured join. Every task captures the outer scope; task labels are not variables for siblings. Failure cancels and drains siblings cooperatively. Transitively linear result payloads are rejected.
Signals
5 recipes
- Basic Signal Create a signal with `signal()`, read with `.get()` and write with `.set()`.
- Effects The `effect` runs immediately on creation and re-runs every time a tracked signal changes.
- Computed Values The `computed` creates a memoized derived signal: it recalculates only when a dependency changes.
- Untracked Reads `signal_untrack`, `peek` and `modify`: read a signal without creating a dependency and update functionally.
- Batch, Dispose and Full Demo Coalesce multiple writes with `signal_batch`, cancel effects with `.dispose()` and see the three primitives composed.
State Machines
6 recipes
- Declaration and Transitions How to declare states, set the initial state, and fire events with `.send`.
- Typed events and state payloads A typed machine owns its State and Event enums. Transition patterns bind event and state payloads only within that transition. Initial payloads run once per instance. send returns false if no transition applies; can_send checks patterns without executing guards or actions.
- Transition Actions Code blocks that execute automatically when a transition occurs.
- Querying and Observers `.can_send` checks if an event is applicable; `.on_transition` registers global observers.
- Introspection `.transitions()` exposes the full transition table for tooling, diagrams, and tests.
- State-indexed machines new_static creates an owned machine with a state witness. send consumes it and returns the destination state type; a guarded failure returns the original machine and event. Functions can require Order<.Paid>. erase_state returns a dynamic machine when static checking is no longer needed.
Algebraic Effects
8 recipes
- Fundamentals Declare an effect, fire operations with perform, and install a handler with handle … with.
- Multiple Effects with E1 + E2 combines effects in the signature; nested handlers and transitive effects complete the picture.
- resume and abort resume(v) returns control to perform with a value; abort(v) discards the continuation and exits the handle.
- Handlers as Values handler { … } creates a first-class value; compose, override, record, and panic_unhandled layer on top of it.
- Prefix lexical handlers with handler_value { body } evaluates and installs the handler before the body. It uses the same semantics as handle body with handler_value. Nested scopes restore the enclosing handler, and returns and cleanup retain their lexical boundaries. The value must be a proven Handler.
- Typed handler overrides A handler with operation selectors replaces only those operations, keeping the remaining effect coverage and answer type. Selector signatures and bodies are checked against the actual effect. Composition preserves the handler frame and lexical abort semantics.
- Functional Patterns Reader, State, and Writer implemented as effects; a real-world case with Db + Fs + Log + Metric and in-memory mocks in @test.
- Advanced Types multi fn, generic effects State<T>, row polymorphism, effect aliases, and std::effect combinators.
08 Act 8 Standard Library
Standard Library
26 recipes
- Math (std::math) Math functions, constants, exact decimal arithmetic and approximate float comparison.
- Strings (std::string) Trimming, splitting, replacing, searching, case conversion, character iteration and formatting.
- Arrays (std::array) Mutation, transformation, filtering, aggregation, search and flattening of arrays.
- Vectors (std::vec) 2D/3D/4D vectors: constructors, arithmetic, dot and cross product, length, normalisation and rgba swizzle.
- Maps (std::map) Creation, insertion, reading, deletion, iteration and the difference between #{} literals and Map::new().
- Sets (std::set) Uniqueness, membership, removal, set operations (union and intersection) and conversion with arrays.
- Iterators (std::Iter) Lazy sequences: range, map, filter, fold, take, skip, zip, collect and each.
- Result (std::Result) Error handling without exceptions: Ok, Err, unwrap, map, and_then and the ? operator.
- JSON (std::json) Serialising and deserialising JSON: encode, decode, error handling, json"..." literal, path and schema interop.
- YAML (std::yaml) Parsing and serialising YAML: hierarchical configs, lists and round-trip.
- TOML (std::toml) Parsing and serialising TOML: sections, explicit types and round-trip.
- CSV (std::csv) Parsing and serialising CSV: rows, headers, quote escaping and round-trip.
- Regular Expressions (std::regex) Validate, extract, replace and split strings with regular expressions.
- Date and Time (std::datetime) Get the current instant, format and parse dates, and perform calendar arithmetic with std::datetime.
- Paths (std::path) Manipulate paths portably: join components, extract parts, normalize, and resolve relative paths.
- File System (std::fs) Read, write, delete files, list directories, and work with temporary files using std::fs.
- Environment Variables (std::env) Read, set, and remove environment variables; get system information with std::env.
- Process (std::process) Access arguments, working directory, run external commands, and inspect the current process with std::process.
- I/O Reactor (std::io_runtime) I/O-reactor-based timers: one-shot sleep, periodic timers with cancellation.
- HTTP Client (std::http) Make GET, POST and other requests via std::http.client — custom headers, JSON body and error handling.
- URLs (std::url) Parsing, percent-encoding and query string building with std::url.
- Cryptography (std::crypto) Unique ID generation and cryptographically secure randomness with std::crypto.
- Hashes (std::hash) SHA-256, SHA-512, MD5, CRC32 and HMAC for content integrity and message authentication with std::hash.
- Base64, Hex and Base32 (std::base64) Standard, URL-safe, Hex and Base32 encoding and decoding for representing binary data as text.
- Log (std::log) Structured logging by severity level, verbosity control and custom formatting with std::log.
- Statistics (std::stats) Central tendency, dispersion, percentiles, correlation and linear regression with std::stats.
09 Act 9 Platform & Apps
HTTP Server
6 recipes
- HTTP Client — GET Making GET requests with optional headers and reading the response object.
- HTTP Client — POST JSON and fetch Sending data with http.post and using http.fetch for arbitrary methods and timeout.
- Server — Router and Decorators Creating an HTTP server with http.router() via pipes or with @get/@post decorators.
- Route Parameters and Middleware Reading :id and query string via req.params/req.query; adding middlewares with http.middleware.
- Responses and Workers http.response, http.html and http.redirect for status control; http.workers for parallelism.
- Typed Action Forms Submit scalar fields to an @action with native HTML and server-side decoding from its signature.
Database
7 recipes
- Connection and Ping Open a connection with Database.open, verify with ping, and close with defer.
- DDL and Insertion Create tables with db.execute and insert rows, counting the affected ones.
- Row Querying db.query returns a list of maps {column: value} that can be iterated with for.
- Parameterized Queries Use ? + bindings array to safely pass external values.
- Transactions db.transaction guarantees atomicity: automatic commit on return, automatic rollback on error.
- sql"…" — Safe Template and Scalar The sql"..." literal auto-parameterizes interpolations and exposes :query, :execute, and :scalar methods.
- SQL Injection Protection Interpolations in sql"..." are always bound parameters — SQL metacharacters in the value are literals, never executed.
CLI and Tooling
9 recipes
- Process Arguments Read the command line with `process.argv()` and isolate user arguments.
- Environment Variables and Platform Read, set, and remove environment variables; detect OS, architecture, and home directory.
- Process Lifecycle Exit with `process.exit`, register `on shutdown` hooks, catch panics, and handle OS signals.
- Doctor Command Diagnostic pattern: aggregate environment checks and exit with a non-zero code when something fails.
- @cli Builder Declarative parsing with `@cli` + `@arg`: flags, positionals, automatic `--help`, and subcommands.
- Command Literals and Safety Build lazy commands with sh"…", interpolate argv safely, and handle typed process errors.
- Process Effects and Pipelines Mock process execution, pipe real processes, and transform structured process data.
- Streaming and Executable Scripts Consume process output incrementally and run .zolo files with a Unix shebang.
- Parallel Commands and Terminal UI Run independent commands concurrently and add accessible ANSI styling to interactive CLIs.
Hot Reload
6 recipes
- Basic Reload The "hello world" of zolo dev: edit a module, save it, and see the new version in action without restarting the VM.
- State Preservation pub let with a stable runtime type survives the swap — the accumulated value continues; only the new literal initializer is discarded.
- Reload Lifecycle Hooks Three extension points in the swap pipeline: __on_reload for notification, #[hot_dispose] to capture state before, and #[hot_accept] to restore it after.
- Asset Reload HMR for data files: edit config.json, shaders, or CSVs and the __on_asset_reload hook reacts without restarting the VM.
- Undo/Redo and Advanced Cases Time-travel through swap history via :undo / :redo / :history, plus transparent reload in tight loops without yield and closure references captured outside the live-binding.
- Render Loop and HTTP Server Hot reload in a wgpu render loop with 1000 cubes and in a running HTTP server — without losing the listener or GPU state.
10 Act 10 Build, Test & Ship
Testing
5 recipes
- Matchers with expect() The expect() library and its matchers: equality, length, membership, prefix, and negation.
- Suites with @suite Group tests in modules annotated with @suite, including nested suites.
- Test Attributes Control execution with skip, should_throw, only, and tag in the @test and @suite decorators.
- Human-readable tests A test declaration uses a quoted human title and the existing test runner. Discovery, filtering, reports and editor navigation keep that title. Existing test attributes and suites remain available. Running the file normally does not execute its tests: use zolo test.
- Lifecycle Hooks Run code before and after each test or the entire suite with @before_each, @after_each, @before_all, and @after_all.
Compilation
4 recipes
- run vs build Interpreted VM (`zolo run`) and native backend (`zolo build`): when to use each one and what changes between them.
- --emit Flags Stop the compilation pipeline early: `--emit ir` prints the ZoloIR; `--emit obj` writes an object file without linking.
- Release Build The `--release` flag enables Cranelift optimisations to produce a faster binary, at the cost of a slower build.
- compile and bundle `zolo compile` shows the Lua generated for the VM; `zolo bundle` groups the entry point and its modules into a single portable Lua file.
WebAssembly
4 recipes
- Emit Wasm Compile a Zolo program to `.wasm` with `zolo build --emit wasm`; understand VM-embedded mode and the default host-shim ABI.
- Host Profiles Select the runtime profile with `--host wasi|browser` and enable the experimental AOT backend with `--aot`.
- Export Functions Use `@export` to expose Zolo functions to JavaScript/TypeScript via AOT wasm with `--host browser`.
- Arrays in Wasm Use arrays in AOT functions and export functions that accept or return `[int]`, `[bool]`, and `[str]` to JavaScript.
Distribution
3 recipes
- .zar and .zex Files Package code as a reusable library (`.zar`) or portable executable (`.zex`) with `zolo build --emit`.
- Standalone Binary Embed the runtime into the `.zex` with `--standalone` to distribute without requiring `zolo` to be installed.
- GUI and Windowed Distribute desktop apps without a console window using `--windowed` / `--gui`; run ready-made files with `zolo run`.
OS Integration
1 recipes