Inference everywhere
Annotations are optional. Generics, traits and requirements like “must be ordered” are inferred from the body and checked.
Rill has ML's types, Go's concurrency and SDL's state machines, compiled straight to native code. No virtual machine, no garbage collector, no runtime to install.
Type inference, algebraic data types, exhaustive pattern matching, lightweight threads over typed channels, processes with mailboxes drawn as the telephone exchanges drew them — and the compiler decides where every allocation is freed, so nothing pauses.
See the language The reference Try the model editor Read the bookAlcedo atthis · H. von Kittlitz, 1832
The same program, compiled. Bars are the true ratio — not adjusted to fit. The 51 KB includes the crash reporter: a fault names the function and the line.
A small language, read in an afternoon. What follows is most of the syntax; traits, records, machines and a few conveniences are the rest, and the reference has all of it, chapter by chapter, with the programs that show each construct.
A type is a list of the shapes a value can have. match tests the
shape and binds the pieces in one step, and the compiler checks that you covered
every case.
type Shape Circle(r: Float) Rect(w: Float, h: Float) fn area(s) = match s Circle(r) -> 3.14159 * r * r Rect(w, h) -> w * h fn main() = shapes = Cons(Circle(1.0), Cons(Rect(2.0, 3.0), Nil)) println(sum(map(shapes, area)))
$ rill run shapes.rill 9.14159
Leave out a case and the program does not compile — and the compiler names the one you forgot:
colour.rill: type error at line 6: match is not exhaustive; no arm matches `Blue`
A call in tail position compiles to a jump, so this runs in constant stack
at any size. for x in xs and while c with (acc) = init
exist for the reader, and are this underneath.
fn sum_to(n) = go(n, 0) fn go(i, acc) = if i == 0 then acc else go(i - 1, acc + i)
go(20000000, 0) → 1.3 MB resident
x |> f(a) means f(x, a), so a calculation reads
in the order it happens instead of inside-out.
range(1, 10) |> filter(\x -> x % 2 == 0) |> map(\x -> x * x) |> sum
220
Many lightweight threads over typed channels, taken from Go. One of them is a
strand — a strand of a rope, of which a program is many. A hundred thousand
of them cost about 65 MB and start in microseconds; with --parallel
they spread across cores, and the compiler refuses a program in which two of them
could write the same storage.
fn worker(jobs, results) = n = recv(jobs) send(results, n * n) worker(jobs, results) # a loop that never ends fn main() = jobs = channel() results = channel() spawn worker(jobs, results) send(jobs, 7) println(recv(results))
49
The compiler works out where each value's last use is and inserts the release there, following ownership rules. Nothing runs periodically; a value is freed at the instruction after it dies, and a box that dies where a new one is made is reused in place. A reference ring — the one thing counting cannot free — is refused at compile time, where it is written. Set one variable and the runtime tells you what was still alive at exit:
$ RILL_DEBUG_ALLOC=1 ./program 100000 live allocations: 0
A machine is a process in the sense the telephone
exchanges gave the word — a state machine with a mailbox, one strand, one signal at
a time — and a system is the block diagram between the kinds of
machine and the environment, which the compiler checks against the code.
system exchange env -> line: OffHook, Digit, OnHook, Answer, Report line -> env: Status line <-> line: Ring, Answer, OnHook machine line(me: Chan(Mail(Signal)), log: Chan(Str)) state Idle OffHook -> Dialling(Nil) Ring -> Ringing(sender) state Dialling(digits) Digit(n) if n >= 0 -> Dialling(Cons(n, digits)) after 50 -> send(log, "gave up dialling") Idle state Ringing(caller) Answer -> output(caller, Answer) Talking(caller)
Timers, save, priority, procedures with states of
their own, and a signal no arm names is consumed and the state kept — the rules
SDL had. A run traced with RILL_TRACE=1 is a sequence chart
(rill msc); on the virtual clock a night of retransmissions takes no
time; rill diagram draws the system and each machine in SDL's own
symbols; and a signal a machine sends that no route carries is a compile error
naming both.
The editor at rillsdl.baltavista.com draws all of this in the browser: click a state or an input to build it in a form, run a scenario and watch the chart grow while the machines light the state they are in. The code stays the model — every edit is written back through the formatter — and the programs run in the compiler's sandbox, so try anything.
Annotations are optional. Generics, traits and requirements like “must be ordered” are inferred from the body and checked.
Nested patterns, and a checker that names the case you forgot rather than a runtime default branch.
Absence is Option, failure is Result. Nothing unwinds the stack from under you.
One specialized copy per concrete type. Nothing is boxed and trait dispatch is resolved at compile time.
extern "m" fn sqrt(x: Float) -> Float and it links. Opaque pointers, explicit string bridging, exact C widths, inline asm.
rill fmt gives every program exactly one formatting, which is what makes it predictable to read and diff.
Forty-three modules in Rill: JSON, YAML, CSV, deflate, HTTP and TLS, WebSockets, a regex, a B+ tree store, SHA-2, big integers, decimals, UUIDs, a grid that keeps up with NumPy — every function with an example the test suite runs.
rill test runs every test_ function in a process of its own; rill doc reads the comments off the file; rill lsp answers any editor, with a VS Code extension.
--parallel spreads strands across cores and refuses shared writes; --target wasm32-wasip1 runs in a browser; --sandbox refuses a program that would reach past the process.
Rill 0.13.0 on an Apple M4, 16 September 2026; Go 1.23, OCaml 5.5;
outputs verified identical across the languages, time the median of 25 runs.
Reproduce with python3 benchmarks/run.py.
| Benchmark | Rill | Go | OCaml | Rill RSS | Go RSS | OCaml RSS |
|---|---|---|---|---|---|---|
| fib(35) | 15.0 ms | 20.9 ms | 22.0 ms | 1.3 M | 3.6 M | 2.1 M |
| binary trees | 16.3 ms | 55.1 ms | 57.3 ms | 13.3 M | 14.6 M | 18.3 M |
| 100k-thread ring | 12.7 ms | 94.4 ms | 64.8 ms | 64.7 M | 269.3 M | 103.0 M |
| 100M-iteration loop | 121.0 ms | 264.6 ms | 284.2 ms | 1.3 M | 3.6 M | 2.1 M |
Four microbenchmarks are not a workload. The first row is really “both of these are LLVM-quality code generation”; the rows that carry information are the third, where the concurrency implementation differs by five times, and the second, where a compiler inserting frees beats two mature garbage collectors on time and memory. Against C (measured 13 September, before this release) — twenty programs, each written once in Rill and once in C to the same algorithm and layout — Rill wins eleven, is within run-to-run noise on nine, and is behind on none; on disk and sockets it is ahead of Go on the four stream programs and within a few percent on the round trips, which are the kernel's. Eight independent computations on eight strands scale1.0× · 1.9× · 3.1×on one, two and four workers. The reasons, step by step, are inbenchmarks/.
Some of these are choices and some are a version number. Both are listed.
{ p | x = 1 }. The exceptions are storage: Buf, Map and a string builder, for C and for hot tables.Result and Option instead, with ? to pass a failure up.map is a function on lists, not a method of a functor.#s counts them; the char_ functions and the text library walk it by code point when that is the question.Rill is version 0.13. It is a good language to learn these ideas in and a good one to model a protocol in; whether it is one to run a business on is a question this page would rather leave open than answer for you.
0.13.0 — the model editor. The release where the models got an editor in the browser, with its server written in Rill on the standard library. The page shows a file's system as the block diagram, channels routed as SDL drew them, and each machine as a process in SDL's symbols, beside its coloured code, every symbol a click from its line.
Since then:
Programs that are not benchmarks, written in Rill and nothing else.
A walkable low-poly valley at dusk, computed by Rill and rendered through WebGPU:
the terrain, every pine, crystal and grass tuft, the walking camera and the shadow
matrices are worked out in world.rill compiled to WebAssembly — the
whole valley rebuilt in about four milliseconds, denser or sparser until the
display's frame budget is spent. Runs in the browser; ?join shares the
valley with whoever else is there, over a server that is Rill too.
A first-person maze lit by torches, native on Apple silicon, with no C written
anywhere — no shim library, no generated bindings, no build script: Metal, the
window and the sound reached through extern declarations alone. A 405 KB
binary and its assets, nothing to install.
The SDL editor's server is a Rill program on the standard library — HTTP, JSON, processes, paths — that runs the compiler for the page and sandboxes what it runs. So is the server the valley's visitors share a world through, and the packer that puts the valley's assets into one file.
Functional Programming with Rill — 86 pages, seventeen chapters.
It starts with what functional programming is, at length and before any Rill appears: expressions instead of commands, why assignment hides information, purity, higher-order functions, recursion and tail calls, and the types that make illegal states unrepresentable. Then the language, then algorithms — sorting, trees, a recursive-descent parser, graphs and flood fill, dynamic programming — each one put beside the same algorithm in Python, Java or Go. It ends underneath: memory without a collector, strands and channels, and machines — a protocol modelled, traced and drawn.
Every program in it was compiled and run by the compiler in the repository, and the output printed in the book is the output it produced. Where a listing shows a compiler error, that error came from running the broken program.
Download the PDF Back to the languageRill 0.13.0, packaged: the compiler, the runtimes, the standard
library, the examples and the reference, in one directory. Needs a
C linker on the PATH — the Xcode command-line tools on a Mac, gcc or
clang on Linux — and nothing else.
rill-0.13.0-macos-arm64.tar.gz · macOS 13 or later on M-series. Unsigned: a browser download is quarantined, so either curl -O it or xattr -dr com.apple.quarantine rill after unpacking.
rill-0.13.0-linux-x86_64.tar.gz · glibc 2.36 or later (Debian 12, Ubuntu 22.04 and newer). Needs gcc or clang for the link.
Intel Macs, Linux on ARM and Windows have no build: the compiler is built with a static LLVM, and only these two machines have been built on. Windows and wasm are targets — --target — from either.
curl -O https://rill.baltavista.com/dl/rill-0.13.0-macos-arm64.tar.gz
tar xzf rill-0.13.0-macos-arm64.tar.gz
rill/bin/rill run rill/examples/hello.rill # hello, world
Checksums in SHA256SUMS. The wasm target needs wasm-ld, which comes with LLVM.
rill run program.rill # compile to a temporary binary and run rill build program.rill -o p # keep the binary; --parallel, --sandbox, --target wasm32-wasip1 rill test program.rill # every test_ function, each in its own process rill check program.rill # type-check only, saying everything it finds rill fmt program.rill # canonical formatting rill doc lib -o docs # Markdown from the comments the modules already have rill diagram exchange.rill # the system and the machines, drawn rill repl # definitions persist for the session
The runtime is no_std and links as a static library, which is most
of why the binaries are the size they are.