Keal compiles to C11, so what you know about the machine still holds — layout, the ABI, the linker. What changes is that bounds are checked, integers do not wrap silently, memory is freed for you, and the type system knows which pointers can be null.
| Concept | C | Keal |
|---|---|---|
| Variable | int x = 1; | var x = 1 |
| Constant | const int x = 1; | val x = 1 |
| Struct | struct P { double x; }; | record P(val x: Float) |
| Pointer that may be null | char *s = NULL; | val s: String? = null |
| Array | int a[] = {1, 2}; | val a = [1, 2] |
| Loop | for (int i=0;i<3;i++) | for (i in 0..3) |
| Switch | switch (n) { case 0: … break; } | when (n) { 0 -> … } |
| Function pointer | int (*f)(int) | val f: (Int) -> Int |
| malloc / free | malloc / free | counted automatically |
| Header | extern int f(int); | extern func f(x: Int): Int = "f" |
| Inline C | — | native """ … """ |
keal emit-c f.keal prints one self-contained translation unit, runtime included. Nothing is hidden, and keal layout prints every struct's offsets and padding.
Integer overflow, division by zero and out-of-bounds indexing panic with a message and a line, on every engine — rather than being undefined.
A String crossing to C says borrow or own; records cross by copy as headerless mirror structs. The checker refuses anything whose lifetime it cannot state.
Counting frees everything but a cycle. Write weak on the back edge and it dies like anything else.