Envzn /en·VIZH·un/ Documentation GitHub — soon Download — soon

A language you can read

Envzn (pronounced “envision”) is an ahead-of-time (AOT) compiled, statically typed, object-oriented language for building robust application software. Source files use the .ev extension; the compiler is evc, and epm is the package manager that manages builds and dependencies.

Parts of this language were first sketched out in 2001, some parts were designed in 2011, and the rest has been designed and implemented in 2026. The short story is this: after doing deep systems work with C++ and meeting with the first Java implementation team in 1995, I really wanted a memory-safe language without garbage collection, and one that is readable, modern, fast, intuitive, and world-class for developers.

It carries most of the features a software engineer would expect to find in a modern language: a package manager (epm), integration into most IDEs, object orientation, concurrency, an extensive standard library, generics and templates, and column-major arrays. As an extension of those tenets, complexity is naturally relocated inward, with the goal of providing a great experience for software engineers and for the people who use the applications they build on Envzn.

While designing Envzn, inspiration and learnings were taken from many other languages that are the gold standard of software today: Rust is known for its memory safety and speed, Julia for its work in math and metaprogramming, Go for its versatility and what a developer can do with it, Fortran for its matrices, Python for its extensions and scripting abilities, Java for paving the way for many modern languages, and C++ for its speed and for being the initial base for many modern languages including this one. You may find some similarities to the best features of these, plus a few additional things that you should find interesting.

Where it stands

Envzn is not released yet. The language, its compiler, its kernel, and its standard library are under active development in a private repository, and the public repository is not open. There is nothing to download today, and the download control above stays gray until there is. The documentation linked from this page is generated from the working tree and is current; treat everything else as a description of a language still being built.

Tenets

Every open design decision is weighed against six traits. They are not a slogan — they are the argument a change has to win before it lands, and they are written into the language specification as its first article.

When the six conflict, the tiebreak is fixed and public: memory safety is the floor and is never traded; otherwise relocate the cost inward, toward the compiler and away from the developer; otherwise judge the case in the open, on the record.

A first look

A program is a module. A module names classes; one of them implements TaskStarter and is where execution begins.

MODULE hiddenClassSmoke

INTERFACE Counter {
    MODIFY METHOD bump(int32 by)
    METHOD total() RETURNS int32
}

HIDDEN CLASS TallyImpl IMPLEMENTS Counter {
    PRIVATE int32 running

    INIT() { .running = 0 }

    MODIFY METHOD bump(int32 by) {
        .running = .running + by
    }

    METHOD total() RETURNS int32 { RETURN (.running) }
}

CLASS Main IMPLEMENTS TaskStarter {
    INIT(String[] argv) { }

    METHOD start() RETURNS STATUS {
        Counter c := CREATE TallyImpl()
        c->bump(5)
        c->bump(7)
        printline("total is 12")
        RETURN (SUCCESS)
    }
}

Three things in that fragment are the salient keys. A method that changes the object says MODIFY, so mutation is visible at the declaration rather than inferred from the body. A HIDDEN class is an implementation detail that callers reach only through the interface. And := moves ownership where = copies a value — two different operators because they are two different acts.

Features

Memory safety, proved at compile time

Envzn has no null, no optional type, and no ? suffix. The absence of an object is a state of the binding — EMPTY — that the compiler forces you to test before use, and primitives cannot be absent at all. Ownership is single and explicit, borrows are checked, and the analysis is a whole-program walk rather than a per-file approximation. Safety is the trait that is never traded away for any of the other five.

There are no pointers to manage. Envzn uses ownership handles and reference handles for objects, and these are never null — with one exception, WEAK REFERENCE.

Speed

Envzn compiles ahead of time to native code through a C++20 backend, over an intermediate representation of its own (EvIR) that is specified node by node. Fixed-size arrays live inline in the object and arrive default-initialized, so they cost no allocation and no zero-fill loop. The standard library is written in Envzn, not in a faster language behind a wrapper, which means the performance you can reach is the performance the library already reached.

A teaching compiler

A compiler error carries a stable code, a file, a line and a column, a remedy, and a reference to the clause of the specification it enforces. It was important for the evc compiler to become a “teaching compiler” so someone new to the language can learn it easily.

An extensive standard library

The kernel carries text, collections, math, time, and I/O; around it sit modules for JSON, logging, regular expressions, tabular data, networking, testing, and multi-dimensional arrays, with a package manager (epm) that fetches a module and its dependencies rather than a whole distribution. Every module ships a generated API page.

Generics — one mechanism, qualified up front

Generics and templates are not two things in Envzn. There is one mechanism, written TEMPLATE, and it exists to let you write a single homogeneous, type-preserving container instead of many. What opens one is a preamble of qualifiers on its type parameters, so a template states what it will accept before it states what it does:

