Zynx by Example
A compact tour of current Zynx source patterns.
These examples are meant to be runnable and practical. The focused reference pages remain the source for full rules.
Save any standalone example as main.zx and run it with:
./zynx run main.zx
import std.os;
fn clamp_min(value: i32, minimum: i32) -> i32 {
if value < minimum {
return minimum;
}
return value;
}
fn main() {
let total = 0;
let i = 0;
while i < 3 {
total = total + clamp_min(i, 1);
i = i + 1;
}
_ = os.write("total={total}\n");
}
Output:
total=3
What this demonstrates:
- functions use
fn name(args) -> Type letbindings are mutable locals- interpolation formats
{total}before writing text
Common mistake: assert(...) is not the normal assertion spelling. Write
assert condition; or, in tests, assert condition, "message";.
Use throws(E) for fallible functions, throw for errors, try to
propagate, and catch to recover.
error FileError {
NotFound,
Denied,
}
fn read_id(fail: bool) throws(FileError) -> i32 {
if fail {
throw FileError.NotFound;
}
return 42;
}
fn main() {
let ok = try read_id(false);
let fallback = read_id(true) catch {
.NotFound => 0,
.Denied => -1,
};
assert ok == 42;
assert fallback == 0;
}
This example has no output when the assertions pass. A failed assertion aborts the program.
What this demonstrates:
throws(FileError)marks a fallible functiontrypropagates a recoverable errorcatchchooses a fallback value with match-like arms
Common mistake: returning FileError.NotFound with return is rejected. Use
throw FileError.NotFound;.
Most owned values move on assignment or call. Types that implement the builtin
Copy marker can still be used after copying.
import { String } from std.string;
struct Point: Copy {
x: i32,
y: i32,
}
fn main() {
let point = Point { x: 3, y: 4 };
let again = point;
let text = try String("owned");
let moved = text;
assert point.x == 3;
assert again.y == 4;
assert moved.length() == 5;
}
What this demonstrates:
Point: Copyopts into copy-after-assignment behaviorStringis owned text and moves by default- clone or constructor APIs can allocate, so they are fallible
Long-lived references are created explicitly with &. Assigning through a
reference writes to the referenced value; it does not rebind the reference.
This is intentionally different from C-style *ref = value syntax.
fn print(value: const &i32) {
assert value > 0;
}
fn main() {
let number = 5;
{
let ref: &i32 = &number;
ref = 8;
}
print(&number);
}
What this demonstrates:
let ref: &i32 = &number;creates a reference that can outlive one call.ref = 8;writes through the reference.print(&number)passes an explicit read-only borrow becauseprintexpectsconst &i32.
Calling an async fn creates a cold std.future.Future<T>. The body starts
when the future is consumed by await or a structured future API.
import std.future;
async fn answer() -> i32 {
await future.yield();
return 42;
}
async fn main() {
let pending: future.Future<i32> = answer();
let value = await pending;
assert value == 42;
}
import binds module namespaces. Aliases make the local binding explicit.
External packages are resolved through zynx.json and zynx.lock; code still
imports modules by identity.
import std.os as output;
import { String } from std.string;
fn main() {
let text = String("imported") catch { _ => String() };
_ = output.write("{text.str()}\n");
}
Output:
imported
std.env exposes borrowed process arguments, std.path provides lexical path
helpers, and std.fs provides narrow text-file read/write helpers for CLI
tools.
import std.env;
import std.fs;
import std.io;
import std.path;
fn main() {
let args = env.args();
if args.length < 2 {
io.eprintln("usage: copy-text <input>");
return;
}
let input = args[1];
let output = try path.join(path.dirname(input), "copy.txt");
let text = try fs.read_file(input);
try fs.write_file(output.str(), text.str());
io.println("wrote {output.str()}");
}
Run it with an input file path:
./zynx run main.zx notes.txt
# wrote copy.txt
The source tree includes small CLI examples that exercise std.json and package
metadata. examples/jsoncheck validates strict JSON input.
examples/jsonset mutates one top-level JSON object key and can write through
the one-shot encoder or streaming encoder.
cat > input.json <<'EOF'
{"name":"old","items":[1]}
EOF
./zynx run examples/jsonset/main.zx -- input.json name '"new"'
Output:
{"name":"new","items":[1]}
Foreign calls and other low-level operations require an explicit unsafe
context. Safe wrappers should keep the unsafe block narrow.
foreign "C" fn abs(value: i32) -> i32;
fn magnitude(value: i32) -> i32 {
unsafe {
return abs(value);
}
}
String and str are UTF-8 text. Use byte views when code needs the encoded
bytes explicitly.
import std.bytes;
import { String } from std.string;
fn main() {
let text = try String("hi");
let view = text.utf8();
assert view[0] == 104 as u8;
assert text.length() == 2;
}