No Runtime: Why Koru Compiles Away the Abstraction Layer
No Runtime: Why Koru Compiles Away the Abstraction Layer
The premise: Most programming languages hide a runtime between your code and the machine. Koru doesn’t. Here’s what happens when you eliminate that layer entirely.
What “No Runtime” Actually Means
When you run a typical program, you’re actually running multiple layers:
Your Code → Language Runtime → Operating System → Hardware Java: Your bytecode → JVM interpreter/JIT → OS → CPU
Python: Your script → Python interpreter → OS → CPU
JavaScript: Your JS → V8 engine → OS → CPU
C#: Your IL → .NET runtime → OS → CPU
Koru: Your code → Zig compiler → Native machine code → CPU
There’s no Koru runtime. There’s no hidden allocator, no garbage collector, no virtual machine, no interpreter.
The Zig Foundation
Zig was designed from day one to be “runtime-less”:
- No garbage collector - Memory management is explicit
- No hidden allocations - Every allocation is visible in your code
- No runtime type information - Types exist at compile time only
- No exception handling runtime - Errors are explicit via error unions
- No hidden thread scheduler - Concurrency is explicit
When you compile Zig, you get machine code that does exactly what you wrote, nothing more.
Koru builds on this foundation. We don’t add a runtime layer on top of Zig - we compile to Zig and let Zig do what it does best.
What This Means for Event Continuations
Here’s where it gets interesting. Most event systems need a runtime:
# Python needs an event loop runtime
async def handle_event():
await some_async_operation()
# The runtime schedules this, handles the await, etc. // Java needs a virtual machine runtime
CompletableFuture.supplyAsync(() -> {
// The JVM manages threads, scheduling, etc.
return result;
}); Koru event continuations compile to direct machine code. Here’s a real example from Koru’s test suite.
// Koru source: labels and jumps for looping
~event count { current: i32, target: i32 }
| next { value: i32, target: i32 }
| done { final: i32 }
~proc count {
std.debug.print("Count: {}\n", .{current});
if (current < target) {
return .{ .next = .{ .value = current + 1, .target = target } };
} else {
return .{ .done = .{ .final = current } };
}
}
// Flow using labels and jumps to loop
~#loop count(current: 1, target: 5)
| next n |> @loop(current: n.value, target: n.target)
| done d |> _ This compiles to actual Zig code (no runtime, just native code):
// Generated Zig - note the label transformation!
pub const count_event = struct {
pub const Input = struct { current: i32, target: i32 };
pub const Output = union(enum) {
next: struct { value: i32, target: i32 },
done: struct { final: i32 },
};
pub fn handler(__koru_event_input: Input) Output {
const current = __koru_event_input.current;
const target = __koru_event_input.target;
std.debug.print("Count: {}\n", .{current});
if (current < target) {
return .{ .next = .{ .value = current + 1, .target = target } };
} else {
return .{ .done = .{ .final = current } };
}
}
};
// The label becomes a while loop!
pub fn flow0() void {
var loop_current: i32 = 1;
var loop_target: i32 = 5;
var result_0 = main_module.count_event.handler(.{ .current = loop_current, .target = loop_target });
loop: while (result_0 == .next) {
const n = result_0.next;
loop_current = n.value;
loop_target = n.target;
result_0 = main_module.count_event.handler(.{ .current = loop_current, .target = loop_target });
continue :loop;
}
const d = result_0.done;
_ = &d;
} Look what happened:
- The
~#looplabel became awhileloop - The
@loopjump becamecontinue :loop - Event continuations became union types and function calls
- No runtime dispatcher, no event loop, no scheduler
This is zero-cost abstraction in action. The elegant Koru syntax compiles to optimal native code.
The Performance Implications
Zero-Cost Abstractions
When we say “zero-cost abstractions,” we mean it literally:
~event calculate { n: u32 }
| result { value: u32 }
~proc calculate {
return .{ .result = .{ .value = n * 2 } };
}
~calculate(n: 42)
| result r |> print(r.value) This compiles to machine code equivalent to:
uint32_t result = 42 * 2;
printf("%u
", result); The event syntax disappears entirely at runtime. There’s no event object allocation, no union handling, no dispatch overhead.
Predictable Performance
With no runtime, performance is predictable:
- No GC pauses - There’s no garbage collector to stop the world
- No JIT warmup - Code runs at full speed from the first instruction
- No hidden allocations - Memory usage is exactly what you write
- No scheduler overhead - No runtime deciding when your code runs
This makes Koru suitable for systems programming where predictable performance matters:
- Embedded systems - No hidden memory usage or timing variations
- Real-time applications - No GC pauses or scheduler interference
- High-frequency trading - No JIT warmup or allocation surprises
- Game engines - No frame drops from runtime activity
What You Give Up (At The Language Level)
Eliminating the runtime means giving up some language-level conveniences, but many of these can be implemented as libraries:
No Built-in Dynamic Loading
You can’t load Koru code at runtime and execute it… but:
- Zig has excellent support for shared libraries (.so, .dll, .dylib)
- A Koru standard library could easily provide
@loadSharedLibrary()and@callFunction() - This would be a library feature, not a language feature
- Koru’s compile-time metaprogramming could even generate the bindings automatically
No Runtime Reflection
You can’t inspect types or event structures at runtime… but:
- Zig has
@typeInfo()for compile-time reflection - Koru could generate runtime type descriptors at compile time
- You could serialize event structures and reconstruct them
- Again, this would be a library pattern, not a language runtime
No Hot Code Swapping
You can’t replace functions while the program is running… but:
- With shared libraries, you could unload/reload them
- Event continuation graphs could be serialized and reloaded
- The “hot swapping” would be explicit library calls, not automatic runtime behavior
No Garbage Collection
You manage memory explicitly, just like in C or Zig… but:
- Zig has
std.heap.ArenaAllocatorfor scoped memory management - A Koru library could provide RAII patterns using event continuations
- You could build reference counting as a library pattern
The Key Distinction
Traditional languages: These features are built into the runtime:
- Python’s dynamic loading is part of the interpreter
- Java’s reflection is built into the JVM
- C#‘s hot swapping is part of the CLR
Koru: These features would be explicit library calls:
@loadSharedLibrary("plugin.so")- explicit, visible@reflectType(MyEvent)- compile-time generated, runtime used@reloadPlugin("plugin.so")- explicit reload, not magic
What You Gain
In exchange for explicitness, you get:
- Predictable performance - No hidden runtime costs
- Minimal memory usage - No runtime overhead
- Fast startup - No runtime to initialize
- Small binaries - Only the code you actually need
- Easy debugging - What you write is what runs
- Composability - Libraries can be swapped, mixed, and matched
The Library Ecosystem
The power of Koru’s approach is that the standard library becomes the runtime:
- Need hot reloading? There’s a library for that
- Need reflection? There’s a library for that
- Need dynamic loading? There’s a library for that
But these are explicit choices, not hidden runtime features. You pay for what you use, and only what you use.
This is the opposite of languages like Java or Python, where you pay the runtime cost even if you don’t use all the features.
The Philosophy
This reflects a deliberate design choice: explicit over implicit.
Instead of hiding complexity behind a runtime, Koru makes it explicit:
- Memory management is explicit
- Error handling is explicit
- Concurrency is explicit
- Control flow is explicit
The compiler handles the complex parts (event continuation optimization, tap fusion, etc.) but the runtime stays minimal.
This is the opposite of languages like Python or JavaScript, which hide complexity behind increasingly complex runtimes.
Real-World Impact
Embedded Systems
A microcontroller with 64KB of RAM can run Koru code. There’s no runtime that needs 512KB just to start up.
Game Development
Game loops run at consistent frame rates. No GC pauses, no JIT compilation stutters.
Systems Programming
OS components, device drivers, and system utilities can be written in Koru without competing with the system for resources.
Scientific Computing
Number-crunching code runs as fast as hand-optimized C, with the safety of event continuations.
The Readability Revolution
One of Koru’s most underrated advantages is readability. Koru code often reads like a cleaner, more structured version of Python:
# Python: imperative style
def process_data(data):
results = []
for item in data:
if item.is_valid():
result = transform(item)
results.append(result)
else:
log_error("Invalid item")
return results # Koru: declarative event style
~event process { items: []Item }
| processed { results: []Result }
| error { msg: []const u8 }
~proc process {
for (items) |item| {
if (item.is_valid()) {
// Continue processing
} else {
return .{ .error = .{ .msg = "Invalid item" } };
}
}
return .{ .processed = .{ .results = results } };
} But the real magic happens as we lift more C/Zig libraries into Koru’s semantic space. Instead of exposing Zig’s complexity, we capture the patterns in the standard library:
// File operations in Koru - no Zig complexity visible
~event read_file { path: []const u8 }
| success { contents: []const u8 }
| error { msg: []const u8 }
~event write_file { path: []const u8, contents: []const u8 }
| success {}
| error { msg: []const u8 }
// High-level file operations
~read_file(path: "config.txt")
| success c |> parse_config(c.contents)
| error e |> show_error("Failed to read config: {s}", .{e.msg})
// Network operations - also captured in semantic space
~event http_get { url: []const u8 }
| success { body: []const u8, status: u16 }
| error { msg: []const u8 }
~http_get(url: "https://api.example.com/data")
| success r |> process_api_data(r.body, r.status)
| error e |> handle_network_error(e.msg) The standard library handles all the Zig complexity:
- Memory management
- Error handling patterns
- System call wrappers
- Resource cleanup
You just work with semantic flows that describe what you want to accomplish.
From Beginner to Expert
Koru scales across skill levels:
Beginners: Write high-level event flows without worrying about memory management or error handling boilerplate
Intermediate: Use event continuations for complex control flow that would be spaghetti in other languages
Experts: Wrap existing C/Zig libraries, create semantic abstractions, build domain-specific languages
The Five Pillars of Koru
Event taps are just one of Koru’s major features:
- Event Continuations - Exhaustive, type-safe control flow
- Event Taps - Zero-cost observability and side effects
- Metacircular Compilation - Programs that transform themselves
- Phantom Types - Compile-time state machine encoding
- Zero-Cost Abstractions - High-level code with native performance
Each of these features is general-purpose and composable. They work together for everything from simple scripts to complex systems.
The Trade-Off Space
Different languages make different trade-offs:
| Language | Runtime | Performance | Productivity | Use Case |
|---|---|---|---|---|
| Python | Heavy interpreter | Slow | High | Scripting, prototyping, web development |
| Java | JVM with JIT | Medium | High | Enterprise applications, Android |
| JavaScript | V8 engine | Medium | High | Web applications, Node.js |
| Rust | Minimal runtime | Fast | Medium-High | Systems programming, web assembly |
| Zig | No runtime | Very Fast | Medium | Systems programming, embedded |
| Koru | No runtime | Very Fast | High | General purpose: from scripts to systems |
Koru sits in the same performance space as Rust and Zig, but with event continuations for complex control flow.
The Future
As we continue developing Koru, the “no runtime” principle stays fundamental:
- Event taps compile to inline function calls
- Phantom types compile to zero-cost constraints
- Metacircular compilation happens at build time
- Distributed continuations serialize as data, not runtime objects
Even advanced features like distributed event continuations work without a runtime - they serialize the continuation graph as data and reconstruct it on the other side.
The Bottom Line
Koru’s “no runtime” approach isn’t about being minimalist for minimalism’s sake. It’s about giving you:
- Predictable performance - Your code runs exactly as fast as the machine allows
- Explicit control - You decide when memory is allocated, when threads run, when errors happen
- Zero-cost abstractions - Event continuations don’t cost anything at runtime
- Simple deployment - No runtime to install, configure, or tune
The complexity is in the compiler, not the runtime. The compiler does the hard work so your runtime code can be simple and fast.
This is the promise of Koru: event-driven programming without the runtime penalty.
Published November 23, 2025