Zith is a systems language with safety, expressiveness, and a small, composable core.
No GC. No borrow checker. No hidden allocator.
Compiled, statically typed, and memory-safe by default — with low-level control when you need it and high-level ergonomics when you don't.
Structs, enums, components, unions, generics — the basics are here and work as expected. See Type System.
struct Point { [x, y, z]: f32 }
fn dot(a: view Point, b: view Point): f32 {
a.x * b.x + a.y * b.y + a.z * b.z
}
Unions are runtime tagged. dyn gives dynamic type erasure with compile-time safety. is narrows both, letting you operate on the concrete type inside each branch:
union Value { i32, f64, string }
fn handle(v: dyn Value) {
if (v is i32) {
v += 32; // v is i32 here
} else (v is f64) {
@println("{v:.2}"); // v is f64 here
}
}
No manual type switches. No unsafe downcasts. The compiler tracks the type through every branch.
The -> operator pipes values left to right. .. refers to the previous value. See Control Flow.
getData() -> process(..) -> validate(..) -> save(..);
Inline blocks and comma sub-chains keep side-effects local without breaking the flow.
No exceptions. No try/catch. ?T for optionals, T! for results. Propagate with ? or !. Recover with or. See Error Handling.
fn loadConfig(path: string): Config! {
let file = File.open(path)!
let data = file.read()!
parse(data)!
}
let name = ?user.name or "guest";
let data = !primary() or backup() or defaultData;
with bundles multiple fallible calls, catch handles any failure:
with (connectDb(), user: getUser(id)) {
process(user);
} catch (err) {
log(err);
}
This is where Zith pulls ahead of most systems languages. Chain flow and error propagation together:
readFile("config.toml")
-> decode(..)!
-> validate(..)?
-> apply(..);
Compare to Go, where the same logic is buried in boilerplate:
// Go
data, err := readFile("config.toml")
if err != nil { return err }
decoded, err := decode(data)
if err != nil { return err }
if validated, err := validate(decoded); err == nil {
err = apply(validated)
}
return err
The Zith version does the same thing — read, decode, validate, apply — without a single if err != nil. The ! propagates errors out; ? continues on null; the chain keeps the data moving forward.
Recovery stays on the same line:
readFile("config.toml") ->
{ decode(..)! -> validate(..)? -> apply(..) }
or fallbackConfig(); // any failure -> use default
Or with fail blocks for contextual error handling:
loadConfig {
readFile("config.toml")
-> decode(..)!
-> validate(..)!
} fail loadConfig(err) {
if (err is NotFound) { continue(defaultConfig()); }
throw Error{ context: "load failed", cause: err };
}
A real-world example — processing a request pipeline:
parseRequest(raw)!
-> authenticate(..)!
-> validate(..)?
-> queryDb(..)!
-> marshal(..)
-> send(..);
In Go this is ~30 lines of if err != nil. In Zith it is one.
No borrow checker. No garbage collector. No reference counting. The compiler statically tracks every value's ownership, lifetime, and aliasing — and catches misuse before the program runs.
var a = Point { x: 1.0, y: 2.0 };
let b = a; // b owns the data; a is now dead
// a.x -- compile error: a is dead
a = Point{3, 4}; // fine: reassignment creates a new node
For borrowing, two keywords cover most code:
| Keyword | Meaning |
|---|---|
lend |
Exclusive mutable borrow |
view |
Read-only borrow (many can coexist) |
fn scale(p: lend Point, factor: f32) { p.x *= factor; }
fn length(p: view Point): f32 {
sqrt(p.x * p.x + p.y * p.y)
}
For advanced ownership: unique (single owner), share (multiple mutable names), belong (part-of relationship for back-pointers).
Safe by default. Zero runtime overhead. No ceremony.
C interop is first-class — include headers directly, call functions immediately. See Raw & Unsafe.
import "openssl/ssl.h";
SSL_CTX_new(method);
When you need to bypass safety, raw fn and unsafe blocks are explicit and scoped:
raw fn readRegister(addr: opaque): u32 {
unsafe {
let ptr = addr as *u32;
*ptr
}
}
The Trust capability lets you expose safe wrappers over unsafe internals:
trait MMIONode extends Trust {
raw fn read(self, offset: u64): u32 {}
raw fn write(self, offset: u64, val: u32) {}
}
fn safe_copy(src: view MMIONode, dst: lend MMIONode) {
let v = src.read(0x00);
dst.write(0x00, v);
}
Group macros, operators, and DSLs under use blocks. No global pollution. See Contexts.
use SQL QueryBlock {
SELECT * FROM users WHERE id = :id
}
// Outside, SQL syntax is unavailable
Built-in state machines via markers, docks, and jumps for embedded, OS, and game loops. See Control Flow.
flow fn scheduler(): noreturn {
marker ContextSwitch(next: TaskId) {
saveRegisters();
loadTask(next);
}
dock { jump ContextSwitch(nextTask); }
}
const fn and const blocks for compile-time computation. @ intrinsics for reflection and type manipulation. See Intrinsics.
const fn processData(data: []char): JsonValue { ... }
const parsed = processData(AssetData);
// Reflection at compile time
let isStruct = (T is @struct);
for (member in @members(MyStruct)) {
@println("{}: {}", member.name, member.type);
}
Perfect for games, simulations, and modular applications — isolated memory regions with automatic cleanup. See ECS & Scenes.
scene MainMenu {
entity MenuButton { /* ... */ }
entity TextDisplay { /* ... */ }
}
scene GameLevel {
entity Player { /* ... */ }
entity Enemy { /* ... */ }
}
transition(MainMenu, GameLevel); // old scene dies, new scene allocates
| Feature | Rust | Zith |
|---|---|---|
| Memory Safety | Borrow checker | NRA (ownership types) |
| Learning Curve | Very steep | Gentle |
| Lifetimes | Explicit annotations | Implicit through types |
| Error Handling | Result/Option + macros | Failable types + chain flow |
| DSLs | Macros only | Contexts + words + macros |
| State Machines | External crates | Built-in flow fn / markers |
| Compile Time | Slow | Fast |
| Ecosystem | Large | Growing |
Zith proves you don't have to choose between safety and simplicity.
Zith — A systems language with a small core and a large toolbox.