TEMPLATE: GIVEN TYPE T IS Cloneable, PRIMITIVE (EXCEPT boolean, opaque, complex, number):

Several parameters each carry their own group of qualifiers, and compile-time dispatch (WHEN K IMPLEMENTS Equatable[K]) narrows a parameter further at instantiation. The payoff is in the error messages: because the contract is declared, a misuse is reported against the line the author wrote rather than against something deep inside an instantiation, which is most of the difference between a template a person can read and the error output C++ became notorious for.

What Envzn deliberately does not offer is template metaprogramming. Heterogeneity is expressed with an interface instead, because a generic system powerful enough to compute types is also powerful enough to make an error message unreadable.

Interfaces and inheritance

A class may IMPLEMENT interfaces and EXTEND a base class. Interfaces carry the contract and are the handle you hold; inheritance is available where a genuine specialization exists, rather than being the only tool in the box for sharing behavior.

Interfaces without type erasure

Holding a value through an interface does not throw away what it is. An interface in Envzn is a lens rather than an erasure: the value keeps its concrete identity underneath, and where the flow of a program proves that concrete type, recovering it binds directly and costs nothing. Where the concrete type is genuinely not known, the compiler says so and points at the WHEN … IS check rather than letting the program assert its way through.

Twenty-one primitive types, in ten families

Envzn separates the things other languages let blur together. The families, with the short aliases the compiler accepts:

  • Signed integer: int8, int16, int32, int64, int128; int and integer are aliases for int64.
  • Unsigned integer: uint8, uint16, uint32, uint64, uint128; uint is an alias for uint64.
  • Float: float32, float64; float is an alias for float32.
  • Boolean: boolean, holding TRUE or FALSE.
  • Character: char8, char16, char32; char is an alias for char32, a full Unicode code point in U+0000..U+10FFFF and deliberately not a C char.
  • Binary: binary, an uninterpreted octet — a bit pattern rather than a number.
  • Opaque: opaque, a type-erased value, and the one primitive with no default that must be initialized explicitly.
  • Number: number, a self-aware integer-or-float that reports which one it is holding.
  • Complex: complex, a pair of numbers written with the im literal — 3 + 4im.
  • Decimal: decimal128, an exact base-10 number with round-half-even arithmetic; decimal is an alias for decimal128.

A char is never an octet, and neither one is a number: char, binary, and uint8 are three distinct types and none implicitly converts into another. Conflating a byte with a character is the classic Unicode defect, so the language refuses the conflation at the type level rather than documenting it and hoping. Four further types (C.size_t, C.ssize_t, C.long, C.unsignedLong) exist only inside a foreign-function declaration and are described below.

Text containers on two axes

Four containers fall out of two independent questions — text or bytes, immutable or mutable: String, ByteBuffer, DynamicString, DynamicByteBuffer. Text prints as text and binary prints as hexadecimal; a String is never a place to carry arbitrary bytes.

Views that borrow and cannot escape

A StringView or ByteBufferView is a window onto storage someone else owns — a bounded range of code points or bytes, obtained with ->view(start, length), with no copy. A window of a window is a window, so a view narrows further with its own ->view, and materialises into an owning array with ->copy() when you actually need one. What makes it safe rather than merely fast is that a view cannot outlive what it borrows: storing one in a field or returning it as an escaping value is a compile error, not a documented hazard.

Value classes

Between the struct and the class sits a third kind. A VALUE CLASS carries the methods, generics, and interface conformance of a class, but the value identity and inline storage of a struct: it lives where it is declared rather than behind a heap handle, is passed by value, and is copied whole. This is what lets a number-like type carry real behaviour without paying for an object — decimal128, number, and complex are all value-class-backed, which is why they have methods and operators when the bare primitives do not.

Dense N-D arrays, column-major

A multi-dimensional array is a real type, not an array of arrays. float64[m,n] constructs at runtime and float64[3,4] stores inline; one comma-subscript a[i,j] gives one coordinate per axis. The storage order is column-major — axis 0 is contiguous — which is the order the numerical libraries of the last fifty years expect, so an Envzn matrix hands to BLAS and LAPACK without a transpose. Loop with the first index innermost, and the compiler will tell you when you have not.

int32 m = 3
int32 n = 4
float64[m,n] a
a[0,0] = 1.5
a[2,3] = 9.0

FOR j = 0 UNTIL n {
    FOR i = 0 UNTIL m {
        a[i,j] = 0.0        // column-major: first index innermost
    }
}

Fixed-size arrays live inside the object

