Lake

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.

RuntimeTimeRelative
Lake295 µs1.0×
Rust (tokio)1194 µs4.0×
C++ (coroutines)1677 µs5.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 | sh

Then 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.


© 2026 Lake authors. Documentation on this site is licensed CC BY-SA 4.0; the compiler is MIT.