Thanks
· sons of the silent ageHeroes draws on ideas from other languages and research. This page names the authors and each contribution.
These credits name the people and teams who designed the languages. They were not consulted about Heroes and are not responsible for how it uses their ideas.
The foundations and tools
The compiler translates Heroes into C11 and uses clang to produce executables and check C library declarations against their headers, the files that describe functions and types. The first compiler, used to reach the one written in Heroes, was written in Rust.
- C: Dennis Ritchie, who designed it; Ken Thompson, who wrote B, the language C came out of, and Unix with Ritchie; and Brian Kernighan, who co-wrote the book that taught it to everyone. Heroes uses C to access existing libraries.
- LLVM, clang: Chris Lattner. Heroes uses clang to check C function declarations against library headers.
- Rust: the Rust team, and Graydon Hoare, who started it. The first compiler used a restricted set of Rust features, chosen to make conversion to Heroes straightforward.
- Zig: Andrew Kelley, and Nim: Andreas Rumpf. The C file the Heroes compiler can always be rebuilt from is kept in the repository, as Zig keeps in its own the ready-made program it starts compiling itself from, and Nim keeps the C its compiler generated, with scripts to build it, in a repository of its own, each copy made from a named version of Nim’s source.
The value model
In Heroes, changing a copy does not change the original. Values cannot form reference cycles, so memory can be freed by counting the references still in use. No garbage collector is needed to find cycles. This model draws on the work cited below.
- Hylo, formerly Val: Dimitri Racordon, Denys Shabalin, Daniel Zheng, Dave Abrahams and Brennan Saeta, the authors of Native Implementation of Mutable Value Semantics. The sentence Heroes depends on is theirs: references cannot be stored in variables or fields, hence all values form disjoint trees. This restriction avoids the need to detect cycles or annotate how long references remain valid.
- Swift: Chris Lattner, Doug
Gregor, John McCall, Ted Kremenek and Joe Groff. Copy-on-write, which delays copying data until a change requires it, reference counting,
inoutparameters (this language’s@), and leading-dot variants. And the idea that the space around an operator tells its uses apart: at the start of a line in a list, Heroes reads a minus written against its value as a sign, and refuses a minus set apart from its value, which Swift would read as a subtraction. Heroes draws on these ideas without introducingclassor theweakandunownedreference forms. - APL, J, K: Kenneth E. Iverson, Roger Hui and Arthur Whitney. These languages show how to work with whole values without exposing mutable references in the code.
- R: Ross Ihaka and Robert Gentleman. MATLAB: Cleve Moler. Two examples of copy-on-write values used in statistics and numerical computing.
Safety without a garbage collector
Heroes aims to prevent invalid memory access and data corruption. The projects below explored safety without a garbage collector, the system that finds and frees unused data.
- Cyclone: Dan Grossman, Michael Hicks, Trevor Jim and Greg Morrisett. References only as function parameters, which deletes lifetime annotations from the language. This project uses their rule twice: it is the discipline the Rust bootstrap compiler was written in, and it is why the port to Heroes was mechanical. The rule has their name on it in the repository.
- Modula-3: Luca Cardelli,
James Donahue, Lucille Glassman, Mick Jordan, Bill Kalsow and
Greg
Nelson. A model for concentrating unsafe operations in an identifiable part of the program that can be reviewed.
Its definition also gave Heroes the shape of
use:IMPORT Iis short forIMPORT I AS I, and the same local name may not be used twice, so ausemay rename what it brings in, and a name only has to be unique within one file.
Keeping the language small
Wirth’s languages provided a reference for keeping the specification short. For the Heroes compiler, the limit instead applies to individual files: they should remain understandable to a reader.
- Pascal, Modula, Oberon: Niklaus Wirth,
with
Jürg
Gutknecht on Oberon.
recordandvariantcome from here. So does the calibration: the Oberon report at sixteen pages is how long a specification is allowed to be. Pascal-P4, at roughly four thousand lines, once showed how little a self-hosting compiler needs; that is history, not a size this project holds itself to. Wirth also wrote an entire operating system in a language with no raw pointers, and nothing argues harder for this whole approach. Oberon dropped Modula’s variant record, which Wirth found mostly misused to get around the types, and for that reason Heroes refused to bind a C union, a type whose fields all share one piece of memory, as avariant. And the Oberon report’s rule for itself, that what it leaves unsaid follows from the rules it states or is left out on purpose, is a test the Heroes specification applies: it says outright that there are no anonymous functions, functions written in place without a name, because nothing in its rules lets a reader work that out.
Types and alternatives considered
- ML: Robin Milner and
the group at Edinburgh, among them
Michael J. C.
Gordon. Product and sum types, which group fields or represent alternative cases. Heroes expresses them with
recordandvariant. - The arrow: Alonzo Church.
->in a function type is familiar notation, and it reached Heroes through ML, Haskell, Rust, Swift and Python’s type hints without changing shape. It was kept exactly as it was found. - The subroutine: David Wheeler, on EDSAC in 1951. Somebody had to invent the idea of calling a piece of code from more than one place.
- Type inference: J. Roger Hindley and Robin Milner. This one is a thank-you for something Heroes does not use: whole-program inference can make it harder to connect an error to its cause. Knowing precisely what was being given up, and why, is what their work makes possible.
- Rust again, the Rust team. Its manual, the Rust Reference,
counts two kinds of statement, a declaration and an expression, and puts
letamong the declarations. In Heroes too a declaration is not a value, so one written as the short body of a case ofmatch, the form that picks among cases, as in=> x = 5, is refused as the program is read. Rust’s own check for a type that contains itself once overflowed the stack, the memory a program uses to keep track of its calls, so the Heroes check for such a type walks it step by step instead of calling itself deeper and deeper. - Rust and Zig together: a float, a number with a fractional part, cannot be the key of a map. Rust’s map asks its keys for equality and a hash, a number worked out from the key to find it again, and Zig refuses to hash a float key while the program compiles.
- C++: Bjarne Stroustrup, who designed it, and WG 21, the ISO and IEC working group that keeps its standard. Stroustrup’s paper on concepts says that a C++ template, a piece of code written once for many types, hides in its body what it needs from its argument, and that the error appears only when the template is used, long after the point of call. So Heroes reports, inside a function written for many types and at the place it is written, an operation those types may not allow.
- Swift again, and Java:
James Gosling, who
designed Java, and Oracle, which develops it. In a function written for many
types, a type parameter (the stand-in for the types it works on) that
appears nowhere in its signature, the line naming its parameters and
result, is an error where the function is declared, as in Swift. One that
the arguments leave open takes the type its context asks for, as Java works
out a generic method’s result from the assignment or the parameter it
goes to. Swift also kept both layers of a value that may fail inside a
value that may fail: its
try?flattens only its own result, and such nested values stay legal. Heroes keeps both layers too.
What the language feels like to use
These languages influenced Heroes’ syntax, checks and commands. Readers who know them will find many familiar choices.
- Go: Robert
Griesemer,
Rob Pike and
Ken Thompson. One
canonical format with no options, unused variables as errors, a line that
ends without a semicolon, and the initial choice to omit generics.
Escapes are split as Go splits them (an escape is a backslash that changes
what the next character means): the one for a single quote is allowed only
in a character written between single quotes, the one for a double quote
only in a text between double quotes. A structure that contains itself
directly, or inside an array, a list of values, is refused as a type with no
size, as Go refuses it, and the message names the whole loop of types, the
repair Go made when its own error named only one. What a function written
for many types may do with them is declared, as in Go’s design for
type parameters, and never deduced from whatever its body happens to do. As
in Go, the types worked out for a call are the same whatever order its
parameters come in, and an argument whose type is written counts before one
whose type is not. Go’s tracker, its public list of reported
problems, is also the evidence for a limit: working out a type from where a
value is used stops being easy to pin to one place once calls nest, as in
f(f(f(0))), so Heroes does it one level deep, from a written type, a declared result or the parameter of a function that is not generic. - Nim: Andreas Rumpf. The foreign-function mechanism, reusing previous compilation results for each module, and
nim r, which isheroes run. Also the standing warning the project repeats to itself: copy Nim’s surface, never its implementation. - Zig: Andrew Kelley. Tests in the same file as the code they check, and NUL-terminated strings, which use a zero byte to mark the end as C expects.
- ABC: Leo Geurts, Lambert Meertens and Steven Pemberton, at CWI in Amsterdam. Indentation as the syntax, with no braces at all. Guido van Rossum worked on ABC for years before carrying that into Python, and Peter Landin had named the rule in 1966. Heroes takes it for two reasons at once: a brace nobody writes is a token nobody spends, and the models that write code have read so much Python that this is one piece of layout they already know.
- Python: Guido van
Rossum, and
Tim
Peters for
import this, which becameheroes this, included in the compiler. It is here. - Odin, Jai: the author of Odin, who publishes as gingerBill, and Jonathan Blow. One uniform way to write a declaration, whatever is being declared.
- Ruby: Yukihiro Matsumoto. The reading rhythm (named blocks, named arguments, dot chains), used as a guide to readability, without adopting metaprogramming, the ability to alter code behaviour through other code.
- Unicode: the Unicode Consortium, in a report edited by Robin Leroy and Mark Davis. Two invisible characters, the zero-width joiner and non-joiner, stay allowed in a string, because their guide to handling source code says the spelling of some major languages needs them.
- Haskell: the committee that designed it, named further down for
its early compilers. Its report defines
filteras giving back the elements that satisfy the function it is handed, and the Heroes specification says the same in words:filterkeeps what the function accepts. - C again, in Dennis Ritchie’s own history of it: B and early
C spelled
+=as=+, a mistake repaired in 1976. That is the evidence behind refusing=@, the reversed form of the@=that declares a Heroes variable that can change: a reader could take=@for=followed by@.
Error messages and unfinished code
- Rust: the Rust team, and Elm: Evan Czaplicki. Between them they proved that the quality of an error message changes the whole experience of a language, and that it is a design deliverable rather than plumbing. Heroes aims to include the information needed to fix an error without opening other files.
- Idris: Edwin Brady. Agda: Ulf Norell, and
Catarina Coquand on the first version. Typed holes, unfinished expressions for which the tool reports the expected type: in Heroes,
???is a legal expression, and the compiler answers it by saying what belongs in the gap. - GCC: the GNU Project, and GHC, the Glasgow Haskell
Compiler: Kevin Hammond, who wrote it first,
Simon Marlow,
Simon Peyton
Jones and the Glasgow Haskell Team. In Heroes, a form the compiler
cannot translate yet is reported as a kind of message of its own. GCC files
its “sorry, unimplemented” as a kind apart, beside errors and
warnings, and
GHC’s
Sorrystands beside itsPanic, for something known not to work yet, which is not counted as a bug.
Immutability at scale
- Erlang: Joe Armstrong, Robert Virding and Mike Williams. Immutable values and isolated processes provide a reference for managing data. In Heroes, independent values also help readers understand a function without looking for changes elsewhere.
Generating C code
Heroes generates C and lets a C compiler handle the work needed for each machine. The languages below followed this approach before Heroes.
- cfront: Bjarne Stroustrup. C++ began as a program that emitted C.
- Nim: Andreas Rumpf. Named further up this page for the way it calls C functions, and it belongs here as well, because the two are one decision. Nim’s compiler writes C when nobody asks it for anything else, and C++, Objective-C and JavaScript are alternatives a user turns on. A compiler that writes C can include the header of any library it wants to call, which is what makes that foreign-function mechanism work at all. Heroes took the same road for the same reason.
- Vala: Jürg Billeter, Raffaele Sandrini and Rico Tzschichholz. Chicken Scheme: Felix Winkelmann. Cython: Robert Bradshaw and Stefan Behnel. Three examples of languages that generate C.
- Early Haskell: designed by a committee, among them Paul Hudak, John Hughes, Simon Peyton Jones and Philip Wadler. Early implementations of this high-level language also used C.
Where Heroes meets C
A Heroes program calls C libraries directly, and it runs on a runtime written in C, the piece of program that sits underneath and keeps things working while it runs. The rules below, for what crosses between Heroes and C, were taken from projects that had worked at that border first.
- Rust again, the Rust team. In Heroes a C union is built naming
exactly one of its fields, as Rust builds its own, and one built with more
is refused. Turning text that C hands over into a Heroes string is a check
that can fail and repairs nothing, as Rust’s
to_strfails on text that is not valid UTF-8, the usual way of storing text as bytes, beside a separate conversion that replaces the bad bytes, and as Swift spells its checking conversion apart from the others. Rust’s documentation is the evidence against handing C a value nobody has filled: callingmem::uninitializedis undefined behaviour, where anything at all may happen, on nearly every type. So Heroes obtains a record that a C function fills from a function that returns it. Rust reports one C function declared under one name with two different types, though not between its crates, the packages it builds separately; Heroes reports it between its own modules too, before the linker, the program that joins the compiled pieces into one executable, puts the program together. And Rust’sthread::spawnasks for the work it hands a new thread, a line of work that runs alongside the others, to be passed by value, under conditions written into its signature. In Heroes, likewise, the place where a thread is created checks what crosses into it, and lets through only what was measured safe to cross. - GCC again, the GNU Project. In Heroes a C function that hands out
memory is declared with the function that gives it back, as GCC’s
mallocattribute pairs an allocator with its deallocator. The C that Heroes writes zeroes a union itself, because GCC 15 no longer promises that{0}clears all of one. A length handed to C is counted in C’s own unit, as GCC’saccessattribute counts elements of the type pointed to, or bytes when the pointer isvoid *. And passing C a pointer of the wrong type is an error in every C file Heroes writes, as GCC 14 made it, because such warnings “are easily ignored, resulting in difficult to diagnose bugs”, and as clang 22 does by default. - Clang again: Chris Lattner, its original author, and the LLVM Developer Group. In clang’s reference counting for Objective-C, a counted value may start out as a null pointer, an address that points nowhere, and counting a null pointer up or down does nothing. Its static analyzer, which looks for mistakes without running the code, sets an annotation that holds memory beside one that takes it over: a holder will give the memory back at some later point, and giving back held memory is reported as giving back memory one does not own.
- Racket: PLT Inc. In Racket, calling by hand the function that frees a value cancels the most recent automatic release registered for it. Heroes, so that no value is freed twice, refuses while compiling a call of a C library’s freeing function on a value the compiler will free by itself.
- C again, in the words of its standard. A header name holding a
quote, a backslash,
//or/*is refused, because C leaves undefined what happens to such a name. Heroes makes no promise about the padding bytes inside a record, the gaps C may leave between its fields, because C11 leaves their values unspecified after a store. And where C99 lets a function ask for an array of at least N elements, clang checks the array passed in and warns only when it is too small; Heroes checks a length against the field of a C record it goes into in the same way, only where that length is a number known while compiling. - The Linux kernel:
Linus Torvalds,
its author, and its community contributors. C’s plain
charhas no sign of its own: on some machines it can hold negative numbers and on others it cannot. Heroes fixes its sign with one compiler flag, the same on every machine, and a patch for the kernel, the core of the operating system, proposed adding the opposite flag. A proposal to make Rust’sc_charthe same everywhere was closed by its own author the next day. - Go and Zig again. The options a C library’s package hands to the compiler are admitted word by word, by their whole name, as cgo, Go’s way of calling C, admits them since a security flaw. On Windows, the words a program is started with reach it through WTF-8, an encoding built on UTF-8, with nothing lost, as Go converts what Windows hands it and Zig converts every Windows path. In Go, a conversion that lost characters had once made the function that removes a folder and its contents hang.
- gnulib: the GNU Project. A float prints as gnulib prints it:
starting from one digit for the tiniest values and from a fixed count,
FLOAT_DIG, for the rest, digits are added until the printed text reads back as the same number. - Nim again: Andreas Rumpf. A Nim program whose threads raised errors into one value shared by the whole program freed the same memory twice, because Nim’s reference counting is not atomic (an atomic step is one no other thread can interrupt halfway). On that evidence Heroes refused to let several threads share its counted values.
- CPython, the reference implementation of Python: Guido van Rossum, its author, with the Python core developers and community, supported by the Python Software Foundation; and OCaml: Xavier Leroy, Damien Doligez, Jérôme Vouillon, Didier Rémy and Ascánder Suárez, developed at the French Institute for Research in Computer Science and Automation. Both ask a thread that C creates to register with them before it runs their code. From that rule comes the Heroes one: its runtime notices a thread it does not know and stops cleanly instead of corrupting memory.
- Chromium: Google, its author, and the Chromium Projects, which
Google controls; and LLVM: Chris Lattner and
Vikram Adve, its
authors, and the LLVM Developer Group. On macOS, LLVM’s code asks for
the main thread’s stack size for a thread it creates, because the
default for other threads there is 512KB, and Chromium gives a created thread the larger of the default and the
current limit, since macOS applies that limit to the main thread alone. The
Heroes runtime does the same: a thread it creates gets at least the stack of
the thread that ran
main, the function a program starts from. - Java again. Its virtual machine’s specification states
what happens when the stack runs out, a
StackOverflowError, and allows stacks of a fixed or a growing size without choosing one. Heroes does the same: running out of stack stops the program the same way everywhere, and no depth is promised. - D: Walter
Bright, joined later by
Andrei
Alexandrescu, and the D Language Foundation; and Zig again.
D’s
scope(exit)runs its action when the current block closes, not where it is written, and Zig’sdeferruns when its block is left, itserrdeferonly when the function returns an error. Heroes has no such statement; the panel that weighed one ruled that if it ever enters, it ends with its block, as theirs do.
Somebody got there first
Heroes reached each rule below on its own, and found out only afterwards that somebody had got there first. The credit is theirs.
- Native Implementation of Mutable Value Semantics: Dimitri Racordon, Denys Shabalin, Daniel Zheng, Dave Abrahams and Brennan Saeta, named above for Hylo. Besides the sentence Heroes depends on, their paper already held two more of the rules Heroes follows: the compiler writes by itself, for each type, how its values are copied and how they are destroyed; and an array keeps count of the places that share it and is checked at the moment it is changed, so that it is copied only when a change requires it.
- Ada: Jean Ichbiah for Ada 83, and Tucker Taft for Ada 95 through 2012. Until Ada 2012, a call that could change two of its arguments copied both back at the end, in an order the language left unspecified. Ada 2012 then made the call illegal when two parameters it may write to are the same variable of a simple type, such as a number, and the compiler can tell that they are.
- Swift: Chris Lattner, Doug Gregor, John McCall, Ted Kremenek, Joe Groff and Apple. Passing the same variable twice to one call, in two places that may each change it, counts as two overlapping changes, and they conflict. Counting references is written as explicit instructions in the intermediate language, the halfway form the compiler translates a program into before it reaches the machine. When a list separated by commas is left open, Swift’s compiler ends it at a new line that begins with a word that could never start an item. A bug report brought over to GitHub from the old tracker opens its title with its old key in square brackets and repeats it in a row of its text.
- Go: Robert Griesemer, Rob Pike and Ken Thompson, its designers, and Google and contributors, its developers. A calculation made only of constants is worked out exactly, a constant with a declared type must fit in it, and dividing a constant by zero is not allowed. The name of a package to import is first taken out of its quotation marks and only then checked, and it is refused if it is empty or holds a character that is not allowed. An @ in an option handed to the linker on Apple’s systems could be used to run any code at all, and was registered as a security flaw. Since version 1.20, code written once for many kinds of value, asking only that its values be comparable, may also be used with a kind whose comparison can stop the program with an error while it runs.
- Rust: the Rust team. When a variable is never used and a piece of text in quotation marks names it between braces, the compiler asks whether its value was meant to be filled in there, and suggests how to write that. In the form of its messages made to be read by other programs, the place of a mistake is given by two counts of bytes from the start of the file, counted from zero, the second just past the end, with the column numbers beside them. And the fixed limit on the steps the compiler may take to work out a constant became a warning, repeated at ever longer intervals.
- Zig: Andrew Kelley and the Zig Software Foundation, which develops it. A name declared inside a function may not be the same as the name of something declared further down the same file. A variable declared as changeable and never changed is an error, and the repair is to declare it constant. A report on their tracker says it is not acceptable for the same piece of code to compile one time and not another.
- C#: Anders Hejlsberg, who designed it, and Mads Torgersen, who develops it, both at Microsoft. A name declared inside a function may not take a name the code around it already uses, and the compiler reports it in whichever order the two declarations are written. A calculation made only of constants is checked by default, and a result too large for its type is an error while the program is still being compiled.
- C: Dennis Ritchie, who designed it, and WG 14, the ISO and IEC working group that writes its international standard. The standard asks that a calculation made only of constants give a value its type can hold.
- D: Walter Bright,
joined later by
Andrei
Alexandrescu, and the D Language Foundation. In D’s description of
the C library, the name handed to
getenv, the C function that looks up by name a setting of the environment the program runs in, carries the wordscope, while the function handed toatexit, which C keeps to call when the program ends, carries no mark. - GCC: the GNU Project. When GCC compiles itself in stages, the compilers of the second stage and the third are compared, and if they differ, it is usually the second stage’s compiler that compiled GCC wrongly. Its tool that looks for mistakes in code without running it knows that memory handed out by one function may be correctly given back through any one of several others, and follows each piece of memory against that whole set. When it optimises a whole program at the moment its pieces are joined, GCC warns without being asked if files compiled separately declare one name, visible to the whole program, with different types. While it reads a system header, GCC shows no warnings; a header is a system one when it sits in a folder the compiler chooses, or in one added with an option that marks it as such. And an instruction written in the source to change how warnings are reported counts for more than any option the compiler was started with.
- Clang: Chris Lattner,
its original author, and the LLVM Developer Group. In its checks for code
run by several threads at the same time, the annotation on a function that tries to take something and may fail begins
with
trueorfalse, the answer by which the function says it succeeded. - Microsoft SAL: Microsoft, whose source-code annotation language
describes how a function uses what it is given and what it guarantees when
it finishes. A function carries at most one
_Success_annotation, and it states the condition under which the function succeeded.
Choices to consider carefully
The project also studied the limits and costs of other languages’ choices. These precedents help determine which approaches to adopt and which to avoid. What was refused here, and who refused it, has a page of its own: the refusals →
- Lua, with Nelua and Pallene: Roberto Ierusalimschy, Luiz Henrique de Figueiredo and Waldemar Celes. The precedent for a small language that compiles, and the cost of converting data from a universal table before and after C calls. Heroes gave up the universal table for that reason.
- APL again, in the other direction: ultra-compact notation is the one place where this project cites Iverson against itself: Heroes prefers more explicit forms to help models read and generate code.
- R and MATLAB again: both document the cost of copying shared data when it changes. In Heroes too, a change may require copying the structure, a cost to consider inside loops.
- Unison: Paul Chiusano and Rúnar Bjarnason. Code stored as a tree with names as labels: the origin of an idea that was explored here at length and then dropped in favour of one readable syntax.
And the name
David Bowie, for “Heroes” (1977), the song and the album. The name keeps the album’s quotation marks. Bowie is also behind the book the language grew out of. Musical references stay in the project’s prose and do not appear in error messages or library names.
- hunky dory the album itself, 1971
- changes Hunky Dory, 1971
- quicksand, avoided Quicksand, on Hunky Dory, 1971
- five years Ziggy Stardust, 1972
- moonage daydream Ziggy Stardust, 1972
- lady stardust Ziggy Stardust, 1972
- rebel rebel Diamond Dogs, 1974
- speed of life Low, 1977
- sound and vision Low, 1977
- a new career in a new town Low, 1977
- heroes “Heroes”, 1977
- sons of the silent age “Heroes”, 1977
- fashion Scary Monsters, 1980
The gold lines beside some section headings are Bowie songs. All twelve are above, oldest record first, along with the one this language is named after. Every title opens the recording on YouTube; where it names an album, the link opens the album’s entry on Wikipedia.
Next: the panel, where five judges review proposals and discuss how to adapt these ideas to Heroes.