Getting Started
Kora is a small, statically typed programming language. If you have written any C-like language you will feel at home. Programs are made of functions, structs, and modules, with a few modern conveniences layered on top. Types are inferred, generics are monomorphized, optionals replace null, and a garbage collector means you never free memory by hand.
One compiler serves three runtimes. The LLVM backend produces native executables, the JavaScript backend produces programs for Node, and the browser playground runs the compiler itself as WebAssembly with an extra canvas API for graphics. A program behaves the same on all three.
Here is what Kora looks like:
import "std/io";import "std/conv";
int fib(n: int) { if (n < 2) { return n; } return fib(n - 1) + fib(n - 2);}
int main() { for (let i = 0; i < 10; i = i + 1) { io.print(conv.to_string::<int>(fib(i))); } return 0;}This guide starts from nothing and works up through the language one feature at a time. If you would rather see everything on one page, read Kora in 5 Minutes.
Try Kora in the browser
Section titled “Try Kora in the browser”The fastest way to try Kora is the playground: nothing to install, and the file menu has example programs ranging from a word counter up to Snake, Tetris, and Doom. The playground also adds drawing and input functions for building small games.
Install the toolchain
Section titled “Install the toolchain”To build native executables you need the compiler. Building it requires Rust (stable), LLVM 21, and a C compiler for linking.
git clone https://github.com/gnikdroy/kora.gitcd koracargo build --releaseThis produces the kora compiler at target/release/kora. If the build cannot
find LLVM, point LLVM_SYS_211_PREFIX at your LLVM 21 installation.
Compile and run
Section titled “Compile and run”kora takes a source file and produces a native executable (named after the
input unless you pass -o):
kora hello.kora -o hello./helloPass --emit-js to produce JavaScript instead. It writes hello.js next to
the input, ready to run with Node:
kora hello.kora --emit-jsnode hello.jsOptimization levels are set with -O (0 to 3, s, z, default 2).
Anything after -- is passed to the linker.
If you want to hack on the playground itself, it is the same compiler built
for WebAssembly (frontend and JavaScript backend only, since LLVM cannot
target wasm). It needs wasm-pack:
wasm-pack build --target web --out-name compiler -- --no-default-featurespython -m http.serverYour first program
Section titled “Your first program”import "std/io";
int main() { io.write("Hello, world!\n"); return 0;}Every program starts at main, which returns an int exit code, 0 meaning
success. Notice that the return type comes before the function name. That is
true of every Kora function. import "std/io"; pulls in the standard I/O
module, whose functions are then called as io.write, io.print, and so on.
For a slower walk through this program, see Hello World.
Variables and inference
Section titled “Variables and inference”let declares a variable. The compiler infers the type from the initializer,
or you can write it yourself after a colon:
let x = 42; # inferred as intlet y: real = 3.14; # annotatedlet name = "Kora"; # string
x = x + 1; # plain assignment; x stays an intTypes are checked at compile time: assigning a string to x is an error, not
a surprise at runtime.
Constants
Section titled “Constants”A let at module level, outside any function, declares a constant. The
initializer is any expression built from literals, constants declared above
it, and operators, evaluated at compile time. Assigning to the name is a
compile error. Division by zero is also a compile error.
let WIDTH = 640;let HEIGHT = 480;let AREA = WIDTH * HEIGHT;let TITLE = "kora" + " v1";let ESC = 27 as char;
int rows(cell: int) { return HEIGHT / cell;}A constant is visible anywhere in its module, and other files reach it through
the module name after an import, just like a function: config.WIDTH. A
constant simply names its computed value, so every use is that value. Locals
may shadow it.
The basic types
Section titled “The basic types”Kora has five primitive types:
int: integers. 64-bit on the native backend, and safe up to 2^53 - 1 on the JavaScript backend.real: 64-bit floating point.char: a single byte, written'a','\n','\t'.bool:trueorfalse.string: text. Astringis exactly an array ofchar(bytes), so everything arrays can do, strings can do.
There are no implicit conversions. Convert explicitly with as:
let c = 65 as char; # 'A'let f = 65 as real; # 65.0let n = 3.9 as int; # 3 (the fraction is dropped)Arithmetic (+ - * / %), comparisons (== != < <= > >=), and logic
(&& || !) work as in C. On strings and arrays, == compares contents,
element by element. + concatenates strings:
let greeting = "Hello, " + "Kora";Control flow
Section titled “Control flow”if/else, while, and the C-style for behave as you expect, with break
and continue available in loops. Conditions must be bool. There is no
truthiness.
if (x % 2 == 0) { io.print("even");} else { io.print("odd");}
while (x > 0) { x = x - 1;}
for (let i = 0; i < 10; i = i + 1) { if (i == 3) { continue; } if (i == 7) { break; }}There is also a for-each form, for x | array, which binds each element in
turn:
for word | ["one", "two", "three"] { io.print(word);}Functions
Section titled “Functions”A function is written return type first, then the name, then parameters as
name: type. Use void for no return value.
int add(a: int, b: int) { return a + b;}
void greet(name: string) { io.print("Hello, " + name);}Functions are also values. A function type is written as the return type
followed by the parameter types in parentheses, so int(int) reads as
“a function taking an int and returning an int”:
int square(n: int) { return n * n; }
int apply(f: int(int), x: int) { return f(x); }
let g: int(int) = square;apply(g, 6); # 36Passing behavior around like this is how the std/iter
helpers work. Kora has no closures yet. You pass named, top-level functions.
Arrays
Section titled “Arrays”[...] is an array literal, and new T[n] makes an array of n default-valued
elements. Arrays grow dynamically and carry built-in methods:
let xs = [1, 2, 3];let zeros = new int[10];
xs.push(4); # [1, 2, 3, 4]xs.insert(0, 9); # [9, 1, 2, 3, 4]let last = xs.pop(); # 4; removes itxs.remove(0); # removes index 0let mid = xs.slice(1, 3); # elements [1, 3), a new arrayxs.extend([7, 8]); # append every element of another arraylet n = xs.len();Every index is bounds-checked: xs[99] on a three-element array stops the
program with a clear message rather than reading garbage. And since a string
is an array of char, all of the above works on strings too: s.len(),
s[0], s.slice(1, 3).
Structs and methods
Section titled “Structs and methods”struct groups named fields. Construct values with new, and attach methods
in an impl block, where each method’s first parameter is self.
struct Point { x: int, y: int,}
impl Point { int manhattan(self) { return math.abs(self.x) + math.abs(self.y); }
void move_by(self, dx: int, dy: int) { self.x = self.x + dx; self.y = self.y + dy; }}
let p = new Point{ x: 3, y: -4 };p.move_by(1, 1);p.manhattan(); # 7Structs and arrays are reference types. Assigning one to a new variable or
passing it to a function shares the same underlying value, which is why
move_by can update the caller’s point. The primitives (int, real, char,
bool) are copied by value.
Optionals
Section titled “Optionals”There is no null in Kora. When a value can be absent, its type says so: T? is
“a T, or nothing”. The empty value is written none, and you cannot use a
T? where a T is expected without handling the empty case first.
int? first_even(xs: [int]) { for x | xs { if (x % 2 == 0) { return x; } } return none;}
let found = first_even([1, 3, 4, 7]);if (found != none) { let n = found!; # unwrap: found is known to be present}The ! operator force-unwraps an optional. If the value is none, the program
panics. So unwrap only after checking, or when absence would be a bug anyway.
Much of the standard library returns optionals for things that can legitimately
fail: io.input() at end of input, conv.string_to_int on a malformed string,
fs.open on a missing file.
Generics
Section titled “Generics”Structs, impls, and functions take type parameters in <...>. Supply type
arguments with ::<...> (the “turbofish”) on calls, or directly on new:
struct pair<A, B> { first: A, second: B,}
T id<T>(x: T) { return x; }
let p = new pair<int, string>{ first: id::<int>(1), second: "two" };Every instantiation is monomorphized. The compiler generates concrete code per
type, so generics cost nothing at runtime. The generic containers in
std/collections are built this way.
A generic body sometimes needs different code per type. A compile-time if
compares a type parameter against a type. It is written without parentheses,
and the untaken branch is discarded at instantiation, before it is ever
checked:
string describe<T>(v: T) { if T == int { return "int " + conv.to_string::<int>(v); } else if T == string { return v; } else { return v.__str__(); }}A discarded branch may contain code that would not compile for the current
type. That is the point: describe::<int> never sees v.__str__(), and
the final else only has to make sense for the types that reach it.
Modules
Section titled “Modules”Programs split across files. import takes a path. The module is named after
the last path segment, or a name you choose:
import "std/math"; # used as math.abs(...)import "std/math" m; # or renamed: m.abs(...)import "geometry.kora"; # your own file, used as geometry.area(...)The standard library is itself a set of Kora modules covering I/O, strings, math, files, networking, threads, collections, and more. Import what you need. Nothing is loaded implicitly.
When things go wrong
Section titled “When things go wrong”Kora fails loudly, not silently. Indexing out of bounds, dividing by zero,
pop() on an empty array, and force-unwrapping none all stop the program
with a clear message. There is no undefined behavior and no corrupted memory.
The garbage collector reclaims unused structs and arrays automatically, so
there is no free and no use-after-free.
Where to go next
Section titled “Where to go next”- Kora in 5 Minutes: the whole language on one page.
- Standard Library: every module, with signatures and examples.
- Playground Functions: graphics and input for browser games.
- Architecture: how the compiler itself is put together.