import std/io
const {
name: "World"
debug: true
count: 42[i32]
}
std/io:print.blk {
{% if debug %}[DEBUG] {% endif %}Hello, {{ name:s }}!
The answer is {{ count:d }}.
}C-class throughput with none of the ceremony. Koru hides a wild amount of complexity at compile time — so the runtime stays lean.
Measured against C and Rust under one hyperfine protocol.
Phantom obligations track resources and state in the type system. Use-after-close, unhandled branches, and protocol errors are compile errors — not crashes.
Zero runtime cost. The compiler carries the proof.
Tors and their named exits give AI assistants clear contracts. Explicit branches, bounded contexts, and a browser playground make the language teachable and collaborative.
Built for pair-programming with a machine.
Monomorphization means you do not trade power for safety, or speed for ergonomics. Koru is designed so the obvious thing to write is also the fast, correct thing.
The hub. The umbrella. The whole point.
Two interactive playgrounds — no local toolchain required.
Koru's tor-driven architecture creates natural bounded contexts that AI assistants understand intuitively. Each tor declaration is a clear contract with explicit inputs and named exits. Those boundaries provide perfect scoping for code generation—an AI can implement a tor knowing exactly what inputs are available and what exits are expected, without needing to understand the entire codebase. The explicit branching and type safety mean AI-generated code is more likely to be correct on the first try, and errors are caught at compile time rather than runtime.
Koru's flows and subflows can be implemented in isolation and made available for tooling using a partial AST, creating another clear reasoning boundary.
Koru is implemented using AI (Claude Opus 4.1-4.5, Claude Sonnet 4.5) to do mainly AI-first development.
Every abstraction in Koru compiles away before the binary exists. Tors, continuations, phantom obligations—they all disappear at compile time, leaving only the essential machine operations. You get high-level expressiveness without runtime overhead.
import std/io
// One output is an arrow return, not a one-armed branch
tor compute { x: i32, y: i32 } -> i32
compute -> x + y
compute(x: 2, y: 3): sum |> std/io:print.ln("{{ sum:d }}")
// No runtime, no allocator, no dispatch - the tor is gone by link timeThe same patterns work at every level. A tor can invoke other tors, creating flows within flows. This self-similarity makes complex systems comprehensible and composable at any scale.
Koru provides first-class access to program structure at compile-time through special
parameter types. ProgramAST provides the entire program structure, and Source parameters let you access raw
source code as compile-time strings.
This enables powerful metaprogramming: generate boilerplate based on flow structure, validate architectural constraints at compile-time, build custom DSLs on top of Koru, or create sophisticated code transformations. Because these are built into the type system, metaprogramming is type-safe and integrates seamlessly with the rest of the language—no separate macro system or preprocessor needed.
Koru's templating system uses Liquid-style syntax for string interpolation with runtime
variable capture. Use {{ var }} for interpolation and {% if var %}...{% endif %} for conditionals. Format specifiers like {{ name:s }} ensure type-safe output.
import std/io
const {
debug: true
name: "Alice"
count: 42[i32]
}
// Inline templating with runtime conditionals
std/io:print.ln("{% if debug %}[DEBUG] {% endif %}User: {{ name:s }}")
// Multi-line block syntax
std/io:print.blk {
=== Report ===
{% if debug %}Mode: Debug{% endif %}
Count: {{ count:d }}
==============
}The magic: conditionals are evaluated at runtime, but the template compiles down to raw POSIX write calls with inline conditional expressions—zero overhead, zero allocations, no format machinery.
The compiler is written in Koru itself, demonstrating the language's ability to reason about and transform its own constructs. This self-hosting nature ensures the language is powerful enough to build sophisticated tooling.
Compile-time resource safety with semantic auto-dispose. The ! suffix mints an obligation, and a parameter written <!state> is the way to discharge
it. Unclosed files, uncommitted transactions, and leaked connections are tracked by the type
system, not by discipline.
import std/io
// Connect - mints the obligation
pub tor connect { url: string } -> string<connected!>
connect -> url
// Begin - requires a live connection, mints a second obligation
pub tor begin { conn: string<connected> } -> string<in-transaction!>
begin -> conn
// Two ways to discharge <in-transaction!>
pub tor commit { tx: string<!in-transaction> }
commit = std/io:print.ln("commit")
pub tor rollback { tx: string<!in-transaction> }
rollback = std/io:print.ln("rollback")
// The only way to discharge <connected!>
pub tor disconnect { conn: string<!connected> }
disconnect = std/io:print.ln("disconnect")
connect(url: "postgres://localhost/app"): conn
|> begin(conn): tx
|> commit(tx)
|> disconnect(conn)Abandon that transaction and the compiler refuses: commit and rollback both discharge it, so
it will not guess which one you meant. Where exactly one tor can discharge an obligation— disconnect, here—it inserts the
call for you. All of it at zero runtime cost.
Continuations chain naturally with the |> operator. Each tor can exit through several named branches, and the type system ensures all
of them are handled—like algebraic effects with explicit control flow. Flows compose into subflows,
creating a free monad where complexity emerges from simple, composable chains.
import std/io
tor fetch { url: string }
| ok string
| failed string
tor parse { body: string }
| ast string
| bad string
fetch => ok "1 + 2"
parse => ast body
// Exits nest: each arm opens the next tor's exits underneath it
fetch(url: "https://korulang.org")
| ok body |> parse(body)
| ast tree |> std/io:print.ln("parsed {{ tree:s }}")
| bad err |> std/io:print.ln("bad: {{ err:s }}")
| failed err |> std/io:print.ln("failed: {{ err:s }}")The call sites use punning: parse(body) means parse(body: body) when the binding
already carries the parameter's name. Writing the label out anyway is a compile error, not
a style preference.
A tor declares its named exits upfront. When you call it, you must handle every one,
making control flow visible and traceable. No hidden exceptions, no unhandled cases. A tor
with a single output skips the ceremony entirely and returns through -> T instead.
import std/io
// A tor declares its exits up front
tor parse { input: string }
| ast i32 // the success exit
| message string // the failure exit
parse => ast 42
// Every exit must be handled - drop an arm and it will not compile
parse(input: "1 + 2")
| ast a |> std/io:print.ln("parsed {{ a:d }}")
| message m |> std/io:print.ln("error: {{ m:s }}")Transform how you think about problems by elevating concepts into the type system and tor space. Validation, state machines, workflows—they become first-class language constructs rather than patterns buried in code.
With semantic space lifting you can target any existing C-library and remove defensive coding, knowing that errors will be caught at compile-time.
Koru tracks purity transitively—if a tor calls an impure tor, it becomes impure automatically. Pure tors guarantee no side effects and can be aggressively optimized (memoized, reordered, or eliminated). You always know exactly which parts of your code have side effects, enabling fearless refactoring and clearer reasoning. The compiler tracks purity across the entire call graph, so you can see at a glance whether a tor performs I/O, modifies state, or stays pure.
Observe and intercept tors without modifying their flow. Attach logging, metrics,
auditing, tracing, or debugging behavior transparently. Taps compose, and widening the
pattern to tap(* -> *) instruments the
whole program at once.
import std/io
import std/taps
tor compute { x: i32 } -> i32
compute -> x * 2
// Observe every transition out of compute - compute itself is untouched
tap(compute -> *)
| Profile p |> std/io:print.ln("tapped {{ p.source:s }}")
compute(x: 21): r |> std/io:print.ln("{{ r:d }}")Koru aims to, and mostly succeeds in, matching hand-written C on apples-to-apples comparisons, often landing inside measurement noise. Koru takes performance seriously. The main benefits come from abstractions made possible by continuations, like taps, that don't really have a comparison in other languages but typically outperform the same use case hand-rolled. And where hand-written machine-level code still wins, it is one escape hatch away.
Koru used to be described as a layer on top of a host language. It has outgrown that. The
standard library, the collections, the store, the kernels, most of the compiler—all of it
is written in Koru proper, and a pure .k file never mentions a host at all.
What remains is an escape hatch, and it earns its keep the way unsafe earns its keep in Rust:
rarely, deliberately, and at a seam you can point at. A proc is where host code lives—an intrinsic,
a syscall, an existing C library you have no intention of rewriting. Everything around it stays
Koru, and the tor signature keeps the seam typed.
import std/io
// Almost everything is written in Koru proper
tor double { n: u64 } -> u64
double -> n * 2
// ...until you need the metal. A proc is the seam, and the only
// place host code appears.
tor popcount { n: u64 } -> u32
proc popcount|zig { return @popCount(n); }
double(n: 21): d |> popcount(n: d): bits |> std/io:print.ln("{{ bits:d }}")The host is a target, not a foundation. Zig is the one we emit today because of its compilation speed and its FFI reach; JavaScript is the second, which is how the playground runs the whole pipeline in your browser. The parser and most of the toolchain are host-agnostic, so the list is expected to grow. You are not locked into a package manager, a build system, or a toolchain either—work with whatever ecosystem you already have.
In Koru, compilation is just another tor—specifically compiler.coordinate. The entire compiler pipeline (analysis, pass planning, optimization, validation) is
defined as a subflow that your program can completely override. Want domain-specific
optimizations? Replace the default coordinator. Need to inject custom passes? Override
individual compilation tors. Every compilation phase is user-configurable. Koru
compiletime is host-language runtime, so you can do anything the host language can do
at compiletime.
The standard library provides compiler.coordinate.default so you always have a working baseline, but you can wrap it, extend it, or replace it entirely.
Game engines can optimize for consistent frame timing. Trading systems can inline aggressively
for minimal latency. Web servers can optimize for code size. Each program defines how it should
be compiled—there's no separate "compiler plugin API" because the compiler is just tors, and
your program defines the subflows.