Lake
A small, compiled programming language built around processes, not threads. Every function is a cooperative machine; the scheduler decides when to yield.
§1The idea
A Lake program is a collection of machines. Each machine has one or more branches — patterns matched against the arguments at the call site. A branch may finish, change the machine's state with self(...), or spawn another machine with spawn(...). There is no syntactic distinction between synchronous and asynchronous code; everything runs under the same cooperative scheduler.
The compiler targets native code through Cranelift. Lake does not link to libc; it issues syscalls directly. A typical program weighs six to ten kilobytes after stripping.
§2A small program
The following spawns three concurrent counters. Each one decrements its argument until it reaches zero, then writes a line.
@rt(rt_write)
counter is {
0 i64 -> { rt_write(1 "done\n" 5) }
n i64 -> { self(n-1) }
}
main is {
_ -> {
counter(5)
counter(3)
counter(7)
}
}The two branches of counter are dispatched on a literal guard. 0 i64 matches the terminating case; n i64 matches everything else and recurses by means of a state transition. Three independent processes run on a single thread. The scheduler interleaves them up to 256 reductions at a time before yielding to the next.
From the command line:
$ lakec counter.lake
Compiling counter.lake [opt=2]
✓ built 59ms
✓ linked 22ms
→ build/counter
size: 9.3 KB
$ ./build/counter
done
done
done§3Why processes
Threads are heavy and preemptive. Async/await colours every function and forces the programmer to carry a runtime through type signatures. Lake takes a third path, well-trodden by Erlang, Smalltalk, and the actor literature: everything is a process. A process is cheap, isolated, movable, pausable, discardable.
The cost of spawning a Lake process is one allocation and a queue insertion. The cost of switching to one is a function-pointer call.
§4A type system, not just a scheduler
Around the process model Lake is a full statically-typed language. It has records and enums with exhaustiveness-checked when, generics that monomorphise to zero-cost concrete code, and protocols — interfaces with static dispatch and no vtables. There is no null and there are no exceptions: failure is an Option[T] or Result[T E], and panic aborts with a source location.
Shape is enum {
Circle(i64)
Rect(i64 i64)
}
area is {
s Shape -> ret i64 {
when s {
Circle(r) -> { ret 3 * r * r }
Rect(w h) -> { ret w * h }
}
}
}The standard library — Vec, String, IntMap, SHA-256/HMAC/PBKDF2 — is written in Lake itself. On top of it, the pg package is a pure-Lake PostgreSQL client (v3 protocol, SCRAM auth). Put together you can ship a native, database-backed web service written entirely in Lake — that is exactly what runs at shortener.lake-lang.com.
§5Performance
On concurrent I/O — ten workers each issuing a write syscall — Lake completes in roughly a quarter of the time tokio takes, because it does not pay for futures, wakers, or task queues.
| Runtime | Time | Relative |
|---|---|---|
| Lake | 295 µs | 1.0× |
| Rust (tokio) | 1194 µs | 4.0× |
| C++ (coroutines) | 1677 µs | 5.7× |
On purely CPU-bound workloads Lake is currently slower than its competitors by an architectural margin: every CPS dispatch counts against the reduction quota. We consider this a property, not a defect — see the scheduler section in the guide.
§6Install & status
One command installs the toolchain (lakec, house, the standard library) via lakeup:
curl -fsSL https://lake-lang.com/install.sh | shThen house new app && cd app && house run. The compiler runs on x86-64 Linux and emits self-contained native binaries — no virtual machine, no garbage collector. Lake is in active development and not yet production-ready.
Source lives at github.com/morphqdd. The full guide is at /learn (start with installation); an interactive playground at /play.