Language guide
This guide introduces core syntax. The repository’s syntax contract and semantic contract remain authoritative. These are teaching fragments; see backend support for differences.
Variables and types
Section titled “Variables and types”name = "Ku"count:int = 3ready:bool = truevalues:[int] = [1, 2, 3]count += 1The 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.
Functions and control flow
Section titled “Functions and control flow”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 = 0for 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.
Ownership and read only borrowing
Section titled “Ownership and read only borrowing”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.
Structs, objects, and modules
Section titled “Structs, objects, and modules”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.
Recoverable errors
Section titled “Recoverable errors”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.
Concurrency boundaries
Section titled “Concurrency boundaries”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.