When the size is known, T[N] declares N live, default-initialized slots inline in the object. No CREATE, no heap allocation, no zero-fill loop — it arrives usable, because every numeric primitive defaults to zero. The growable T[] pays an allocation plus whatever initialization you write for it. The difference is not theoretical: a membership table in the kernel's string scanner was building a 256-entry growable array per call; declaring the field as int32[256] and deleting the loop took the same scan from 553 ms to 81 ms — 6.8×, with no change to the algorithm.

Deterministic destruction

Destruction happens at a point the source determines, not whenever a collector next runs. Every owned object is released at the end of the scope that owns it, and a CLEANUP block runs on the way out — including the unwind path of a panic, and in reverse construction order, so the last thing built is the first thing torn down. Leaving a scope early by any route unwinds it the same way: a labelled BREAK, a return, a panic. This is what “memory-safe without garbage collection” actually costs and actually buys — release is a fact about the program text rather than about a runtime's schedule.

Errors and “exceptions”

The two are different situations, so they get different mechanisms. Errors are recoverable and are returned as a STATUS value — a file that is not there, a parse that did not match, a socket that closed. PANIC occurs when a foundational law has been violated: a divide by zero, an index outside its array, an arithmetic overflow. A panic is minimally recoverable, deliberately so — enough for a developer to catch it, log it, and shut the application down gracefully, and not so much that a program can make a habit of continuing past a broken invariant.

RETURNS, either-or

Many methods have to answer with one thing or the other: here is the object you asked for, or here is why you cannot have it. Envzn writes that directly into the return declaration with a | separator, and exactly one side is populated per call — the success side or the failure side, never both:

METHOD subscript(int64 index) RETURNS (MUTABLE REFERENCE T result | STATUS s)
MODIFY METHOD next() RETURNS (REFERENCE T value | STATUS s)

The success side may carry several typed slots, all populated together; the failure side is a single STATUS. Because the two sides are declared, the caller cannot read the value without having dealt with the possibility that there isn't one — there is no sentinel to miss, no null to forget, and no exception travelling invisibly up the stack. It is also distinct from an ordinary multi-value return, where every slot is always populated.

Arithmetic that refuses to lie

An integer operation that overflows raises a MathError panic. It does not wrap around silently, and it is not undefined. Where wrapping is genuinely what you want — a hash, a checksum, a ring buffer — you ask for it by name with the modular operators &+, &-, and &*, so the intent is on the page rather than in the reader's assumptions. Shift counts are policed the same way: a count outside the width is a compile error where the compiler can prove it and a panic where it cannot, never a silently zeroed result.

Structured concurrency

A developer writing concurrent Envzn does not write threads — they write tasks inside a scope. The unit of work is a Task, and every task's lifetime is bound to the CONCURRENT { … } block that launched it. The closing brace is a join point, which gives a guarantee worth stating plainly: a function call cannot leave background work running after it returns. The everyday shapes — a pool of workers draining a queue, a pipeline of stages, a broker fanning a message out to subscribers — are named, typed classes in the standard library rather than primitives you assemble; Channel, Future, Mutex, and the atomics stay in the kernel for the cases the standard shapes do not fit.

Reflection, computed at compile time

IDENTITY(x) asks the compiler what something is and hands back a struct describing it: the bare name, the module it lives in, its parameterized full name, its kind (class, value class, interface, enum, primitive …), whether it is generic, whether it is internal, whether it is blittable, and the ancestry it was reached through. For an enum it also carries the cases, name and value, in declaration order. All of it is computed during compilation and costs nothing at run time, and the compiler is its only producer — a program cannot fabricate one. That is enough to write serialization, logging, and configuration binding as ordinary library code rather than as a framework with its own annotations.

No casting, ever — but conversion is ordinary

There is no syntax in Envzn that reinterprets one type as another. No C-style cast, no parenthesised type in front of a value, nothing that lets a program assert a type it has not earned. What a strongly typed language actually needs is not casting but conversion, and conversion is common, so it gets two operators that say which kind it is. x INTO T is the widening, lossless one and always succeeds. x AS T is the narrowing, lossy one and is fallible — it hands back a value or a status, so the case where it does not fit is a case the code has to handle rather than a surprise at runtime.

Where a program needs to branch on a more specific type, it asks with a WHEN type-check. The narrowing inside the true arm is something the compiler has proved, not something the developer has asserted — and that difference is the whole reason the cast is missing.

No globals — a namespace instead

There are no static variables and no static functions sitting at a global level. Where a set of functions belongs together but no object needs to exist to hold them, the mechanism is a NAMESPACE: a stateless host of free functions and constants, reached by name — Math.PI, Math.sqrt(x). It holds behaviour and constant data and nothing else; a variable field, a mutating method, or a lifecycle in a namespace is a compile error, because a namespace has no instance to have state in. A namespace exists so that not everything need be a class.

