What Makes Envzn Different
Sixteen trouble spots it closes, and eleven design choices that are rare among mainstream languages
Most of what a systems language does is settled practice, and Envzn follows it. This page covers the rest: places where a familiar language lets a defect through silently and Envzn refuses it, and places where Envzn made a choice few other languages have made. Every example below is checked by the documentation build. A block marked as failing must fail to compile with the error shown, so a claim here cannot drift from the compiler without the build noticing.
Part 1: Trouble spots other languages leave open
(a) Comparing signed with unsigned
Where it bites: C and C++. The signed side is silently converted, so -1 < 1u is false.
Envzn: a mixed-sign comparison uses the mathematical values, and mixed-sign arithmetic is computed exactly, then
checked against the destination's range (Constitution I.F.ii(c.v)).
int32 delta = -1
uint32 count = 1
IF delta < count THEN { printline("true in Envzn") }
int64 offset = -5
uint64 size = 3
int64 sum = offset + size
printline(("sum = $1")->format(sum))
(b) Integer overflow
Where it bites: undefined behaviour in C and C++; silent wraparound in Go and Java; and in Rust, checked in debug
builds but wrapped in release builds, which is exactly where it ships.
Envzn: overflow on any fixed-width integer, signed or unsigned, raises a catchable MathError, in production builds
too. Wraparound is available, but only by asking for it with &+ &- &* (I.F.ii(a.iii), (a.iv)).
int32 big = 2147483647
int32 wrapped = big &+ 1
printline(("wrapped on purpose: $1")->format(wrapped))
TRY {
int32 next = big + 1
printline(("next: $1")->format(next))
}
RECOVER (MathError e) {
printline("overflow refused")
}
(c) Shifting by the width of the type or more
Where it bites: C and C++, where the result is undefined.
Envzn: a shift count must lie in [0, width), and a constant that breaks the rule is a compile error (I.F.ii(c.iv)).
int32 a = 5
int32 b = a LSHIFT 40
(d) Bitwise operators that bind looser than comparison
Where it bites: C, C++, Java and JavaScript, where x & mask == 0 means x & (mask == 0).
Envzn: mixing a bitwise operator with a comparison requires parentheses.
int64 x = 6
int64 mask = 4
IF x BAND mask == 0 THEN { printline("clear") }
(e) A leading zero that silently means octal
Where it bites: C, C++ and Java, where 0755 is 493.
Envzn: octal literals are refused; write decimal, 0x or 0b (I.C(e)).
int32 mode = 0755
(f) == on strings comparing references
Where it bites: Java, and C's char*, where two equal strings can compare unequal.
Envzn: == on a String is a compile error; content equality is ->equals() (I.F.ii(c.i)).
String a = "hello"
String b := a->clone()
IF a == b THEN { printline("same") }
(g) Null dereference
Where it bites: C, C++, Java, Go and C#.
Envzn: there is no null. A class-typed value may be EMPTY, and the compiler refuses any use it cannot prove is
present; IF w IS VALID is the proof (I.D.iv).
MODULE nullExample
CLASS Widget {
PRIVATE int32 id
INIT(int32 i) { .id = i }
METHOD ident() RETURNS int32 { RETURN (.id) }
}
CLASS Main IMPLEMENTS TaskStarter {
INIT(REFERENCE String[] argv) { }
METHOD start() RETURNS STATUS {
Widget w = EMPTY
printline(("id $1")->format(w->ident()))
RETURN (SUCCESS)
}
}
(h) Numbers used as truth values
Where it bites: C, C++, Python and JavaScript, where if (count) and if (ptr) hide a comparison.
Envzn: a condition must be a boolean (I.D.i(b.iii)).
int64 count = 3
IF count THEN { printline("some") }
(i) Changing a collection while iterating over it
Where it bites: C++, where it is undefined behaviour, and Java, where it throws at run time. Envzn: a live loop or view locks its source against change, at compile time (I.D.vi(d.ii)).
int64[] items := CREATE()
items->append(1)
FOR x IN items { items->append(x) }
(j) Error results that are never checked
Where it bites: C and Go, where an error code can be stored and forgotten.
Envzn: a STATUS kept in a variable must be checked before it goes out of scope. Dropping a result on purpose is
written visibly, as $? := call() (I.F.iv).
MODULE statusExample
CLASS Main IMPLEMENTS TaskStarter {
INIT(REFERENCE String[] argv) { }
METHOD save() RETURNS STATUS {
RETURN (SUCCESS)
}
METHOD start() RETURNS STATUS {
STATUS s = save()
RETURN (SUCCESS)
}
}
(k) Catch-all error handlers
Where it bites: Java, C# and Python (except:), where one handler silently swallows every failure.
Envzn: recovering from the base Error class is refused; name the errors you handle (I.M.ii).
TRY { printline("work") }
RECOVER (Error e) { printline("swallowed") }
(l) Silent narrowing, and casts
Where it bites: C and C++, where assigning a wide integer to a narrow one silently drops bits.
Envzn: there are no casts. A lossy conversion is the explicit, fallible AS; a lossless one is INTO (I.D.viii).
int64 big = 70000
int32 small = big
int64 big = 70000
IF (big AS int32) THEN { int32 small = $RETURNED
printline(("fits: $1")->format(small)) }
ELSE { printline($!) }
(m) Binary floating point used for money
Where it bites: almost every language, where 0.1 + 0.2 is not 0.3 and there is no built-in decimal type.
Envzn: decimal128 is a built-in exact decimal type, and mixing it with a binary float is refused (I.D.i(b.iv)).
decimal128 price = 19.99
float64 tax = 0.07
decimal128 total = price + tax
(n) Bytes confused with characters
Where it bites: C and C++, where char is a byte, so text and binary data share a type.
Envzn: a character (char, a code point), its UTF code units (char8, char16, char32) and a raw octet
(binary) are separate types. An octet is a bit pattern, not a number: it is written in hex or binary and cannot be
multiplied (I.D.i(b.i)).
binary b = 195
binary b = 0xC3
binary c = b * b
(o) Switch fall-through
Where it bites: C, C++ and Java, where a forgotten break runs the next case.
Envzn: MATCH never falls through, and it dispatches only over closed sets (an enum, a GROUP of types, or a
STATUS). Matching on arbitrary values is an IF chain (I.H.v).
int32 n = 5
MATCH n {
WHEN 5: { printline("five") }
}
(p) Build steps that run arbitrary code
Where it bites: npm install scripts, Cargo's build.rs, CMake: a dependency can run code on your machine at build
time.
Envzn: manifest build flags that could run code are refused, inline C blocks are not part of the language, and a
module may call C only after opting in with "allows_foreign": true (I.N).
{ "name": "netio", "version": "1.0.0", "target": "library", "allows_foreign": true, "sources": ["Socket.ev"] }
Part 2: Design choices that are rare
- Memory safety without lifetime annotations. Instead of trusting function signatures, the compiler follows each
reference through the code itself, including the code of the libraries it depends on, read from their
.evirfiles. What it cannot prove safe, it refuses (I.A.ii, I.I.v). Rust needs lifetime annotations for the same guarantee; C++ has no equivalent. - Leak-freedom proven at compile time. Every object is owned, and the graph of owning and counted references
must contain no cycle, so a leak is a compile error (
E3033). Swift's ARC and Rust'sRcleak a cycle silently (I.I.i(b.i)). - Overflow checks that stay on in production. See (b): the check is never stripped from a release build, which makes Envzn stricter than Rust on this point.
- Parameter modes spelled as words. A plain parameter owns its argument,
REFERENCEreads it, andMUTABLE REFERENCEwrites it, exclusively (I.I.vi). The same three meanings are sigils in Rust and drift between&,const&and&&in C++. - No casts at all. Every conversion is one of two operators:
INTO, which is lossless and always succeeds, andAS, which may lose information and so may fail. Each conversion pair may be defined only once across a program (I.D.viii). - Every diagnostic cites the law. All 639 compiler diagnostics carry a reference to the section of the Envzn Constitution they enforce, and many include the corrected code (I.A).
- Bounds-check removal you ask for, and that is proven.
HOTLOOPasks the compiler to replace a loop's per-element bounds checks with one check up front. If it cannot prove that one check covers every access, the build fails (E5027) rather than run unchecked (I.H.iii(f)). - Three assignment operators, one per meaning.
=copies a value,:=takes ownership (constructing, cloning or moving), and=@binds a reference. A move is visible at the point it happens (I.F). - Numbers built for real work.
decimal128for exact money arithmetic;number, which holds an integer or a float and promotes rather than overflows; andcomplexwith animliteral, as in3 + 4im(I.D.i). - Column-major N-D arrays in the language.
T[R, C]is laid out the way BLAS and LAPACK expect, as in Fortran, Julia and MATLAB. No C-family language does this (I.J.i). - Calling C without hand-written wrappers.
FOREIGN BINDdeclares a C function in Envzn types, and the compiler produces the call. Raw pointers stay insideUNSAFEblocks, and a module must opt in before it can call C at all (I.N, I.I.viii).