Koja is a statically typed language for building readable, reliable services. It pairs Ruby-inspired syntax and Erlang-style concurrency with Swift-style value semantics and a Rust-inspired type system. Develop quickly with the interpreter, then ship native LLVM binaries with deterministic memory management and no garbage collector.
For the full language specification, see LANGUAGE.md.
The easiest way to install Koja is with asdf. This
ships the koja compiler and koja-lsp language server for Apple Silicon
and x86-64 Linux.
asdf plugin add koja https://github.com/koja-lang/asdf-koja.git
asdf install koja latest
asdf set --home koja latestPrebuilt tarballs are also attached to every release. For other platforms, or to build from source, see INSTALLING.md.
Write a hello world script and run it:
echo 'IO.puts("hello, world!")' > hello.kojs
koja run hello.kojsThe snippets below are .kojs scripts. Statements run from the top
of the file. Compiled programs (koja.toml projects) instead start
from a type implementing Process. See LANGUAGE.md.
struct User
name: String
end
enum Event
Joined(User)
Left(String)
end
fn describe(event: Event) -> String
match event
Event.Joined(user) -> "#{user.name} joined"
Event.Left(name) -> "#{name} left"
end
end
describe(Event.Joined(User{name: "Ada"})).print()
Assignments produce independent values. Heap-backed storage is shared internally and copied only when one value changes.
struct Config
name: String
end
original = Config{name: "development"}
copy = original
copy.name = "production"
original.name.print() # "development"
copy.name.print() # "production"
fn describe_packet(packet: Binary) -> String
match packet
<<tag::8, length::16, payload: Binary>> ->
"tag #{tag}: #{length} bytes (#{payload.byte_size()} available)"
_ ->
"invalid packet"
end
end
describe_packet(<<1, 5::16, "hello">>).print()
alias Process.Step
alias Process.StopReason
enum CounterMsg
Add(Int)
end
struct Counter
value: Int
end
impl Process<Int, CounterMsg, Int> for Counter
fn start(initial: Int) -> Result<Self, StopReason>
Result.Ok(Counter{value: initial})
end
fn handle(self, msg: CounterMsg, from: Option<ReplyTo<Int>>) -> Step<Self>
match msg
CounterMsg.Add(amount) ->
next = self.value + amount
ReplyTo.reply(from, next)
Step.Continue(Counter{value: next})
end
end
end
counter = spawn Counter.start(40)
counter.cast(CounterMsg.Add(1))
match counter.call(CounterMsg.Add(1), 1000)
Result.Ok(value) -> value.print()
Result.Err(_) -> "counter unavailable".print()
end
Explore more in the runnable language tour, or read the complete language reference.
- Vim -
vim-koja(syntax and indentation) - VS Code -
vscode-koja - Zed -
zed-koja
Any editor with a tree-sitter or LSP client can integrate directly
against the grammar and koja-lsp.
Working on the compiler requires building from source. See INSTALLING.md for toolchain setup (Rust 1.85+, LLVM 18).
Build and run the test suite.
cargo build -p koja-runtime-posix
cargo build --release -p koja-runtime-posix
cargo test --workspace -- --test-threads=4Koja source files can be formatted with the built-in formatter.
koja format --write <file.koja>Compiler formatting and lint checks use Cargo:
cargo fmt --check
cargo clippy --workspaceSee the CI workflow for the complete compiler, language, and standard library test matrix.
Copyright (c) 2026 Henry Popp
This project is MIT licensed. See the LICENSE for details.