No SELF

There is no SELF and no this, and there never will be. The author's view is that the keyword is a vestige that enables identity-centric bad design: god objects, self-referential mutation, and fluent builders that hand themselves back so a caller can write x->a()->b()->c(). The urge to reach for it is really the urge to avoid naming structure, and the language would rather you named the structure.

What replaces it is the two-layer cake. Split the thing in two: a minimal storage layer that holds the data and carries little behaviour, and a rich public face composed over that storage, which holds the behaviour actually worth using. The canonical example is in the text types — char32[] is the storage, and String is the face built over it. Applied to a builder, the storage type accumulates with plain MODIFY methods that mutate their own fields and return nothing, and the face type consumes it; you call the methods on a named local, one line at a time, instead of threading a chain through an object that keeps handing itself back. The result is that no object ever gives out a reference to the whole of itself, and the structure a program actually has ends up written down.

Crossing to C, declared rather than smuggled

Calling a C function is a declaration, not an escape hatch. A FOREIGN BIND names the C symbol and gives it Envzn types; from then on it is called like anything else, and the compiler type-checks the call site the way it checks every other one. Where the C types line up with Envzn's, the binding is a one-to-one passthrough and no hand-written shim exists at all — the kernel's Math namespace binds straight to <math.h>:

FOREIGN BIND sqrt(float64 x) RETURNS float64
FOREIGN BIND pow(float64 base, float64 power) RETURNS float64
FOREIGN BIND atan2(float64 y, float64 x) RETURNS float64
FOREIGN BIND hypot(float64 x, float64 y) RETURNS float64

Where the C types do not line up, the declaration says so rather than guessing. Four C-ABI types — C.size_t, C.ssize_t, C.long, C.unsignedLong — lower verbatim to the C spelling they name and are legal only inside a foreign declaration. They carry no arithmetic and no guaranteed width, and that absence is the contract: size_t is unsigned long where uint64_t is unsigned long long, and which of the two matches flips between macOS and Linux, so a fixed-width primitive would compile on one target and fail on the other. This is the Networking module binding POSIX sockets:

FOREIGN BIND send(int32 fd, binary[] data, C.size_t nbyte, int32 flags) RETURNS C.ssize_t
FOREIGN BIND recv(int32 fd, LOAD binary[] buf, C.size_t nbyte, int32 flags) RETURNS C.ssize_t

Two details are worth reading off that pair. The buffer is binary[] — a byte array, not a text type — because bytes off a socket are bytes. And recv marks its buffer LOAD, which says the C function writes into it rather than reading from it, so the direction of an out-parameter is visible in the declaration instead of living in a comment. Validation stays on the Envzn side: a bind performs the raw call and nothing else, and the checking a caller should get is written in Envzn around it.

A specification that runs ahead of the compiler — on purpose

Envzn is defined by a written constitution, and the implementation is measured against it rather than the other way round. The document describes the language as it is intended to be; a second article records what the compiler actually does today, and is where you confirm whether a construct is live. Where the two diverge, the gap is work not yet overtaken rather than an error in the design — and a construct described in the specification but not yet implemented is marked as such rather than quietly implied. The discipline that comes with it is the part worth having: when the specification and the code disagree, that is a defect to be fixed in the same change, not a divergence to be explained later.

Numbers, data science, and finance

Two application domains shaped the type system rather than being served by libraries after the fact.

Money. decimal128 is a first-class primitive: an IEEE 754-2008 exact base-10 number with round-half-even arithmetic, a rounding-context surface, and a defined 16-byte wire encoding. It is comparable and hashable, so it is a valid dictionary key and a valid sort element. Mixing it with binary floating point in one expression is a compile error, not a silent rounding — if you want that conversion you write it down. On top of it sit the money and quant modules.

Data. Alongside the fixed-width numerics, number is a self-aware integer-or-float value and complex is a pair of them written with an im literal (3 + 4im). Column-major N-D arrays, a tabular Data module, and the AdvancedMathstatsmlearn chain give the numerical stack, written in Envzn over vetted BLAS and LAPACK rather than reimplemented.

Benchmarks

TBD — this section will state, in plain terms, what was measured, on what machine, against which languages, and where Envzn currently loses. Nothing is claimed here until the numbers are published and reproducible.

BenchmarkEnvznCRustGoNotes
TBDTBDTBDTBDTBDTBD
TBDTBDTBDTBDTBDTBD
TBDTBDTBDTBDTBDTBD

Documentation

Four documents, in the order most people want them, plus the generated API for every module in the standard library.

Standard library API — per-module API pages, generated from the module sources: kernel, Data, Json, Logger, Networking, Regex, mda, evTest, and epm. See also the conversion rules and the documentation index.