Zena is a statically typed programming language that compiles to WebAssembly GC. It combines a familiar, TypeScript-like syntax with a sound type system, zero- and low-cost abstractions, and modern language features, all designed for ahead-of-time compilation to compact, high-performance WASM binaries.
let x = 42; // Immutable variable, inferred type
var y: String = "hello"; // mutable variable
interface Animal {
sayHi(): String;
}
distinct type CatId = string; // Nominal type alias
class Cat implements Animal {
#greeting = 'Hi'; // Private field
name = 'Bob'; // Mutable public field
let id: CatId; // Immutable field
var(#mood) mood: 'aloof' | 'grumpy'; // Public field, private setter
// Constructor with initializer list
#new(id: CatId) : id = id {}
sayHi() {
return `${this.greeting}, I'm ${this.name}`;
}
}
// A function that uses pattern matching
export let getChildren = (n: Node) => match (n) {
case {left, right}: #[left, right] // array literal
case {children}: children
case _: #[]
}
// Pipelines
let formatTitle = (title: String) => title
|> trim($)
|> titleCase($)
|> truncate($, 80);Warning
Zena is so new that syntax might change a lot! In particular, we're unsure
about #new() for constructors, #[...] for mutable arrays vs [...] for
tuples, and let and var as class field modifiers...
There are lots of languages that can target WASM, but most treat it as a secondary backend. Zena is built WASM-first: every language feature maps directly and efficiently as possible to WASM GC features.
- Familiar syntax. If you know TypeScript, you can read Zena. The type annotations, arrow functions, classes, and generics all look the way you'd expect.
- Modern features. Pattern matching with exhaustiveness checking, pipelines, multi-value returns, enums, distinct types, expression-oriented control flow, and more.
- Sound type system. Types are checked at compile time with no escape hatches. If it compiles, it won't throw a type error at runtime (except for possibly checked downcasts).
- Zero- and low-cost abstractions. Primitives and operators map directly to
WASM. FixedArray is just a WASM Array, and indexing is exactly WASM's
array.get/array.set. Generics are monomorphized (no boxing), multi-value returns go on the WASM stack (no allocation), and unused code is aggressively tree-shaken out of the binary. Classes and interfaces use vtables only when dynamic dispatch is needed. - Immutability-friendly.
letbindings, records, and tuples are immutable. Usevarto opt in to mutability when you need it. - Tiny binaries. Dead code elimination removes unused functions, classes, and even WASM types. Minimal programs compile to as little as 37 bytes.
Zena is not ready for use!
Zena is very early in it' development and may things are changing, including syntax, and defaults for immutability, etc. Many features are partially implemented, and there are likely lots of hidden bugs in the features that are implemented.
Currently, features are being added rapidly, the standard library is being built out, and WASI P2 support is being added, with an immediate goal of porting the compiler to Zena and self-hosting.
interface Printable {
toString(): String;
}
mixin Named {
name: String;
}
class User with Named implements Printable {
age: i32;
#new(name: String, age: i32) {
this.name = name;
this.age = age;
}
toString(): String {
return `${this.name} (${this.age})`;
}
}Classes support inheritance, abstract members, final sealing, private #
fields, accessors, operator overloading, and generic type parameters. Interfaces
use fat pointers with vtables for efficient dynamic dispatch.
Generics are fully monomorphized. Array<i32> stores raw integers with zero
boxing overhead:
let identity = <T>(x: T): T => x;
identity(42); // monomorphized for i32
identity('hello'); // monomorphized for StringType parameters support constraints (T extends Comparable) and defaults
(T = i32). F-bounded polymorphism (T extends Comparable<T>) is coming soon.
Zena lets classes overload ==, [], []=, +, with more comining soon.
Operator overloading should help make Zena ergonomic for scientific computing and working with collections. Since final class members are resolved staticlly, operator overloading doesn't cause any performance impact for array indexing on built-in arrays.
Zena supports immutable records and tuples. Records are collections of named values. Tuples are a fixed list of values.
let point = {x: 1.0, y: 2.0};
let items = [1, 'two', 3];Zena has a growing set of type expressions including primitives, literals, records, tuples, functions, and unions.
type Pet = Cat | Dog;Distinct types create nominal or "branded" types over other types.
distinct type UserId = i32;
distinct type PostId = i32;
let x: UserId = 1 as PostId; // ❌ Error: type mismatchType type system has restrictions to help keep types sound and the WASM output small and fast.
For instance, union members must be distinguisable and able to be stored in one WASM value type. You can't mix primitives and references because there's no WASM type that allows that. You must box primitives instead:
type NullableId = i32 | null; // ❌ Error
type Nullable<T> = T | null; // ❌ Error: T could be a primitive
type NullableId = Box<i32> | null; // âś… OK
type Nullable<T extends anyref> = T | null; // âś… OK
type NullableId = Option<i32>; // âś… Also OKUntagged enums map to i32 or String as distinct types. Tagged enums are planned.
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}if and match are expressions that return values:
let abs = if (x >= 0) x else -x;
let label = match (level) {
case 1: "low"
case 2: "medium"
case _: "high"
};Pattern matching works with the match() expression, which checks for
exhaustiveness:
class Circle { radius: f32; #new(radius: f32) { this.radius = radius; } }
class Rect { w: f32; h: f32; #new(w: f32, h: f32) { this.w = w; this.h = h; } }
let area = (shape: Circle | Rect): f32 => {
match (shape) {
case Circle {radius}: 3.14159 * radius * radius
case Rect {w, h}: w * h
}
};And while/let and if/let statements:
if ((let(value, true) = map.get(key))) {
// key was found in the map, value is valid
}
while ((let(value, true) = iterator.next())) {
// The iterator had another value
}Patterns support literals, records, classes, guards, as bindings, and logical
| / & combinators. Exhaustiveness is checked at compile time.
Functions can return multiple values as unboxed tuples that compile to WASM's multi-value return, with no heap allocation or wrapper objects:
let divmod = (a: i32, b: i32): (i32, i32) => {
return (a / b, a % b);
};
let (quot, rem) = divmod(17, 5); // quot = 3, rem = 2This powers zero-allocation iterators:
interface Iterator<T> {
next(): (T, true) | (never, false);
}Destructuring is a form of pattern matching that always matches. If the match isn't guarenteed, it's a compile error.
You can destructure objects, records, tuples, and unboxed tuples (mult-value returns):
let {x, y} = point; // Object desctructuring
let [_, _, z] = vec; // Tuples
let (value, found) = map.get(key); // Unboxed tuples - no heap allocation
let {x, y, z = 0} = point; // Defaults: point can be 2D or 3D
let {r as red, b as blue} = color; // RenamingThe |> operator turns nested calls into a readable left-to-right flow. The $
placeholder marks where the piped value goes:
let result = data |> parse($) |> transform($) |> validate($);
// Equivalent to: validate(transform(parse(data)))-
let/varbindings (immutable / mutable) - Primitive types:
i32,i64,u32,u64,f32,f64,boolean - Strings (UTF-8, built-in class)
- Arrow functions, closures, first-class functions
- Classes, inheritance, abstract classes,
finalmodifier - Private
#fields, accessors (getters/setters) - Interfaces (nominal typing, fat-pointer vtables)
- Mixins with composition and constraints
- Generics with constraints, defaults, and monomorphization
- Union types with control-flow narrowing (
is, null checks) - Pattern matching (literals, records, classes, guards, exhaustiveness)
- Multi-value returns and unboxed tuples
- Pipeline operator (
|>with$placeholder) - Enums (integer-backed and string-backed, nominal)
- Distinct types (zero-cost newtypes)
- Type aliases and function types
- Records and tuples (structural types)
- For loops, for-in loops, while loops
- Iterators and
Sequenceprotocol - Exceptions (
throw/try/catch) -
nevertype - Mutable arrays and array literals (
#[...]) - Index operator overloading (
[],[]=) - Operator overloading (
==, custom operators) - Tagged template literals
- Modules, imports, and exports
- Boolean literal types (
true/falseas types) - Let-pattern conditions (
if let,while let) - Regexes
-
anytype with auto-boxing - Contextual typing for closures
- Block expressions
- Map literals (
#{...}) - Extension methods
- SIMD
- Async functions
- Intersection types
- Tagged enums (enums with associated data)
- Decorators and macros
- Numeric unit types
- Context parameters
- Pre and post conditions
- Compiler (TypeScript)
- CLI (
zena build) - Dead code elimination (functions, classes, methods, WASM types)
- WASI target support
- VS Code extension (syntax highlighting)
- Website and documentation
- WASI P2 support
- Import .wit files
- Self-hosted compiler (in Zena)
- Online playground
- Package manager
- WIT/Component Model support
-
String,StringBuilder -
Array<T>,FixedArray<T>,ImmutableArray<T> -
Map<K, V>,Box<T> -
Option<T>(Some/None) -
Error,IndexOutOfBoundsError -
ByteBuffer,ByteArray - Ranges (
BoundedRange,FromRange,ToRange,FullRange) -
console.log - File I/O (WASI)
- Math functions
- Extended math: trig, random, etc.
-
Set<T> -
DataViewfor binary data - Built-in WASI P2 interfaces
Zena targets WASM-GC natively, but also supports the broader WASM ecosystem:
# Compile for a JS host environment
zena build main.zena -o main.wasm --target host
# Compile for WASI
zena build main.zena -o main.wasm --target wasi
# Run with wasmtime
wasmtime run -W gc,function-references,exceptions --invoke main main.wasmLinear memory. The zena:memory standard library module provides tools for
working with linear memory when you need direct byte-level access, such as for
binary formats or interop with non-GC WASM modules.
Component Model. We're working toward letting Zena modules import WIT files directly, with no code generation step, to emit WASI components and WIT interfaces with no additional tools required.
Looking ahead. Today, WASI components require lowering GC types to linear memory. We're looking forward to future WASI proposals that support GC types natively, which would let Zena components avoid the lowering overhead entirely.
Zena is designed to reduce the chance of errors, whether the code is written by humans or generated by AI.
Sound type system. There should be no ways for a variable or parameter to lie
about its type. If a variable has type String, it really is a String at runtime. There are no known unsound escape hatches. Soundness is helped by a few additional features:
- Reified generics
Array<i32>andArray<f64>are distinct types, even at runtime, so runtime type checks likex is Array<i32>work. - Checked casts. All
ascasts are either eliminted at compile time or verified at runtime. - Class initializer lists. Constructors use initializer lists that guarantee every immutable and non-nullable field is set before the object becomes visible. It's impossible to leak a partially initialized object.
Some types, like i32, u32, and boolean, or type aliases on the same underlying type, have the same underlying representation and can be cast between each other, but this should not affect the overall soundness of the program's types.
Future correctness projects Zena is going to continue to add more features that aid in ensuring correctness.
- Distinct types and units of measure. Distinct types already let you create
type-safe wrappers at zero cost, so
UserIdandPostIdcan't be accidentally swapped even though both arei32underneath. Planned numeric units of measure will extend this further with smoother syntax and unit analysis, catching mistakes like adding meters to feet at compile time. - Purity. The
@pureannotation marks functions as side-effect-free. Today this is trusted, not verified, but it documents intent and enables future optimizations. In the future we will try to verify purity annotations or automaticaly infer them. - Contracts and verification. We plan to add
requiresandensurescontracts that specify what functions expect and guarantee. Runtime contracts catch violations early. Static verification (via SMT solvers) can prove contracts hold for all inputs. Combined with AI-generated code, this creates a powerful workflow: AI writes the implementation, the verifier proves it matches the spec.
Zena is implemented primarily with the help of generative AI, and would not exist without it. Zena started as a casual experiment: when the latest AI modules showed huge improvements on working with complex codebases, we asked Gemini to create a new programming language from scratch, and it did! The code that we had the experience to review properly looked good, and so we kept going and asking for changes and new features, and Zena is now growing into something much more substantial.
You might call Zena a vibe-coded language, but the process has been less "vive" and more "mentoring". There have been thousands of prompts over hundreds of changes. Not all of the code was closely reviewed, but a lot of it was. Design "discussions" with agents have helped shaped the language and compiler, weighed the tradeoffs Zena is trying to make, and sometimes invovled push-back from both human and agent.
Zena is still an experiment, just a more serious one now. Some of the questions we are trying to answer with Zena include:
- Can coding agents allow one person or a very small team to produce a full, production-quality programming language with all the tooling and ecosystem pieces that are expected of modern languages?
- Can a new language break through the LLM training-set barrier? Many people worry that kickstarting a new language is impossible now, as popular languages that are in LLM training sets have an insurmountable advantage. On the other hand, LLM's universal translator abilities might make it matter less what language they're generating. Zena is attempting to be familiar enough to easily teach an LLM via context how to generate it.
- Can a project move from vibe-coding standards to proper engineering practices and still maintain the massive accelleration that coding agents give?
- Can a programming language help improve generative coding workflows and outcomes?
- Language Reference: Detailed syntax and semantics
- Quick Reference: Comprehensive feature guide
- Design Documents: Architecture and feature design notes
Zena is not yet released. To build from source:
git clone https://github.com/nicolo-ribaudo/zena.git
cd zena
npm install
npm run build
npm test- Node.js v25+
- npm
- wasmtime (for running WASI programs)