Getting Started
What is Krait?
Krait is a fast, memory-safe, compiled systems programming language designed under the
philosophy "Reduce cognitive noise without reducing power." It combines
the clean, indentation-based syntax of Python with the raw execution speed and
compile-time memory safety of C and Rust.
Unlike Python, Krait compiles to native machine code via an LLVM-IR backend — meaning
your code runs at the same speed as hand-written C. Unlike Rust, Krait hides lifetime
annotations and borrow-checker complexity behind an implicit, zero-noise ownership
model.
Installation
You do not need Cargo, Rust, or any build toolchain to install Krait. Install the
pre-compiled binary directly via automated inline scripts:
irm https://raw.githubusercontent.com/skiLLM-Labs/Krait/refs/heads/main/install.ps1 | iex
curl -sL https://raw.githubusercontent.com/skiLLM-Labs/Krait/refs/heads/main/install.bat | cmd
curl -fsSL https://raw.githubusercontent.com/skiLLM-Labs/Krait/refs/heads/main/install.sh | bash
Note: The Krait Interpreter (krait run) is completely
standalone. However, using krait build to generate optimized native
executables requires clang to be installed on your system PATH.
Hello World
Create a file called hello.kr and add the following code:
import io
# Print "Hi" using ASCII codes
putchar(72) # H
putchar(105) # i
putchar(10) # newline
Run it with the interpreter:
Or compile it to a native binary:
$ krait build hello.kr
$ ./hello
Hi
Project Scaffolding
For larger projects, use the built-in scaffolding tool:
$ krait new my_app
$ cd my_app
$ krait run src/main.kr
This creates the following directory structure:
my_app/
├── krait.toml # Package metadata and dependencies
└── src/
└── main.kr # Code entry point
The Interactive REPL
Krait ships with a full interactive shell for fast prototyping. Just run
krait with no arguments:
$ krait
Krait 1.0.0 Interactive Shell
Type 'exit' to quit.
>>> set greeting = "Hello, Krait!"
>>> show greeting
"Hello, Krait!"
Variables & Types
The set Keyword
All variables in Krait are declared using the set keyword. Unlike most
systems languages, you never write explicit type annotations — the compiler infers them
statically at compile time from the value assigned.
# Implicit type inference — clean and noise-free
set age = 21 # Infers Int (64-bit signed)
set price = 19.99 # Infers Float (64-bit)
set name = "Alice" # Infers Str (UTF-8)
set active = true # Infers Bool
Primitive Types
Krait is a statically typed language — every variable's type is fixed at
compile time. The compiler supports five primitive types:
| Type |
Description |
Example |
Int |
64-bit signed integer |
42, 0 - 5 |
Float |
64-bit floating-point number |
3.14, 1.0 |
Str |
UTF-8 encoded string literal |
"hello", "Krait" |
Bool |
Boolean truth value |
true, false |
Void |
Absence of a value |
Implicit in side-effect functions |
Reassignment Rules
Once a variable has been declared with a specific type, its type is permanently fixed.
You can reassign it to a new value of the same type, but attempting to
change its type will trigger a compile-time error:
set age = 21 # Int
set age = 22 # ✓ Valid: still an Int
# set age = "twenty" ← COMPILE ERROR!
# Cannot reassign Int variable to Str.
There is no implicit type coercion in Krait. Integer and float
operations cannot be mixed. This strictness eliminates an entire class of subtle numeric
bugs at compile time.
Functions
The make Keyword
Functions are defined using the make keyword followed by the function name,
parameters in parentheses, and an indented body block. Parameter types and return types
are inferred from usage — you never write type annotations on function signatures.
make calculate_area(width, height)
return width * height
set area = calculate_area(10, 5)
show area # 50
Return Values
Functions return values using the return keyword. If no return
is provided, the function returns Void implicitly. Early returns are fully
supported and are a key pattern for conditional logic.
Recursion
Krait compiles recursive function calls efficiently. The classic Fibonacci example
demonstrates recursion with conditional branching:
make fib(n)
when n < 2
return n
return fib(n - 1) + fib(n - 2)
set result = fib(10)
show result # 55
Real-World Example: Calculator
Here's a more complex example showing multiple functions, conditional dispatch, and
standard library usage:
import math
make calculate(op, a, b)
when op == 1
return a + b
when op == 2
return a - b
when op == 3
return a * b
when op == 4
return a / b
when op == 5
return power(a, b)
when op == 6
return abs(a)
return 0
set r1 = calculate(1, 120, 80)
show r1 # 200
set r2 = calculate(5, 3, 6)
show r2 # 729
Control Flow
Conditionals: when
Krait uses the when keyword for conditional branching. The condition
expression must evaluate to a Bool type. There is
no else or elif keyword — instead, use
sequential when checks or early return statements to structure
your logic.
make sign_of(n)
when n < 0
return 0 - 1
when n > 0
return 1
return 0
show sign_of(42) # 1
show sign_of(0 - 7) # -1
show sign_of(0) # 0
Why no else? Krait's design philosophy prioritizes
explicitness. Sequential when checks make every branch visually distinct
and independently readable — reducing cognitive noise in complex conditional trees.
Loops: repeat
The repeat block executes its body a specific number of times. The syntax is
repeat N times where N is any integer expression. There is no
for or while keyword.
make power(base, exp)
set result = 1
repeat exp times
set result = result * base
return result
show power(2, 10) # 1024
show power(3, 6) # 729
The compiler generates optimal LLVM loop structures with duplicate alloca elimination for
clean variable reassignment inside nested loop scopes.
Structs
Defining Structs
Custom data structures in Krait are defined using make followed by the
struct name (no parentheses) and an indented list of fields with their default values:
Instantiation with new
Create struct instances using the new keyword. This allocates the struct on
the heap and returns a pointer to it. The pointer is managed by Krait's
ownership system (see Chapter 7).
make Point
x = 0
y = 0
set p = new Point
set p.x = 100
set p.y = 200
show p.x # 100
show p.y # 200
Field Access
Read and write field values using the dot (.) operator. Field types are
inferred from their default values in the struct definition. Once a field has a type, it
follows the same strict reassignment rules as variables.
Heap vs. Stack: Primitives (Int, Float,
Bool, Str) are stored on the stack and copied
by value. Struct instances created with new live on the
heap and are subject to ownership rules.
Modules & Imports
The import Statement
Krait supports a modern module system for code organization. The
import module_name command looks up the file
lib/module_name.kr relative to the build path. The compiler parses the
imported module's AST and merges it into your application namespace before semantic
analysis or LLVM-IR generation.
import math
import io
# Use functions from lib/math.kr
set p = power(2, 8)
show p # 256
# Use functions from lib/io.kr
putchar(75) # Prints "K"
putchar(10) # Newline
Standard Library: math
The math module provides high-performance, pure Krait math functions:
abs(n) — Returns the absolute value of integer n
power(base, exp) — Computes base^exp using an optimized
native loop
Standard Library: io
The io module exposes system I/O through the C FFI:
putchar(char_code) — Writes a single character to stdout by its ASCII
integer code
Standard Library Source
Krait's standard library is itself written in Krait (with FFI where needed). Here is the
complete math module source:
# Krait Standard Math Module
make abs(n)
when n < 0
return 0 - n
return n
make power(base, exp)
set result = 1
repeat exp times
set result = result * base
return result
And the io module — a single FFI extern declaration:
Memory & Ownership
The Core Problem
High-performance languages manage memory in one of three ways:
- Manual Allocation (C/C++) — Fast, but prone to memory leaks,
double-frees, and use-after-free segfaults.
- Garbage Collection (Python/Java/Go) — Safe, but introduces runtime
overhead, high memory usage, and unpredictable "stop-the-world" pauses.
- Static Borrow Checker (Rust) — Safe and fast, but adds complex
syntax, lifetime annotations, and high cognitive noise.
Krait's solution: A clean, zero-noise compile-time ownership tracker.
The compiler enforces strict safety during compilation and inserts deallocation code
automatically — hiding the complexity entirely from the programmer.
The Three Rules of Ownership
1
Owner Scope
Every heap-allocated struct instance (created via new) is owned
by exactly one variable at any point in time.
2
Move Semantics
Assigning a struct variable to another variable transfers
(moves) ownership. The original variable becomes immediately invalid and
cannot be used again.
3
Auto-Drop
When a variable owning a heap resource exits its declared scope, the compiler
automatically inserts a native free instruction to deallocate
the memory.
Move Semantics in Action
When you assign a struct variable to another, the underlying heap pointer is transferred
— not copied:
make Point
x = 0
y = 0
# p1 owns the Point allocation
set p1 = new Point
set p1.x = 100
# Ownership moves from p1 → p2
set p2 = p1
show p2.x # 100 ✓ (p2 now owns it)
Compile-Time Safety
If you attempt to access a moved variable, the Krait compiler catches this at compile
time — not at runtime:
set p1 = new Point
set p2 = p1 # Ownership moved to p2
show p1.x # ❌ COMPILE ERROR!
The compiler emits a clear, actionable diagnostic:
[KRAiT OWNERSHiP ERROR]
Variable: 'p1'
Issue : The variable 'p1' was moved and is
no longer valid in this scope.
How to resolve:
1. Avoid using 'p1' after it has been moved.
2. Duplicate the data or reorganize your code
to assign only when done using 'p1'.
Auto-Drop in Action
You never call free manually. The compiler tracks the lifetime of heap
owners and automatically inserts native @free calls when a variable goes
out of scope:
make create_and_use()
set temp = new Point
set temp.x = 42
return temp.x
# temp is automatically freed here!
# Compiler inserts: free(temp)
show create_and_use() # 42
This guarantees: zero memory leaks (freed immediately when unneeded),
zero double-frees (compiler invalidates moved variables), and
optimal memory layouts (clean register utilization, minimal heap
fragmentation).