Why Heroes
· rebel rebelHeroes starts with two constraints: the code must be easy for a model to read, and programs must be able to use C libraries.
Recurring model mistakes · sound and vision
Models that generate code make recurring mistakes: they swap arguments of the same type, forget a case, use an undeclared name or borrow a keyword from another language. Heroes is designed to let the compiler catch these mistakes.
A language designed so that every plausible LLM mistake is a compile error will produce working programs in fewer total tokens than a conventional language, even if each program costs more tokens to write.
One part of the thesis has been tested by introducing plausible mistakes into working programs and counting those the compiler catches. The same programs are then checked with the Heroes-specific rules disabled. The comparison measures the effect of those checks. The total token savings still need to be measured. Tokens are the pieces of text a model reads and generates. The test results → What is measured, what was written down before measuring, and what is not known yet →
Why avoiding errors can save tokens
real cost = program tokens × (1 + rewrite rate)
Shorter syntax reduces the tokens needed to write a file. Fixing an error requires more steps: the model reads the message, rereads the code, rewrites a function and tries compiling again. Avoiding a correction can therefore save more tokens than shorter syntax.
Heroes therefore uses explicit declarations and short forms for common operations. It requires extra words where they help the compiler recognise a mistake.
Using existing libraries
Heroes uses C libraries for files, sockets, databases and graphics. Developing and maintaining its own libraries would require too much work for a project run by one person. Heroes provides the essential operations and uses existing C libraries for the rest.
maths beyond arithmetic → libm files, sockets → libc
regular expressions → PCRE2 HTTP → libcurl
JSON → cJSON SQL → SQLite
graphics, audio, terminal → SDL, raylib, ncurses
To use a library, declare the functions you need. The name on the first line identifies the header, the file that describes the library’s functions and types. Clang, the C compiler, checks your declarations against that file.
extern "math.h" link "m"
function sqrt(x: f64) -> f64
function pow(base: f64, exponent: f64) -> f64
function floor(x: f64) -> f64
A person or model might declare a C function that does not exist. The compiler catches this too by checking the library’s header:
error[ffi_unknown_name]: `math.h` declares no `sqrtt` — clang read the header and could not find it
at ffi-demo.hero:2:5
|
2 | function sqrtt(x: f64) -> f64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: an `extern` names what the header already has (§4.19): check the spelling, or name the header that does declare `sqrtt`
Clang uses the library’s actual header. It checks names and type widths where required, reporting errors before the program runs. Heroes does not need to maintain a second description of the library.
The header makes it possible to check the declarations. Heroes’ own rules still apply: pow(base:, exponent:) takes two values of the same type, so the call must name its arguments.
The available operations include print, len, push, slice, sort, conversions between types, map, filter and fold. Every addition requires a recorded decision.
Using C libraries should be as straightforward as writing Heroes code. When a library cannot be used from Heroes, the programmer has to write more C code. The project aims to reduce that need. Programs that use C libraries →
Why the compiler emits C
Heroes translates your program into C and lets clang produce the executable. This lets clang check the C function declarations against the libraries’ headers on every build. The initial plan was to generate machine code directly; it changed to make use of these checks. Library verification, rather than a speed advantage, drove the choice.
Seven words can begin a line
A file is a list of declarations, each starting with the word for what it
declares. constant for a fixed value, function,
record for a group of named fields, variant for a
value that comes in several forms. Three more declare nothing about your
data: use for another file, test, and
extern for what comes from C. The list ends there. No classes,
no interfaces, and nowhere a global variable anybody can modify.
# A position on the integer grid.
record Point
x: i64
y: i64
# A rectangle, by two corners.
record Rect
corner: Point
extent: Point
You can write declarations in any order. Two functions can call each other without forward declarations. During compilation, Heroes reduces the code to seven constructs, the basic internal forms used to represent a program.
Why strings have holes
A string can carry values, and an f before the opening quote is what turns {n} into the text of n.
function line(at: i64, of: i64, star: Star) -> str
return f"{at}/{of} {star.name} at magnitude {star.magnitude}"
Why the f: a brace already had a job inside plain strings, and over two hundred of the compiler’s own strings carried one, so a string that reads holes announces it at its first character and every other string is unchanged. Why any expression: listing the admitted forms cost more specification tokens than admitting them all, and the count decided. What a hole may hold, what {{ writes, and what the compiler says to a hole with no text are in the chapter. Strings, holes included → The whole example →
The language rules · fashion
- A line is understood by looking at that line. Its meaning is
decided by the line and the function around it, never by something written
three files away. No inheritance, no macros, no exceptions, no implicit
conversions, no null: absence is a type,
i64?. C’s zero pointer keeps its own name,nullptr; aptris a bare address no Heroes code reads through, and the pointer a C library hands you carries the name the library gives it, so one cannot be handed where another belongs. Every one of these no’s is on the record, with who said it: what Heroes refused → - One way to write anything. A formatter with one canonical form, one word for loops, and argument names required where types cannot tell two arguments apart.
- Error messages that machines can use. Each message identifies the problem and its location in the code. If a declaration elsewhere is involved, it points there too. Suggested fixes are marked
certainwhen they are certain orguesswhen they need review. Use???to leave an expression unfinished and ask the compiler which type it expects. - A program must not corrupt memory. The program stops and reports the error location in cases such as: an integer exceeds its range, integer division has a zero divisor, an index is outside an array, a string slice splits a character, recursion goes too deep or
.must()receives an error. In design decisions, preventing unchecked memory access takes priority over elegance and brevity. - Small enough to fit in a prompt. The specification stays below 16K tokens, counted the way the model itself counts, through Claude’s token-counting API. The count is recorded with the model that made it, and a test on every commit, the project’s word for a recorded change, holds the recorded count under the ceiling and refuses a text that moved without a new count. You can give the whole specification to a model. Read it →
What Heroes is bad at
Each one is a choice, and this is what it costs.
- There is no standard library. The built-ins are one line of the
specification: printing, length,
push,slice,chars,keys,join,repeat, sorting, the numeric conversions, andmap,filter,fold,find,any,allandrange, which are themselves written in Heroes; plus reading and writing a file, the command-line arguments andexit. Everything else is a C library you declare yourself, and there is no package manager and no registry to pull it from. - Parallel work carries one number. A thread takes an
i64and gives one back. Put a list inside a callback’s type and the compiler refuses it before anything runs, witherror[ffi_type], and its note tells you to hand C a pointer and a length instead. Spreading a large array across cores is not what this language does today. - Past the C boundary, what is checked is what you wrote down.
clang checks that your declarations match the library’s header. A
handle, the pointer to something the library keeps for itself, taken with a
call marked
acquiresis counted, and the program stops at exit if one was never given back; one you were only lent cannot be handed to a call that closes it. What no check sees: what the library does with an address once it has it, and whether it still reads a text after you took it back. That part stays yours. What the boundary does check → - Some mistakes no check catches. A typo in a string used as an
error code is still a valid string, and
10where you meant100is still a number. The errors page carries the measurement, so the share the compiler does catch is a number you can read rather than a promise. The available results → - Programs run slower than the same program in C. Every index is checked, every integer operation stops the program on overflow instead of wrapping, and a value shared between two names is copied the moment one of them changes it. Speed is not a goal of this language, and a crash or a corrupted memory is a bug. No benchmark against another language has been published, and this page will say so until one has.
- You write more. Arguments are named where two parameters share a type, every call that can fail is answered on its own line, and there are no anonymous functions, no inheritance, no overloading, no second variable under a name already in use, and no default values. Each of those is a refusal with its reason written down. The refusals →
- Linux, macOS and Windows, on 64 bits, through clang. Heroes runs on 64-bit machines and compiles through clang, which you install yourself: there is no installer and no package yet. The browser and microcontrollers are not where it runs today. Getting started →
Common questions · quicksand, avoided
Why no null, why named arguments, why every assignment is a copy, how fast it is, what it costs, whether to trust a compiler an AI writes: the questions people ask before trying Heroes have a page of their own, each answered in a few lines. Common questions →
To continue: The whole language is one file, small enough to hand to a model with room left for your program. Every program in the repository is written in it, and four commands build the compiler.