Learn Zynx

A linear first tour through current Zynx syntax, errors, ownership, modules, and packages.

This tutorial is the beginner path. It avoids SDK, package-cache, ABI, and internals detail until you have run a few small programs.

Zynx is 0.0.0-dev, so syntax can still change. The goal here is to teach the current shape, not promise a final language.

1. Run A File

Create main.zx:

import std.io;

fn main() {
    io.println("Hello, Zynx");
}

No output is produced yet; this block is the source file. The next command runs it.

Run it:

./zynx run main.zx
# Hello, Zynx

import std.io binds the standard I/O module as io. io.println is for human-facing command output and intentionally ignores low-level write errors.

2. Variables And Types

Use let for mutable local bindings and const for immutable ones. Many examples rely on inference, but written types are available:

import std.io;

fn main() {
    const name: str = "Ada";
    let score: i32 = 40;
    score = score + 2;

    io.println("{name}: {score}");
}

Expected output:

Ada: 42

str is borrowed UTF-8 text. Owned text uses std.string.String.

3. Control Flow

Blocks use braces and statements end with semicolons. Ranges use ... for an inclusive end and ..< for an exclusive end.

import std.io;

fn main() {
    let total = 0;

    for value in 1...4 {
        total += value;
    }

    if total == 10 {
        io.println("ok");
    } else {
        io.println("bad");
    }
}

Expected output:

ok

4. Errors

Recoverable failures use error unions. A function that can fail writes throws(ErrorSet) before the return type. Use try to propagate and catch to recover.

import std.io;

error ParseError {
    Empty,
}

fn parse_id(text: str) throws(ParseError) -> i32 {
    if text == "" {
        throw ParseError.Empty;
    }
    return 42;
}

fn main() {
    let id = parse_id("") catch {
        .Empty => 0,
    };

    io.println("id={id}");
}

Expected output:

id=0

Bare fn main() can use try without listing every propagated error; the runtime reports an unhandled propagated error and exits nonzero.

5. Ownership And References

Most owned values move on assignment or call. Copy values, such as integers and structs that opt into Copy, can still be used after copying.

import {
    String
} from std.string;

fn main() {
    let text = try String("owned");
    let moved = text;

    assert moved.length() == 5;
}

Expected output: none. The assertion passes silently.

After let moved = text;, use moved; text is no longer available.

Checked references use &T. Assigning through a reference writes to the referenced value; it does not rebind the reference:

fn set_to_answer(value: &i32) {
    value = 42;
}

fn main() {
    let number = 0;
    set_to_answer(&number);
    assert number == 42;
}

Expected output: none. The assertion passes silently.

When a function expects &T or const &T, a value argument may be borrowed for the duration of that call. Long-lived references still need explicit &.

6. Modules

Imports bind modules or exported names:

import std.io;
import std.os as raw_os;
import {
    String
} from std.string;

Expected output: none. These are import forms, not a complete program.

Top-level declarations are private unless exported:

export fn add(left: i32, right: i32) -> i32 {
    return left + right;
}

Expected output: none. This declaration is used by importing or calling it from another file.

Normal source uses import. Dynamic require(...) is experimental/internal.

7. Packages

Create a project:

./zynx new hello
cd hello
../zynx run

Expected output:

Hello from Zynx

zynx new itself does not print a success message.

zynx new writes zynx.json and src/main.zx. Dependencies are exact git dependencies in zynx.json and are locked in zynx.lock.

Check package state without fetching or changing the lockfile:

../zynx package check

Expected output:

package check: ok

Use Packages when you need dependency and lockfile details.

Read Hello World for a slightly more complete first program, Zynx by Example for more runnable patterns, then use Current Language Surface as the compact reference for current source boundaries.