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
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.
- Memory-safe Checked at compile time, across the whole program.
- Readable A developer who does not know Envzn should still be able to follow what a program does.
- Modern Ownership, modules, generics, and a typed foreign boundary, not retrofitted conventions.
- Fast Compiled and native. Performance is a language property, not an optimization pass a user is expected to apply.
- Intuitive The obvious way to write something should be the correct way.
- World-class Where a solved problem exists, take the vetted answer rather than a homegrown one.
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;intandintegerare aliases forint64. - Unsigned integer:
uint8,uint16,uint32,uint64,uint128;uintis an alias foruint64. - Float:
float32,float64;floatis an alias forfloat32. - Boolean:
boolean, holdingTRUEorFALSE. - Character:
char8,char16,char32;charis an alias forchar32, a full Unicode code point inU+0000..U+10FFFFand deliberately not a Cchar. - 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 ofnumbers written with theimliteral —3 + 4im. - Decimal:
decimal128, an exact base-10 number with round-half-even arithmetic;decimalis an alias fordecimal128.
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 AdvancedMath → stats →
mlearn 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.
| Benchmark | Envzn | C | Rust | Go | Notes |
|---|---|---|---|---|---|
| TBD | TBD | TBD | TBD | TBD | TBD |
| TBD | TBD | TBD | TBD | TBD | TBD |
| TBD | TBD | TBD | TBD | TBD | TBD |
Documentation
Four documents, in the order most people want them, plus the generated API for every module in the standard library.
User Guide
The book: learn Envzn properly, chapter by chapter.
Reference
Every name, indexed — keywords, primitives, and the types the modules expose.
Constitution
The specification. Article I is normative and wins on conflict.
IR Spec
How each EvIR node lowers to C++20 — generated from the compiler’s own trace.
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.