Skip to content
English

Language guide

This documentation describes the frozen scope of the v0.0.18 experimental prerelease. Actual publication and downloadable assets are recorded in GitHub Releases. This guide introduces core syntax. The repository’s syntax contract and semantic contract remain authoritative. These are teaching fragments; see backend support for differences.

name = "Ku"
count:int = 3
ready:bool = true
values:[int] = [1, 2, 3]
count += 1

The first assignment declares a variable. Primitive types are int, float, bool, str, and null. Integers are signed 64-bit; floating point uses binary64. Integers do not become conditions through truthy conversion.

This complete basic example comes from examples/function.ku:

fn add(a: int, b: int): int {
return a + b
}
fn main() {
println(add(10, 20))
}

Expected output: 30. This control flow fragment can be placed inside main:

total = 0
for i in 5 {
total += i
}
if (total == 10) {
println("sum ok")
}

for i in 5 iterates from 0 through 4. if and while require bool conditions. Use braces for multiple statements. Branch expressions use match; there is no switch.

int, float, bool, and null are Copy values. Strings, arrays, objects, structs, enums, Results, and function values are Owned. Assignment and ordinary arguments move owned values by default. Use .clone() when you need an independent value. Tasks cannot be cloned.

original = "Ku"
copy = original.clone()
println(original)
println(copy)

A synchronous function can declare a read only borrowed parameter:

fn length(&text: str): int {
return text.len()
}

Call it as length(value), without & on the argument. Borrowed data cannot be mutated, moved out, or escape the call. Combinations such as borrowed async parameters remain unsupported. Read only borrowing does not guarantee arbitrary function purity.

struct User {
name: str
age: int
}
user = User { name: "Ku", age: 1 }
println(user.name)

Dynamic objects use { name: "Ku" }. A normal missing object index fails. value["name"]? handles lookup through Result, yielding a KuValue on success. Use .as_str()? or .as_int()? for a concrete type. A missing key does not silently become null.

Standard library imports use dotted paths, such as import json from "std.json". Local modules can use import math from "./math". User defined top level exports start with an ASCII uppercase letter; lowercase names remain private to the file.

The return type T! can return either T or Error. ? propagates a recoverable error; fail creates one:

fn read_name(): str! {
fail "name missing"
}
fn main() {
try {
println(read_name()?)
} catch (error) {
println(error.message)
}
}

Error has domain, code, and message fields. Ordinary catch does not catch panic, Task cancellation, or timeout. Cancellation cleanup in finally shares an absolute deadline budget; it does not guarantee completion of arbitrary user cleanup.

Application code obtains a single use Task handle by calling an async fn, then uses await. There are no application APIs named task.spawn, Task.new, or thread.spawn. The interpreter and native Task subset have different coverage. An example working in one backend does not prove full support in another. See platform and backend support.