Lesson 3.1
Why a Runtime Library?
The small C library that lives inside every Monk binary. What it does and why it exists.
The code you didn't write.
When you write show("hello") in Monk, the compiler turns it into C. But the C code can't just call printf directly. Monk has dynamic types, tagged unions, deep copying, error handling. None of that exists in C out of the box.
Somebody has to provide those capabilities. That somebody is the runtime library -- a small piece of C code that gets compiled alongside every Monk program. Your program calls functions from the runtime. The runtime does the heavy lifting.
Think of it like the engine in a car. You turn the steering wheel (your Monk code), but the engine (the runtime) is doing the actual work under the hood. You didn't build the engine. It was already there when you got the car.
Why Monk can't skip this.
Three problems make a runtime necessary:
Dynamic types need a container.
Monk variables can hold integers, strings, arrays, records -- all different shapes. C has no such container. The runtime provides a tagged union that can hold any Monk value.
Built-in functions need implementations.
When a Monk program calls show(), append(), or split(), the code generator emits a call to a C function. That function has to exist somewhere. The runtime is where it lives.
Error handling needs setjmp/longjmp.
Monk's guard/against/throw system is implemented with C's checkpoint mechanism. The runtime sets up and manages those checkpoints.
Runtime vs. code generator: who does what?
The runtime is passive. It doesn't know about Monk syntax, AST nodes, or the compilation pipeline. It just provides functions that the generated C code calls. The code generator is the one that knows how to translate Monk into the right sequence of runtime calls.
How the runtime gets into your binary.
When you run monk build hello.monk, here's what happens:
The Go compiler reads hello.monk, lexes it, parses it, and generates C source code.
The generated C includes #include "runtime.h" at the top.
The C compiler (gcc/clang) compiles both your generated code and runtime.c together.
The linker produces a single native binary. The runtime is baked in.
The entire runtime is roughly 2-5 KB of C. Smaller than a JPEG. Every Monk binary carries it, and the cost is negligible.
The runtime is built BEFORE the code generator. Generated C calls runtime functions. If the runtime doesn't exist, there's nothing to call. That's why this is Phase 3 and code generation is Phase 4.
What's inside.
The runtime is two files: a header and an implementation. Together they provide everything a compiled Monk program needs:
MonkValue
A tagged union representing every possible Monk value -- integers, floats, strings, booleans, none, arrays, records, functions.
Value operations
Deep copy, free, compare, arithmetic, truthiness. The fundamental operations on MonkValues.
40+ built-in functions
Output, type conversion, math, string operations, array operations, file I/O, environment access.
Error handling
setjmp/longjmp infrastructure for guard/against/throw.
Pure C. No C++ features, no external dependencies beyond the C standard library. Maximum portability.
Key takeaways
A runtime library is the code your program needs at execution time but you didn't write.
Monk needs a runtime because C doesn't have dynamic types, built-in functions, or structured error handling.
The runtime is compiled together with the generated C code into a single binary. About 2-5 KB.
The runtime must exist before the code generator, because generated code calls runtime functions.