# Kairo Full Documentation > Kairo is a statically typed, compiled systems programming language with native bidirectional C++ interoperability. > This file contains the complete language documentation concatenated for LLM consumption. > Generated: 2026-06-25T22:02:04.689Z --- ======================================================================== SECTION: GETTING STARTED ======================================================================== ## Welcome to Kairo URL: https://www.kairolang.org/docs/ > [!WARNING] > The language reference describes Stage 1 Kairo. Not the Stage 0 compiler. Stage 1 is still under development and the syntax and standard library may change before 1.0. The Stage 0 compiler is stable and can be used to build Kairo programs, but it does not yet support all Stage 1 features. # Kairo Kairo is a statically typed, compiled systems language with native bidirectional C++ interoperability. C++ projects can `#include` Kairo files directly. Kairo code calls C++ libraries without a binding layer. The compiler emits ABI-compatible object code that links with GCC, Clang, or MSVC output. ```kairo ffi "c++" import "engine.hh" as engine fn main() { var ctx = engine::create_context() try { engine::run(ctx) } catch e: std::Error::Runtime { std::println(f"engine failed: {e}") } } ``` --- ## Why Kairo **Full control, less friction.** Kairo gives you manual memory management, raw pointer access, struct layout control, and zero-cost abstractions with a compiler that tracks lifetimes, promotes smart pointers, and catches null dereferences at compile time. You opt into safety by default and opt out explicitly when you need to. **C++ interop that works both ways.** `ffi "c++"` imports C++ headers and makes every declaration available as a native Kairo symbol. The `kcc` driver lets C++ code `#include "file.k"` with no code generation step. Templates, concepts, classes, and smart pointers cross the boundary cleanly. **Zero-cost abstractions.** Interfaces are structural and carry no vtable. Generics are monomorphized. Panic handling compiles to tagged returns and branches no unwinding tables, no runtime. Virtual dispatch exists only when you write `virtual`. --- ## At a Glance ```kairo struct Point { var x: f64; var y: f64; // semi-colons are optional! } extend Point { fn length(self) const -> f64 { return std::sqrt(self.x * self.x + self.y * self.y) } fn op +(self, other: Point) -> Point { return Point { x: self.x + other.x, y: self.y + other.y } } } enum ParseResult { Ok { value: T }, Error { message: string }, } fn try_parse_point(input: string) -> ParseResult { var parts = input.split(",") if parts.length() != 2 { return ParseResult::Error { message: f"expected 'x,y', got '{input}'" } } var x = std::parse(parts[0].trim()) var y = std::parse(parts[1].trim()) return ParseResult::Ok { value: Point { x: x, y: y } } } fn load_config(path: string) panic -> Point { var content = std::read_file(path) if content.length() == 0 { panic std::Error::IO("config file is empty") } return try_parse_point(content).value } var a = Point { x: 3.0, y: 4.0 } var b = Point { x: 1.0, y: 2.0 } std::println(f"sum length: {(a + b).length()}") match try_parse_point("1.5, 2.5") { case .Ok(var value) { std::println(f"parsed: ({value.x}, {value.y})") } case .Error(var message) { std::println(f"failed: {message}") } } var origin = try { load_config("origin.conf") } catch { Point { x: 0.0, y: 0.0 } } std::println(f"origin: ({origin.x}, {origin.y})") ``` --- ## Status Kairo is in active development. The compiler is currently in **Stage 0** a C++ implementation that transpiles Kairo to C++. Stage 1 (self-hosted compiler written in Kairo) is underway. Language syntax and standard library APIs may change before 1.0. The documentation on this site reflects the current language design. --- ## Next Steps - [Philosophy](/docs/philosophy) understand the design principles - [Primitives](/docs/language/primitives) start with the type system - [Classes](/docs/language/classes) objects, inheritance, virtual dispatch - [C/C++ Interop](/docs/language/c-c++) calling C++ from Kairo and back - [Example: HTTP Server](/docs/examples/http-server) a complete project walkthrough --- ## Philosophy URL: https://www.kairolang.org/docs/philosophy/ # Philosophy Kairo is a systems programming language built around a simple idea: give developers full control without forcing them to fight the language. --- ## The Problem Systems programming is stuck between two failure modes. One class of languages gives you unlimited freedom and zero guardrails. You manage memory, track lifetimes, audit every pointer, and hope your discipline holds across a million-line codebase maintained by a rotating team for a decade. When it doesn't, you get CVEs. Another class of languages solves this by restricting what you're allowed to express. Safety comes from rejection the compiler says no until you restructure your program to fit its model. This works, but it trades implementation freedom for cognitive overhead. Simple patterns become puzzles. Prototyping feels like negotiating with a bureaucracy. Kairo rejects both failure modes. The language should help you write correct code without dictating how you structure it. --- ## Design Principles Four ideas drive every decision in the language. ### Control should exist Kairo is a systems language. You can manage memory directly, control struct layout and alignment, work with raw pointers, manipulate ABI boundaries, interoperate with C and C++ at zero cost, and write allocators, runtimes, compilers, or kernels. Nothing is hidden. But unlike traditional systems languages, Kairo does not assume you want to solve every problem manually. The compiler provides: - Ownership tracking and automatic smart pointer promotion - Null safety with compile-time null checks on safe pointers - Panic propagation analysis with exhaustive catch verification - Structural interface validation without explicit registration - Full-program lifetime analysis with no annotations required All of which you can override or opt out of when you need to. You are guided, not trapped. ### Safety should assist, not dominate Safety in Kairo works through visibility, not restriction. The compiler explains mistakes clearly, preserves programmer intent, and keeps experimentation fluid. During development, it helps you move fast. When you ship, it gets stricter. This creates a workflow closer to real engineering: prototype freely, refine intentionally, harden for production. The escape hatches are explicit. `unsafe` blocks suspend AMT tracking. `unsafe *T` pointers bypass null checks and bounds analysis. `unsafe` function overloads relax semantic invariants. Every dangerous operation is visible in the source easy to find during review, easy to audit, easy to grep. ### Code should explain itself A developer should not need IDE magic, hidden compiler behavior, or dense metaprogramming to understand what code does. - `self` is always visible in method signatures - Inheritance uses the `derives` keyword no implicit resolution - Pointer types are visually distinct (`*T` vs `unsafe *T` vs `*const T`) - All conversions use a single `as` keyword - Interfaces are structural satisfy the methods, satisfy the interface - Operator overloads are declared with `fn op` syntax that mirrors the operator ```kairo class Sensor { var reading: f64 mutable var access_count: i32 fn value(const self) -> f64 { self.access_count += 1 return self.reading } fn calibrate(self, offset: f64) { self.reading += offset } } ``` Open this in a text editor with no tooling and you can still read the program. You know which methods mutate, which fields are mutable through `const`, and what the visibility boundaries are. Kairo treats readability as a systems programming requirement, not a beginner convenience. ### Adoption should be incremental Large codebases are built over decades. Entire ecosystems exist in C and C++. Most teams cannot afford full rewrites. Kairo integrates into existing systems one module at a time. Import C headers directly with `ffi "c"`. Import C++ headers with `ffi "c++"`. Kairo emits ABI-compatible object code Itanium on Unix, MSVC on Windows so Kairo `.o` files link with GCC, Clang, or MSVC output without shims. Object layout, vtable structure, name mangling, and calling conventions all follow platform standards. ```kairo ffi "c++" import "engine.hh" as engine fn main() { var ctx = engine::create_context() engine::run(ctx) } ``` A team can migrate one file at a time, validate performance incrementally, and maintain the existing build system throughout. Kairo coexists with infrastructure it does not demand replacement of it. --- ## Zero-Cost Abstractions High-level features compile to predictable low-level code: - Interfaces are structural and carry no vtable or runtime dispatch cost. A type satisfies an interface if it has the right methods no registration, no indirection. - Generics are monomorphized at compile time. No boxing, no type erasure, no runtime cost. - Panic handling compiles to tagged return values and branches. No unwinding tables, no runtime exception handler, no stack unwinding. - AMT (Automatic Memory Tracking) runs at compile time. No garbage collector, no reference counting overhead unless the analysis determines shared ownership is required. - Virtual dispatch exists only when you write `virtual`. Non-polymorphic classes have no vtable pointer. You pay for what you use. Nothing else. --- ## Familiar, But Cleaner Kairo is intentionally familiar to experienced systems programmers. Most concepts map naturally from C++, with complexity stripped where it adds no value: | C++ complexity | Kairo simplification | |---|---| | `static_cast` / `dynamic_cast` / `reinterpret_cast` / `const_cast` | Single `as` keyword | | Implicit `this` | Explicit `self` parameter | | `public:` / `private:` / `protected:` sections | Per-declaration `pub` / `priv` / `prot` | | `const int*` vs `int* const` ambiguity | Left-to-right `const` binding rule | | Header/source split | Single `.k` files | | `friend` declarations | Module-level visibility (`priv`, `prot`) | | `#define` preprocessor | Scoped token macros and AST attributes | | Implicit special member generation/suppression | Always generates unless explicitly deleted | | Exception unwinding tables | Zero-cost panic returns | The goal is not novelty. The goal is removing friction that decades of C++ evolution accumulated without losing any of the power that makes C++ valuable. --- ## Who Kairo Is For Kairo is built for projects that grow: - Compilers and language toolchains - Game engines and real-time systems - Distributed systems and infrastructure - Operating systems and embedded firmware - Long-lived enterprise codebases It is designed for engineers who want the performance and control of systems programming without the maintenance cost that traditionally comes with it. --- ## Installation URL: https://www.kairolang.org/docs/install/ # Installation There are two ways to get Kairo: download a **prebuilt binary** (fastest), or **build from source**. Most users want the prebuilt binary. Kairo ships as two compilers: - **Stage 0** the current compiler, written in C++. It transpiles Kairo to C++ and does **not** require LLVM. This is what almost everyone wants. - **Stage 1** the self-hosted compiler, written in Kairo (work in progress). Building it requires a working Stage 0 compiler **and** the patched LLVM submodule. Unless you are developing Kairo itself, install Stage 0 and stop there. --- ## Prebuilt Binaries Download the archive for your platform from the [release page](https://github.com/kairolang/kairo/releases), extract it, and add the `bin` directory to your `PATH`. | Platform | Architecture | File | |---|---|---| | Linux | x86_64 | `kairo--x86_64-linux-gnu.tar.xz` | | Linux | aarch64 | `kairo--aarch64-linux-gnu.tar.xz` | | macOS | Apple Silicon | `kairo--arm64-apple-macosx.zip` | | macOS | Intel | `kairo--x86_64-apple-macosx.zip` | | Windows | x64 | `kairo--x64-windows-msvc.zip` | | Windows | arm64 | `kairo--arm64-windows-msvc.zip` | ### System requirements - **Linux:** glibc 2.35 or newer (Ubuntu 22.04+, Debian 12+, RHEL 9+, Fedora 37+) - **macOS:** 13.3 (Ventura) or newer - **Windows:** Windows 10 or newer no Visual C++ Redistributable required, the runtime is statically linked The Linux binaries are self-contained no system libc++ or libunwind required. > [!CAUTION] > On macOS, a downloaded binary is quarantined and Gatekeeper will refuse to run it > ("cannot verify this app is free of malware"), because the release binaries are not yet > notarized. Clear the quarantine flag before first run: > > ```bash > xattr -dr com.apple.quarantine ./kairo > ``` > > This applies only to binaries downloaded from the release page builds from source are > unaffected. --- ## Building from Source ### Requirements Stage 0 requires: - **Clang 18 or newer** libstdc++ is **not** supported, you must build with Clang and libc++ - **libc++** and **libc++abi** - **xmake** the build system - **git** Stage 1 additionally requires the patched LLVM submodule (see [below](#stage-1-self-hosted-in-development)). The VSCode extension additionally requires **Node.js** and **npm**. ### 1. Install dependencies Every platform needs Clang 18+ with libc++. The sections below show how to get a satisfying toolchain. #### Linux Arch / Manjaro Arch's `clang` is current (well past 18), so no version pinning is needed. ```bash sudo pacman -S git clang libc++ libc++abi lld cmake ninja xmake ``` #### Linux Ubuntu / Debian Clang 18 is the minimum; newer is fine. Run `clang++ --version` if it reports 18 or higher, skip the apt.llvm.org step below and just ensure `libc++` and `libc++abi` are installed. Otherwise, install Clang 18 from LLVM's apt repo (the distro Clang is often older than 18 or defaults to libstdc++): ```bash wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 18 sudo apt-get install -y libc++-18-dev libc++abi-18-dev git # make clang-18 the default clang/clang++ sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-18 100 sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-18 100 ``` #### Linux Fedora / RHEL Ensure `clang --version` reports 18+. Older RHEL ships an older toolchain you may need a newer LLVM module (`sudo dnf module install llvm-toolset:latest`) to meet the minimum. ```bash sudo dnf install -y clang libcxx-devel libcxxabi-devel git cmake ninja-build ``` Then install xmake on any Linux distro that did not already provide it: ```bash curl -fsSL https://xmake.io/shget.text | bash ``` #### macOS Install the LLVM toolchain and xmake with Homebrew, then put Homebrew's Clang on your `PATH`. Apple's bundled Clang works too, but Homebrew LLVM is recommended for libc++ feature support: ```bash brew install llvm xmake echo 'export PATH="'"$(brew --prefix llvm)"'/bin:$PATH"' >> ~/.zshrc source ~/.zshrc ``` #### Windows Install xmake in PowerShell: ```powershell irm https://xmake.io/psget.text | iex ``` For the compiler toolchain, install **Visual Studio** with the "Desktop development with C++" workload, or **LLVM for Windows** from [releases.llvm.org](https://releases.llvm.org/). xmake detects the toolchain automatically. ### 2. Clone the repository ```bash git clone https://github.com/kairolang/kairo/ cd kairo git checkout archive/beta-helix-0.0.1 ``` Pull submodules. For Stage 0 this is a fast clone it does not pull LLVM: ```bash git submodule update --init --recursive ``` ### 3. Build Stage 0 ```bash xmake ``` No flags needed. The build produces a self-contained compiler in `build/release//bin/`: `kairo` and `kbld`. ### 4. Add to PATH **Linux / macOS:** ```bash export PATH="$PATH:$(ls -d $(pwd)/build/release/*/bin)" ``` Add that line to your `~/.bashrc` or `~/.zshrc` to make it permanent. **Windows (PowerShell):** ```powershell $env:Path += ";$((Get-ChildItem -Directory .\build\release\*\bin).FullName)" ``` This sets `PATH` for the current session. To persist it across sessions: ```powershell $binPath = (Get-ChildItem -Directory .\build\release\*\bin).FullName [Environment]::SetEnvironmentVariable("Path", "$env:Path;$binPath", "User") ``` For Command Prompt, add the path via System Properties -> Environment Variables. ### 5. Validate Create a file called `test.k`: ```kairo fn main() -> i32 { var x = 5; var y = 10; std::print(f"Hello, world! Sum of {x} and {y} is {x + y}"); return 0; } ``` Compile and run it: ```bash kairo test.k ``` --- ## Stage 1 (self-hosted, in development) Stage 1 is the self-hosted compiler, written in Kairo. Building it is optional only do this if you're developing Kairo itself. It requires a working Stage 0 build (above) plus the patched LLVM submodule. LLVM is a hard requirement for Stage 1; there is no LLVM-free path. Kairo carries a **minor fork of LLVM** vendored as a submodule. Use it as-is do not substitute a system or upstream LLVM, the patches are required. ```bash git checkout canary git submodule update --init --recursive # pulls the patched LLVM large download kbld # must be on PATH, or use ./build/release//bin/kbld ``` The first `kbld` builds the patched LLVM before building Stage 1. This is a long, one-time compile it has not hung. Subsequent builds reuse it. You can run test files whose entry point is `fn Test() -> i32 { ... }`: ```bash kbld test Compiler/Lexer/Lexer.k ``` > [!WARNING] > Re-run `git submodule update --init --recursive` after switching to `canary`. That branch > adds the patched LLVM submodule, which the Stage 0 clone deliberately skips to stay small. --- ## VSCode Extension VSCode is currently the only editor with LSP support and `.k` syntax highlighting. The language server ships as part of the compiler, so there is nothing extra to run the extension only needs to locate `kairo`. ### Install Pick whichever is easiest: - **Marketplace** install from the [Kairo extension page](https://marketplace.visualstudio.com/items?itemName=KSF.kairo). - **Prebuilt VSIX** download the latest `.vsix` from [kairo-lsp](https://github.com/kairolang/kairo-lsp/) (e.g. `kairo-0.5.5.vsix`), then in VSCode: **Extensions** -> **⋯** -> **Install from VSIX…** - **Build from source:** ```bash git clone https://github.com/kairolang/kairo-lsp/ cd kairo-lsp npm install npm run build --omit=dev npx @vscode/vsce package ``` This produces a `.vsix` you install the same way. ### Configure If `kairo` is on your `PATH`, the extension finds it automatically no configuration needed. Otherwise set `kairo.path` in your VSCode `settings.json` to the compiler's location. If it cannot find the compiler, you get a single "path not set" warning. ### Debugging - **Run Kairo File** runs and attaches the debugger - **Run Kairo File with Args** prompts for comma-separated args (e.g. `arg1,arg2,arg3`) Both work with `fn main()` or `fn Test()` entry points. > [!WARNING] > With `fn Test()`, VSCode redirects to a temporary file during debugging. Breakpoints work, but > **edits are not saved back to your original file** re-running the debugger resets everything. > Use `fn main()` for active development. --- ## Troubleshooting **macOS: "cannot verify this app is free of malware" when running a downloaded release binary.** Release binaries are not yet notarized. Clear the quarantine flag: ```bash xattr -dr com.apple.quarantine ./kairo ``` This does not affect source builds only binaries downloaded from the release page. **Build fails with `libstdc++` / `use of undeclared identifier 'requires'` / constexpr errors.** Your Clang is defaulting to libstdc++ or an older C++ standard. Confirm `clang++ --version` reports 18+ and that libc++ is installed. Kairo requires Clang with libc++ and C++23. --- ## Compiler Compliance URL: https://www.kairolang.org/docs/compilence/ # Kairo Compiler Standards & Compliance This document covers every standard, ABI, and specification Kairo targets, what compliance means in practice, and where Kairo intentionally deviates and why. It is written for engineers who need to know exactly what guarantees the compiler makes and what it doesn't. --- ## Floating-Point: IEEE 754-2019 Kairo targets **IEEE 754-2019** (the 2019 revision of IEEE 754-2008) for all binary floating-point types: `f16`, `f32`, `f64`, `f128`. What this means concretely: - All four basic arithmetic operations (`+`, `-`, `*`, `/`), `sqrt`, and `fma` produce correctly rounded results in round-to-nearest-even mode (the IEEE default). The result is the representable value closest to the infinitely precise result, with ties going to even. - Intermediate results within a single expression may be fused into FMA operations by the compiler (FP contraction is `on` by default, not `fast`). This can produce results that differ from performing the operations separately with two roundings. If you need strict per-operation rounding, use `--fp-contract=off`. - Overflow produces `inf`, underflow produces `0.0`. Operations that produce `NaN` (e.g., `0.0 / 0.0`, `sqrt(-1.0)`) propagate `NaN` per IEEE 754; no crash, no trap. Check for `NaN` explicitly with `std::is_nan()` when needed. - NaN payload bits are not guaranteed to be preserved across arithmetic operations and are not guaranteed to be portable across architectures. Inspecting NaN bit patterns is explicitly unsupported behavior. If you need NaN payloads, use `f32::from_bits` / `f64::from_bits` and document why. - The 2019 standard deprecated the non-associative `minNum`/`maxNum` operations from 2008 and replaced them with `minimum`/`maximum` which have well-defined NaN handling. Kairo's `min`/`max` builtins follow the 2019 semantics. - Subnormal (denormal) numbers are handled per IEEE spec by default. Flush-to- zero mode is not enabled unless you explicitly opt in with `--fp-model=fast` or the `@float_mode(fast)` block annotation. **What Kairo does not guarantee:** - Reproducibility of floating-point results across different optimization levels, different target architectures, or different LLVM versions. If you need bitwise-reproducible results, use `-fp-contract=off -fp-model=strict` and target a specific CPU with `--mcpu`. - Strict exception observability by default. FP exceptions (divide-by-zero, overflow, underflow, inexact, invalid) are not observable through `fenv.h` equivalents unless you compile with `--fp-model=strict`. The default (`precise`) treats exceptions as ignorable, which is what lets the optimizer e.g. hoist FP computations out of loops. **FP mode quick reference:** | Mode | What it does | When to use | |---|---|---| | `precise` (default) | IEEE arithmetic, no unsafe rewrites, FMA on | General use | | `strict` | IEEE + observable exceptions + no FMA | Scientific code, reproducibility audits | | `fast` | Everything unsafe on (`-ffast-math`) | SIMD kernels, numerical code you've already validated | | `@float_mode(fast)` | Block-scoped fast-math | Hot inner loops only | --- ## Text Encoding: Unicode 15.1 / UTF-8 ### Source Files Kairo source files are **UTF-8** only. No BOM. No other encoding is accepted. The lexer validates UTF-8 byte sequences and rejects invalid sequences as hard errors with a diagnostic pointing at the exact byte offset and offending bytes. A BOM at the start of a `.k` file is an error. ### String Internal Representation Kairo's `string` type is a 32-byte value type backed exclusively by a `u8` array using UTF-8 encoding internally, with small string optimization (SSO). SShort strings are stored inline without a heap allocation (exact threshold is implementation-defined and subject to change until the standard library is finalized). Longer strings are heap-allocated. All strings are null-terminated internally; conversion to C string types does not add a separate null byte. - **Codepoint indexing:** `s[i]` returns a `char` (a `u32` Unicode scalar value) at codepoint position `i`. For string literals, the compiler pre-populates a breadcrumb cache at codegen time mapping codepoint positions to UTF-8 byte offsets, making indexing O(1) with zero runtime cost. For strings constructed at runtime from a raw `*u8` or `*u32` pointer, no cache is available and indexing is O(n); use sequential iteration or copy into a `string` first if you need repeated random access. - **Byte indexing:** `s.bytes[i]` returns a raw `u8` at byte offset `i`. This is O(1) always but returns raw UTF-8 bytes, not characters. - **Iteration:** `for ch in s` yields `char` values (decoded codepoints) sequentially. **Compile-time breadcrumb cache:** For string literals containing multi-byte codepoints, the compiler pre-populates the breadcrumb cache at codegen time. For example, `"hi 🔥"` is emitted as roughly: ```kairo string::stack("hi \xf0\x9f\x94\xa5", string::cache{(3, 7)}) ``` The codepoint-to-byte mapping is fully resolved at compile time. No runtime scanning occurs for literals. For pointer-constructed strings, no cache is injected; codepoint indexing falls back to O(n) sequential decode. See [Primitives](/docs/language/primitives#strings) for the full string API. ### Identifiers Kairo identifiers may contain any Unicode scalar value that has the `ID_Start` or `ID_Continue` property (Unicode UAX #31), **plus** emoji and other non-letter code points that UAX #31 excludes. This is an intentional extension beyond the standard identifier grammar. Concretely: - Letters and digits from any script: Latin, Arabic, Chinese, Cyrillic, Devanagari, etc. all valid. - Emoji: valid. - Combining marks: valid as continuation characters. - Whitespace, control characters: never valid in identifiers. Two identifiers are equal if and only if their sequences of scalar values are equal. Kairo does not perform Unicode normalization on identifiers. This matches the behavior of most modern languages. If you want normalization, normalize your source before feeding it to the compiler. ### String Indexing Indexing a `string` by integer (`s[i]`) returns the codepoint at position `i`, not a grapheme cluster. For most text this is the same thing, but for combining sequences and emoji with modifiers it is not: - `"hello"`: `s[1]` returns `'e'`. Five codepoints, five elements. - A string with a combining sequence (e.g., `e` + combining acute): the base character and the combining mark are separate codepoints and separate indices. - Emoji with ZWJ sequences (e.g., family emoji): multiple codepoints, multiple elements. `length()` returns the codepoint count, not the visual glyph count. Grapheme cluster iteration is not currently in the standard library. If you need to operate on user-visible characters, use a third-party ICU binding. ### Conversions and C/C++ Interop `string` provides explicit conversion methods. When calling C++ functions with Kairo primitives, conversions to C++ string types are implicit: | Conversion | Output | Use case | |---|---|---| | `as libcxx::string` | UTF-8 `std::string` | Natural fit; Kairo strings are UTF-8 internally | | `as libcxx::u16string` | UTF-16 `std::u16string` | Windows WinAPI (`LPWSTR`), Java interop | | `as libcxx::u32string` | UTF-32 `std::u32string` | APIs expecting `char32_t` sequences | | C `const char*` | Null-terminated UTF-8 `u8` pointer | Implicit conversion; no `unsafe` block required | See [C/C++ Interop](/docs/language/c-c++) for the full FFI model. ### What Kairo Does Not Do - **Locale-sensitive operations**: `string` comparison is binary (codepoint by codepoint). Locale-sensitive collation is not in the standard library. Use a third-party ICU binding if you need locale-aware sorting. - **Encoding detection**: Kairo does not detect or guess source file encoding. UTF-8, no BOM. - **Signed byte interpretation**: The internal backing type is `u8`. There is no `i8` / C `char` ambiguity in Kairo's string representation. --- ## ABI: Platform psABIs Kairo follows the platform processor ABI for the target triple. It does not define its own ABI and does not need to because Kairo compiles to native object files via Clang's backend. The relevant documents per platform: ### x86-64 Linux / BSD / macOS - **System V AMD64 psABI v1.0** - calling convention, register usage, stack alignment (16-byte at call site), parameter passing, return value encoding, varargs layout, TLS model, ELF object format. - Kairo respects the red zone (128 bytes below RSP) by default. Kernel code should compile with `--kernel-mode`. - SIMD types (`f32x4`, `f32x8`, etc.) follow the psABI's XMM/YMM/ZMM classification rules. Vectors passed on the stack are 16-byte aligned minimum. ### AArch64 Linux - **ARM64 ELF psABI (AAPCS64)** - 16 general-purpose argument registers (x0-x7 for integers, v0-v7 for floats/vectors), caller-saved x0-x17, callee-saved x19-x28. - Pointer authentication is not enabled by default. Use `--mbranch-protection` to opt in. ### macOS (Mach-O) - **Apple ARM64 ABI** on Apple Silicon - extends AAPCS64. Stack pointer must be 16-byte aligned at all call sites. Clang enforces this. - **System V AMD64** on Intel Mac. - `--macos-version-min` is required if you target macOS older than the build machine SDK. Kairo does not guess a deployment target. ### Windows (PE/COFF) - **Microsoft x64 calling convention** - 4 register parameters (rcx, rdx, r8, r9 for integers; xmm0-xmm3 for floats), shadow space requirement (32 bytes), caller cleans stack. - C++ ABI on Windows is MSVC. Set `--cxx-abi=msvc` when interoperating with MSVC-compiled C++ code. The default is Itanium which is what Clang uses on Windows by default for Clang-compiled code. ### WASM / WASI - **WebAssembly MVP + proposals** per the flags you enable. WASI preview1 or preview2 depending on `--wasi-version`. The WASM calling convention is defined by the Wasm binary format spec; there is no psABI in the traditional sense. --- ## Debug Info: DWARF 5 When compiled with `-g` / `--debug-info`, Kairo emits **DWARF 5** by default. DWARF 4 is available via `--dwarf-version=4` for targets that require it (e.g., older GDB versions, some embedded targets). What Kairo emits: - `DW_AT_language` is set to `DW_LANG_C_plus_plus_14` in Stage 0 (the current compiler transpiles Kairo to C++, so the DWARF reflects the C++ output). A proper `DW_LANG_Kairo` language tag will be registered with the DWARF committee once the language spec stabilizes and the Stage 1 compiler (which emits LLVM IR directly) is complete. - Source file paths in DWARF use the paths as given to the compiler, resolved relative to `--working-dir` if set. Use `--remap-path-prefix` or `--debug-prefix-map` to strip build-machine-specific paths for reproducible builds. - Line tables include column information (DWARF 5 `DW_LNS_set_column`), which allows debuggers to point at the specific expression within a line. - Split DWARF (`--split-dwarf`) produces `.dwo` files. This reduces link-time memory usage on large projects. The `.dwo` files must be accessible at debug time (either in the same directory or via `debuginfod`). - Type units are emitted for types defined in headers, reducing debug info size in multi-TU builds. **Comments and trivia in debug info:** The Kairo AST preserves comments and whitespace trivia. These are currently not emitted into DWARF (there's no standard DWARF attribute for them). They are preserved in the AST for tooling use (formatters, LSP, etc.) but stripped before codegen. --- ## Object Format: ELF / Mach-O / PE-COFF / WASM Kairo emits the native object format for the target: | Platform | Format | Notes | |---|---|---| | Linux, BSD, Android | ELF64 (ELF32 with `--m32`) | | | macOS, iOS | Mach-O 64-bit | | | Windows | PE/COFF | | | WASM | WebAssembly binary | | | Bare metal | ELF (usually) | depends on target ABI | | All platforms | `.klib` | Platform-agnostic library distribution format | Kairo does not emit its own intermediate object format for native targets. The output of a native compilation is a standard object file that any conformant linker for that platform can process. ### klib — Kairo Library Format `.klib` is Kairo's platform-agnostic binary format for distributing libraries. It contains no object code. A `.klib` file contains: - Full serialized AST for all exported modules - Macro definitions - Type information and symbol table - `.amt` module summary (ownership and lifetime metadata consumed by sema passes) `.klib` files are imported identically to source files — `import my_lib` resolves whether `my_lib` is a `.k` source tree or a `.klib` package. ELF/Mach-O/PE output is still available for consumers that link against Kairo code from other languages; `.klib` is the distribution format for the Kairo package ecosystem and is what gets uploaded to package repositories. Because `.klib` carries the AST rather than object code, monomorphization and codegen happen on the consumer's machine for their target platform. This makes `.klib` files self-contained and inherently cross-platform. --- ## C++ Interop ABI When Kairo calls into C++ via `ffi "c++" import`, the C++ code is compiled by Clang using the same backend invocation. Kairo has full native interop for all C++ standard versions through C++26. The ABI boundary between Kairo-emitted code and C++-emitted code is: - **Itanium C++ ABI** on all platforms except Windows MSVC targets. - **MSVC C++ ABI** on Windows when `--cxx-abi=msvc` is set. - Name mangling follows the Itanium C++ ABI scheme by default. Kairo functions exported across the FFI boundary use C++ name mangling to support overloading and seamless interop with Clang-compiled C++ code. Explicit `ffi "C"` linkage (no mangling) is only used for C interop or when specifically requested. - C++ exceptions propagate across the Kairo/C++ boundary normally because both sides use the same unwinding tables (`.eh_frame` / `__eh_frame`). Kairo functions are trivially `noexcept` at the ABI level (panics are returned as tagged values, not thrown), so the unwinder can pass through them cleanly. - Virtual dispatch into C++ classes works without special handling because Kairo lowers method calls on C++ objects directly to Clang, which handles vtable dispatch normally. - C++ smart pointers (`std::unique_ptr`, `std::shared_ptr`, `std::weak_ptr`) map to Kairo's AMT-tracked equivalents (`std::Unique`, `std::Shared`, `std::Weak<*T>`) automatically. No `unsafe` block is required for FFI calls using smart pointers, references, move references, or value types. Only raw pointer parameters (`T*`, `void*`) require an `unsafe` block. See [C/C++ Interop](/docs/language/c-c++) and [Unsafe](/docs/language/unsafe) for the full FFI safety model. --- ## Linker: LLD / Platform Linker `kld` (Kairo's linker driver) defaults to **LLD** for all targets. LLD's compatibility targets: | Mode | Compatibility | |---|---| | ELF | GNU ld compatible | | Mach-O | Apple ld64 compatible | | PE/COFF | MSVC link.exe compatible | | WASM | wasm-ld (LLD's WASM mode) | Kairo does not require LLD. You can use the platform linker by passing `--ld-flags` to forward arguments directly, though the default flag translation assumes LLD syntax. GNU ld is supported for ELF targets. --- ## Reproducible Builds Kairo supports reproducible builds when the following conditions are met: 1. Same source, same flags, same `--target`, same `--cxx-std`. 2. `--no-timestamps` is set (strips all embedded timestamps). 3. `--remap-path-prefix` is used to normalize absolute paths. 4. `SOURCE_DATE_EPOCH` environment variable is set (Clang reads this for embedded timestamps in DWARF; `--source-date-epoch` sets it for you). 5. `--lto=off` (LTO can produce non-deterministic output across runs due to parallel section ordering in ThinLTO). 6. Same LLVM version. The backend is not byte-for-byte stable across LLVM major versions. Kairo does not guarantee reproducibility across different `--opt` levels or when `--fp-model=fast` is used (fast-math can produce different reassociations depending on whether the optimizer's heuristics fire). --- ## What Kairo Explicitly Does Not Target - **POSIX.1-2024**: Kairo's standard library does not wrap POSIX. You call POSIX functions via FFI if you need them. The Kairo stdlib provides its own file, threading, and memory abstractions that map to the platform without requiring POSIX compliance from the target. - **C++ standard (ISO/IEC 14882)**: Kairo is not C++ and does not attempt C++ conformance. C++ interop works through Clang, not through implementing the C++ standard in Kairo. - **MISRA / CERT / SEI coding standards**: These are auditable properties of specific programs, not language-level guarantees. Kairo does not ship a MISRA checker. You can run your Kairo-generated C++ through existing MISRA tools if your target requires this. - **ISO/IEC 9899 (C standard)**: Same as C++. C interop works through the FFI layer. Kairo is not a C compiler. --- ## Version Pinning | Standard | Version targeted | |---|---| | IEEE floating-point | 754-2019 | | Unicode | 15.1 | | UTF-8 encoding | RFC 3629 | | DWARF debug info | 5 (default), 4 (opt-in) | | x86-64 psABI | System V AMD64 v1.0 | | AArch64 psABI | AAPCS64 (ARM IHI0055) | | Itanium C++ ABI | Current (Clang tracks this) | | C++ interop | All versions through C++26 | | WASI | preview1 (default), preview2 (opt-in) | | WebAssembly binary | MVP + explicit proposals | | ELF | System V gABI + platform psABI | | Mach-O | 64-bit Mach-O | | PE/COFF | Microsoft PE32+ | --- ======================================================================== SECTION: LANGUAGE ======================================================================== ## Primitives URL: https://www.kairolang.org/docs/language/primitives/ # Primitives Kairo's primitive types are built into the language and available without imports. They map directly to hardware-supported representations where possible, falling back to software emulation for extended-width types. --- ## Integers All integer types have a fixed, guaranteed size. The default integer type is `i32` if a literal doesn't fit in `i32`, the compiler promotes it to the smallest signed type that can hold the value, up to `i512`. | Type | Size | Description | C++ Equivalent | |---|---|---|---| | `u8` | 1 byte | Unsigned 8-bit integer | `uint8_t` | | `u16` | 2 bytes | Unsigned 16-bit integer | `uint16_t` | | `u32` | 4 bytes | Unsigned 32-bit integer | `uint32_t` | | `u64` | 8 bytes | Unsigned 64-bit integer | `uint64_t` | | `u128` | 16 bytes | [Unsigned 128-bit integer]((#extended-width-integers-u128u512-i128i512)) | `__uint128_t` | | `u256` | 32 bytes | [Unsigned 256-bit integer]((#extended-width-integers-u128u512-i128i512)) | | | `u512` | 64 bytes | [Unsigned 512-bit integer]((#extended-width-integers-u128u512-i128i512)) | | | `i8` | 1 byte | Signed 8-bit integer | `int8_t` | | `i16` | 2 bytes | Signed 16-bit integer | `int16_t` | | `i32` | 4 bytes | Signed 32-bit integer | `int32_t` | | `i64` | 8 bytes | Signed 64-bit integer | `int64_t` | | `i128` | 16 bytes | [Signed 128-bit integer](#extended-width-integers-u128u512-i128i512) | `__int128_t` | | `i256` | 32 bytes | [Signed 256-bit integer](#extended-width-integers-u128u512-i128i512) | | | `i512` | 64 bytes | [Signed 512-bit integer](#extended-width-integers-u128u512-i128i512) | | | `usize` | Platform-dependent | Unsigned, pointer-width integer | `size_t` | | `isize` | Platform-dependent | Signed, pointer-width integer | `ptrdiff_t` | Integer literals default to signed. Use a type suffix to specify: ```kairo var a = 42 // i32 (default) var b = 42u8 // u8 var c = 42i64 // i64 var d = 1_000_000 // i32 underscores are ignored, use freely as separators var e = 0xFF // i32 hexadecimal var f = 0b1010_0011 // i32 binary var g = 0o77 // i32 octal ``` ### Overflow behavior Unsigned integer overflow wraps around (modular arithmetic). Signed integer overflow behavior depends on the build mode: - **Debug:** crashes with a diagnostic. - **Release:** wraps around silently. This matches Rust's overflow model and catches bugs during development without paying for checks in production. ### Extended-width integers (u128-u512, i128-i512) If the target hardware supports wide registers (e.g., AVX-512), these types map directly to hardware. Otherwise, the compiler stores them as structs of smaller integers and emits SIMD-accelerated arithmetic when available, falling back to scalar multi-word operations. Extended-width integers are always stack-allocated they are value types, not heap-allocated objects. --- ## Floating-Point All floating-point types follow the IEEE 754 standard. The default float type is `f64` if a literal doesn't fit in `f64`, the compiler promotes to the smallest float type that can hold the value, up to `f512`. | Type | Size | Precision | C++ Equivalent | |---|---|---|---| | `f16` | 2 bytes | Half (IEEE 754-2008) | `_Float16` | | `f32` | 4 bytes | Single | `float` | | `f64` | 8 bytes | Double | `double` | | `f128` | 16 bytes | Quadruple | `__float128` | | `f256` | 32 bytes | Extended (software) | | | `f512` | 64 bytes | Extended (software) | | ```kairo var x = 3.14 // f64 (default) var y = 3.14f32 // f32 var z = 1.0e-10 // f64 scientific notation ``` Overflow produces `inf`, underflow produces `0.0`. Operations that produce `NaN` (e.g., `0.0 / 0.0`, `sqrt(-1.0)`) propagate `NaN` per IEEE 754 no crash, no trap. Check for `NaN` explicitly with `std::is_nan()` when needed. > [!NOTE] > `f256` and `f512` are not natively supported on any current hardware and are implemented entirely in software, > using SIMD instructions when available. Like extended-width integers, they are stack-allocated value types. > Expect significantly lower performance compared to hardware-backed float types. --- ## Implicit Conversions Integer and float types can be **implicitly widened** `i32` to `i64`, `f32` to `f64` but narrowing conversions require an explicit cast. See [Casting](/docs/language/casting) for details. ```kairo var a: i32 = 42 var b: i64 = a // ok: implicit widening var c: i64 = 1000 var d: i8 = c // compile error: narrowing requires explicit cast var e: i8 = c as i8 // ok: explicit, may truncate ``` --- ## Bool | Type | Size | C++ Equivalent | |---|---|---| | `bool` | 1 byte | `bool` | ```kairo var flag = true var other = false ``` `bool` is 1 byte in memory (not 1 bit) for addressability. Only `true` and `false` are valid values no implicit conversion from integers. --- ## Char | Type | Size | Description | C++ Equivalent | |---|---|---|---| | `char` | 4 bytes | Unicode scalar value (U+0000-U+10FFFF) | `char32_t` | A `char` holds a single decoded Unicode codepoint. It is always 4 bytes regardless of which codepoint it represents. ```kairo var letter = 'A' var emoji = '😶‍🌫' var cjk = '漢' ``` > [!NOTE] > `char` is the *decoded* representation of a single codepoint. Strings store text as UTF-8 bytes internally, > not as arrays of `char`. See [Strings](#strings) below. --- ## Byte | Type | Size | C++ Equivalent | |---|---|---| | `byte` | 1 byte | `std::byte` | `byte` is semantically identical to `u8` in size and representation but restricted to bitwise operations and comparisons no arithmetic. It represents raw data where the value is not meant to be interpreted as a number. ```kairo var b: byte = 0xFF var mask: byte = 0x0F var result = b & mask // ok: bitwise AND // var bad = b + mask // compile error: arithmetic not allowed on byte ``` --- ## Strings | Type | Size | Encoding | C++ Equivalent | |---|---|---|---| | `string` | 32 bytes | UTF-8 | `std::string` | Strings are UTF-8 encoded byte sequences. The `string` type uses small string optimization (SSO); short strings are stored inline without a heap allocation (exact threshold subject to change until the standard library is finalized). Longer strings are heap-allocated. ```kairo var greeting = "Hello, Kairo! 📣" // 18 UTF-8 bytes fits in SSO var name = "Name" // 7 bytes SSO ``` Because UTF-8 is a variable-width encoding, indexing by codepoint (`s[i]`) complexity depends on how the string was constructed. For string literals, the compiler pre-populates a breadcrumb cache at codegen time mapping codepoint positions to byte offsets, making indexing O(1) with zero runtime cost. For strings constructed at runtime from a raw pointer, no cache is available and indexing is O(n). Indexing by byte (`s.bytes[i]`) is always O(1) but returns raw `u8` bytes, not characters. ```kairo var s = "Hello 📣" s.bytes[0] // byte: 0x48 ('H') O(1) s[6] // char: '📣' codepoint indexing, O(1) for literals (compiler cache), O(n) for runtime-constructed strings for ch in s { // ch is char decoded codepoint, yielded sequentially } ``` > [!IMPORTANT] > The stdlib API for strings is still being finalized. Detailed documentation for string methods will be added > in a future update. --- ## Void | Type | Size | C++ Equivalent | |---|---|---| | `void` | 0 bytes | `void` | `void` indicates the absence of a value. It can be used as a normal type and as the target of an `unsafe` pointer (`unsafe *void`), using `void` as a normal type denotes a unit type. ```kairo fn log(msg: string) -> void { // ... } var opaque: unsafe *void = get_handle() // raw, untyped pointer var void_t: MyObj = MyObj() // void is valid here ``` --- ## Pointers | Type | Size | Description | |---|---|---| | `*T` | 8 bytes | Safe pointer non-null, compiler-tracked | | `unsafe *T` | 8 bytes | Raw pointer nullable, no safety checks | `*T` is a thin pointer (8 bytes). It is non-null by construction and supports pointer arithmetic when the compiler can track its provenance via [AMT](/docs/language/amt). See [Pointers](/docs/language/pointers) for full details. `unsafe *T` is a raw C-style pointer with no compiler tracking. It can be null, and dereferencing a null `unsafe *T` is undefined behavior. Use `unsafe *T` for [C/C++ interop](/docs/language/c-c++), custom allocators, and other low-level scenarios. See [Pointers](/docs/language/pointers) and [Unsafe](/docs/language/unsafe) for full details. ```kairo var x = 42 var p: *i32 = &x // safe pointer to x var q: unsafe *i32 = unsafe &x // raw pointer, no tracking ``` --- ## Collections Collections are built-in generic types with literal syntax. All are heap-allocated except fixed-size arrays. ### Vectors `[T]` A growable, owning, contiguous array. Layout: `ptr + len + cap` (24 bytes). ```kairo var nums: [i32] = [1, 2, 3] nums.push(4) nums[0] // 1 bounds-checked ``` When borrowed as `const [T]`, a vector acts as a non-owning view with `cap` set to zero no growth permitted, no deallocation on drop. See [Ownership](/docs/language/ownership) for borrowing semantics. ### Arrays `[T; N]` A fixed-size array allocated inline (stack or struct). `N` must be a compile-time constant. ```kairo var rgb: [u8; 3] = [255, 128, 0] // rgb.push(42) // compile error: fixed size ``` ### Maps `{K: V}` A hash map from keys of type `K` to values of type `V`. ```kairo var ages: {string: i32} = {"Alice": 30, "Bob": 25} ages["Charlie"] = 35 ``` ### Sets `{T}` A hash set of unique elements. ```kairo var primes: {i32} = {2, 3, 5, 7, 11} ``` ### Tuples `(T1, T2, ...)` A fixed-size, heterogeneous, ordered group of values. Stored contiguously with padding for alignment. ```kairo var point: (f64, f64) = (1.0, 2.0) var record: (i32, string, bool) = (42, "Answer", true) ``` ### Function Pointers `fn (T1, T2, ...) -> R` A pointer to a function with the given signature. Platform-dependent size. ```kairo fn add(a: i32, b: i32) -> i32 { return a + b } var operator: fn (i32, i32) -> i32 = add op(3, 4) // 7 ``` > [!IMPORTANT] > The stdlib API for vectors, maps, and sets is still being finalized. Detailed method documentation will be > added in a future update. --- ## Platform-Dependent Sizes `usize` and `isize` match the target platform's pointer width: | Platform | `usize` / `isize` | |---|---| | 64-bit | 8 bytes | | 32-bit | 4 bytes | | 16-bit | 2 bytes | --- ## Summary ```kairo // Integers var a = 42 // i32 var b = 42u8 // u8 var c = 0xFF // i32 (hex) var d = 0b1010 // i32 (binary) var e = 1_000_000 // i32 (underscores as separators) // Floats var f = 3.14 // f64 var g = 3.14f32 // f32 // Bool, char, string var h = true // bool var i = '📣' // char (4 bytes, Unicode scalar) var j = "Hello, Kairo!" // string (UTF-8, SSO threshold TBD) // Byte var k: byte = 0xFF // raw byte, no arithmetic // Pointers var x = 42 var p = &x // *i32 var q: unsafe *i32 = unsafe &x // Collections var nums: [i32] = [1, 2, 3] // vector var rgb: [u8; 3] = [255, 128, 0] // array var ages: {string: i32} = {"Alice": 30, "Bob": 25} // map var primes: {i32} = {2, 3, 5, 7} // set var point: (f64, f64) = (1.0, 2.0) // tuple // Function pointer fn add(a: i32, b: i32) -> i32 { return a + b } var operator: fn (i32, i32) -> i32 = add ``` --- ## Variables & Bindings URL: https://www.kairolang.org/docs/language/variables/ # Variables & Bindings | Keyword | Mutable | Storage | Initializer | Type annotation | |---|---|---|---|---| | `var` | Yes | Automatic | Optional (default-initialized) | Optional (inferred) | | `const` | No | Automatic | Required | Optional (inferred) | | `static` | Yes | Static | Optional | Required | | `eval` | No | Compile-time | Required | Optional (inferred) | > Variables in Kairo are mutable by default. `const` bindings at local scope require an initializer, but > class-level `const` members can be left uninitialized at declaration and assigned exactly once in the > constructor body. See [Classes](/docs/language/classes#const-members-in-constructors) for details. ```kairo var x = 42 var y: string = "Hello, World!" ``` Multi-variable declarations on a single line are not supported. Each binding gets its own statement. --- ## Naming Conventions Kairo recommends the following conventions. They are not enforced as hard errors, but the compiler emits readability warnings when they are not followed. | Kind | Convention | Example | |---|---|---| | Variables | snake_case | `my_value` | | Functions | snake_case | `get_name` | | Constants | UPPER_SNAKE_CASE | `MAX_SIZE` | | Types (classes, structs, enums) | PascalCase | `HttpServer` | --- ## Type Inference The compiler infers the type from the initializer when no annotation is provided. Explicit annotations are optional but can be used to force a specific type. ```kairo var a = 42 // inferred as i32 var b = 3.14 // inferred as f64 var c: u8 = 42 // explicitly u8 var d = "hello" // inferred as string ``` See [Primitives](/docs/language/primitives) for default type rules integer literals default to `i32`, float literals default to `f64`. --- ## Declaration Shorthands When the type is fully determined by the initializer, the `*` or `unsafe *` qualifier can be placed on the variable name instead of writing out the full type annotation: ```kairo var *ptr = @create i32(10) // equivalent to: var ptr: *i32 = @create i32(10) var unsafe *raw = unsafe std::alloc(sizeof i32 * 1) // equivalent to: var raw: unsafe *i32 = unsafe std::alloc(sizeof i32 * 1) ``` The nullable shorthand `?` works the same way: ```kairo var name? = get_name() // equivalent to: var name: string? = get_name() ``` These are purely syntactic sugar the compiler infers the full type from the right-hand side. The long form is always valid and preferred when clarity matters. --- ## Default Initialization All types with a default constructor are zero-initialized when declared without an initializer. Integers default to `0`, booleans to `false`, strings to `""`, collections to empty. ```kairo var a: i32 // 0 var b: string // "" var c: bool // false var d: [i32] // [] ``` If a type has its default constructor explicitly deleted, declaring a variable without an initializer produces a compiler warning for uninitialized state. ```kairo class Token { fn Token() = delete } var t: Token // warning: t is uninitialized (suppress with @no_warn(UNINIT)) ``` --- ## Constants Immutable bindings use `const`. A `const` variable cannot be reassigned after initialization. ```kairo const x = 42 const name: string = "Kairo" // x = 100 // compile error: x is const ``` Unlike `var`, a `const` binding without an initializer is a hard compile error constants must always be explicitly initialized. ```kairo const x: i32 // compile error: const requires an initializer ``` --- ## Compile-Time Constants (`eval`) For values that must be resolved at compile time, use `eval`. This is equivalent to C++'s `consteval` the expression **must** be evaluable at compile time, and a compile error is raised if it cannot be. ```kairo eval PI = 3.14159 eval MAX_SIZE = 1024 * 1024 ``` `eval` bindings are implicitly `const`. See [Eval](/docs/language/eval) for full details on compile-time evaluation, including `eval` functions and restrictions. --- ## Static Variables `static` declares a binding with static storage duration it lives for the entire program. Unlike `var`, `static` requires an explicit type annotation. ```kairo static counter: i32 = 0 ``` `static` can be combined with `const` for immutable static data. See [Modules](/docs/language/modules) for `static` at module scope. --- ## Shadowing Constants can shadow previous bindings of the same name. Each `const` declaration creates a new binding the previous one becomes inaccessible. ```kairo const raw = "42" const raw = std::parse(raw) // shadows string with i32 valid const raw = raw * 2 // shadows again valid ``` Mutable variables cannot shadow. Redeclaring a `var` with the same name in the same scope is a compile error. ```kairo var x = 42 var x = 100 // compile error: var cannot shadow ``` > [!NOTE] > Shadowing is restricted to `const` because shadowing a mutable variable is almost always a bug you likely > meant to reassign (`x = 100`) rather than redeclare. --- ## Destructuring Tuples and structs can be destructured into individual bindings. Use parentheses for tuples and curly braces for structs. ### Tuples ```kairo var point = (10, 20, 30) var (x, y, z) = point // x = 10, y = 20, z = 30 ``` ### Structs ```kairo struct Color { var r: u8 var g: u8 var b: u8 } var color = Color { r: 255, g: 128, b: 0 } var {r, g, b} = color // r = 255, g = 128, b = 0 ``` Destructured names must match the field names in the struct definition. The binding order follows the definition order. ### Discards Use `_` to ignore values you don't need. `_` is not a variable it cannot be referenced after destructuring. ```kairo var (x, _, z) = (1, 2, 3) // discard the second element var {r, _, _} = color // keep only r for _ in 0..10 { // loop body doesn't need the index } ``` The compiler will error if you attempt to use `_` as a value. --- ## Nullable Types `T?` is sugar for `Nullable` a compiler-intrinsic tagged union that holds either a value of type `T` or null. It applies to any type, not just pointers. ```kairo var name: string? = get_name() // may be null var count: i32? = null // explicitly null ``` ### Shorthand declaration The `?` suffix on the variable name infers the nullable type from the initializer: ```kairo var name? = get_name() // inferred as string? // var x? = null // compile error: no underlying type to infer ``` ### Null checking The `?` suffix on a variable name in a condition checks for non-null: ```kairo var result? = find_user("alice") if result? { // result is non-null here std::println(result.name) } ``` Accessing members on an unchecked nullable is a compile error: ```kairo var user? = find_user("alice") user.name // compile error: user has not been null-checked ``` ### Safe access (`?.`) The `?.` operator calls a method or accesses a member only if the value is non-null. If null, it default-constructs the type and calls the method on that instead: ```kairo var config? = load_config() config?.timeout // if null, uses Config().timeout ``` If the type is not trivially default-constructible (deleted default constructor, contains atomic types), `?.` is a compile error. ### Null coalescing (`??`) `??` provides a fallback value when the left side is null. Both sides must be the same underlying type: ```kairo var value = get_f32() ?? 0.212 // value is f32, not f32? var name = get_name() ?? "anonymous" ``` ### Force unwrap `unwrap!()` extracts the value or panics if null: ```kairo var x = unwrap!(maybe_value) // panics if null ``` `unwrap!()` desugars to a null check followed by a `panic` on the null path. The caller must be inside a `try` block or in a function with the `panic` specifier. See [Panic](/docs/language/panic) for the panic model. ### Const interaction `const` on a nullable binding works the same as on any other type the binding cannot be reassigned after initialization: ```kairo const x: i32? = 42 x = null // compile error: x is const x = 100 // compile error: x is const var y: i32? = null y = 42 // ok: var is mutable y = null // ok: can go back to null ``` ### Pointers and nullability `*T` is non-null by construction, it cannot hold `&null`. No null checks are needed on dereference because the pointer is always valid: ```kairo var x = 42 var ptr: *i32 = &x *ptr = 10 // guaranteed valid, no check var bad: *i32 = &null // compile error: *T is non-null ``` To represent a pointer that might not exist, use `*T?`. This wraps the pointer in the standard `Nullable` system with full support for `?`, `?.`, `??`, and `unwrap!()`: ```kairo var ptr: *i32? = find_pointer() if ptr? { std::println(*ptr) // compiler knows ptr is non-null here } var val = ptr ?? &fallback // use fallback if null var forced = unwrap!(ptr) // panics if null ``` For raw nullable pointers with no compiler tracking, use `unsafe *T` with manual null comparison: ```kairo var raw: unsafe *i32 = &null if raw != &null { std::println(*raw) } ``` See [Pointers](/docs/language/pointers) for the full pointer model and how [AMT](/docs/language/amt) tracks pointer provenance. --- ## The `const` Binding Rule `const` in Kairo follows a strict **left-to-right binding rule**: one `const` applies to the thing immediately to its right. This eliminates the ambiguity that plagues C/C++ `const` placement. ### On simple variables ```kairo const x: i32 = 42 // ^ x cannot be reassigned // ^^^ i32 is the type immediately right of const the value is immutable ``` ### On pointers `const` on the binding and `const` on the pointed-to type are independent axes: ```kairo var ptr: *i32 = &x // ptr is mutable, *ptr is mutable can reassign ptr and modify the target const ptr: *i32 = &x // ptr is const cannot reassign ptr to point elsewhere // *ptr is mutable can still modify the target value *ptr = 10 // ok: allowed ptr = &y // compile error: const ptr: *const i32 = &x // ptr is const cannot reassign // *ptr is const cannot modify the target *ptr = 10 // compile error: ptr = &y // compile error: ``` The rule scales to any depth: ```kairo const ptr: *const *i32 = &ptr2 ptr = &ptr3 // ptr is: const *ptr = &x // *ptr is: const **ptr = 10 // ok: the i32 at the end is not const ``` ### A practical example Consider a configuration object that should be readable through a pointer but never modified: ```kairo class Config { pub var host: string pub var port: i32 fn Config(self, host: string, port: i32) { self.host = host self.port = port } fn address(const self) -> string { return f"{self.host}:{self.port}" } fn set_port(self, port: i32) { self.port = port } } var config = Config("localhost", 8080) const ptr: *const Config = &config ptr->address() // ok: address() is a const method ptr->set_port(9090) // compile error: set_port() mutates, ptr points to const Config // in most cases tho you would only want this: var ptr: *const Config = &config // cause you would have a const type but still be able to reassign the pointer if needed. ``` ### On types `const` applied to a type restricts the instance to const methods only methods not marked `const` cannot be called. ```kairo const server = Config("localhost", 8080) server.address() // ok: const method server.set_port(9090) // compile error: set_port() is not const ``` > [!WARNING] > `var x: const i32 = 42` is a compile error. The `const` binding rule is strictly left-to-right from the > declaration keyword position. Use `const x: i32 = 42` instead. This avoids the `const int* x` vs > `int* const x` confusion that C++ is notorious for. --- ## Scope and Lifetime Variables are block-scoped and destroyed at the end of their enclosing block. ```kairo { var x = 42 // x is live here } // x is no longer accessible ``` [AMT](/docs/language/amt) performs full-program analysis to track lifetimes automatically no lifetime annotations are required. If AMT determines that a pointer outlives its referent and cannot automatically promote the pointer (to a shared, weak, or unique smart pointer), it emits a compile error. ```kairo var x = 10 var y = &x { *y = 15 // ok: x is still alive, y is valid } std::println(*y) // AMT error: y's safety cannot be guaranteed at this point ``` See [AMT](/docs/language/amt) and [Ownership](/docs/language/ownership) for the full lifetime and borrowing model. --- All four declaration keywords `var`, `const`, `static`, `eval` work in both local and class/struct scope. --- ## Operators URL: https://www.kairolang.org/docs/language/operators/ # Operators Kairo's operators follow C-style precedence and semantics with a few additions: exponentiation (`^^`), deep equality (`===`), null-safe access (`?.`, `?->`), and ranges (`..`, `..=`). All operators can be overloaded for user-defined types. --- ## Arithmetic | Operator | Description | Example | |---|---|---| | `+` | Addition / unary plus | `a + b`, `+a` | | `-` | Subtraction / unary negation | `a - b`, `-a` | | `*` | Multiplication | `a * b` | | `/` | Division | `a / b` | | `%` | Modulo (remainder) | `a % b` | | `^^` | Exponentiation | `2 ^^ 8` -> 256 | Integer division truncates toward zero, matching C++. `^^` works on any integer combination (`i32 ^^ i32`, `u64 ^^ u8`, etc.) and on float bases with integer exponents (`f64 ^^ i32`). Overflow follows the same rules as other arithmetic see below. ### Integer overflow Unsigned overflow wraps around (modular arithmetic). Signed overflow depends on the build mode: - **Debug:** crashes with a diagnostic. - **Release:** wraps around silently. See [Primitives](/docs/language/primitives) for full details on numeric type behavior. ### Floating-point overflow Overflow produces `inf`, underflow produces `0.0`. Operations that produce `NaN` (e.g., `0.0 / 0.0`, `sqrt(-1.0)`) propagate `NaN` per IEEE 754 no crash, no trap. Check explicitly with `std::is_nan()`. --- ## Comparison | Operator | Description | Example | |---|---|---| | `==` | Equality | `a == b` | | `!=` | Inequality | `a != b` | | `<` | Less than | `a < b` | | `>` | Greater than | `a > b` | | `<=` | Less than or equal | `a <= b` | | `>=` | Greater than or equal | `a >= b` | | `<=>` | Three-way comparison (spaceship) | `a <=> b` | | `===` | Deep equality | `a === b` | `<=>` returns an ordering value, matching C++20 spaceship operator semantics. ### `==` vs `===` On pointers, `==` compares **addresses** whether two pointers point to the same memory location. `===` dereferences both pointers and compares the values they point to. `===` is defined only on `*T`, which is non-null by construction, so no null check is needed. For a pointer that might be null, use `*T?` and the nullable operators (?, ??) before comparing. ```kairo var z = 42 var x = &z var y = &z x == y // true same address x === y // true dereferenced values are equal ``` ```kairo var a = 42 var b = 42 var p = &a var q = &b p == q // false different addresses p === q // true both point to 42 ``` For user-defined types, `===` can be overloaded to implement deep equality. By default it is only defined for pointer types. --- ## Logical | Operator | Description | Example | |---|---|---| | `&&` | Logical AND | `a && b` | | `\|\|` | Logical OR | `a \|\| b` | | `!` | Logical NOT | `!a` | Short-circuit evaluation applies: `&&` does not evaluate the right operand if the left is `false`, and `||` does not evaluate the right operand if the left is `true`. This is identical to C++. --- ## Bitwise | Operator | Description | Example | |---|---|---| | `&` | Bitwise AND | `a & b` | | `\|` | Bitwise OR | `a \| b` | | `^` | Bitwise XOR | `a ^ b` | | `~` | Bitwise NOT (complement) | `~a` | | `<<` | Left shift | `a << 2` | | `>>` | Right shift | `a >> 2` | Right shift is arithmetic (sign-extending) for signed types and logical (zero-filling) for unsigned types. --- ## Assignment | Operator | Description | |---|---| | `=` | Assignment | | `+=`, `-=`, `*=`, `/=`, `%=` | Arithmetic compound assignment | | `&=`, `\|=`, `^=` | Bitwise compound assignment | | `<<=`, `>>=` | Shift compound assignment | All compound assignment operators desugar to `x = x op y`. --- ## Increment and Decrement | Syntax | Name | Behavior | |---|---|---| | `++x` | Prefix increment | Increments `x`, returns the new value | | `x++` | Postfix increment | Returns the current value, then increments `x` | | `--x` | Prefix decrement | Decrements `x`, returns the new value | | `x--` | Postfix decrement | Returns the current value, then decrements `x` | > [!WARNING] > Expressions that modify a variable multiple times without a sequence point (e.g., `i = i++ + ++i`) are > **undefined behavior**. This is inherited from C++ evaluation order semantics. Avoid combining multiple > side effects on the same variable in a single expression. --- ## Ranges | Operator | Description | Example | |---|---|---| | `..` | Exclusive range (end excluded) | `0..10` -> 0 through 9 | | `..=` | Inclusive range (end included) | `0..=10` -> 0 through 10 | Range operators produce a `Range` object that implements iteration. Any type that satisfies the `Steppable` interface can be used with range operators: ```kairo interface Steppable { fn op l++ (self) -> Steppable // step forward (prefix increment) fn op == (self, other: T) -> bool } ``` > [!NOTE] > Types do not need to explicitly `impl Steppable`. If a type satisfies the required method signatures, it > is automatically compatible with range operators. See [Interfaces](/docs/language/interfaces) for details > on structural conformance. Ranges also work with slicing on strings and collections: ```kairo "hello"[1..4] // "ell" [1, 2, 3, 4][0..2] // [1, 2] ``` --- ## Null-Safe Access Kairo provides null-safe operators for working with nullable types (`T?`). | Operator | Description | Example | |---|---|---| | `?.` | Null-safe member access | `obj?.field` | | `?->` | Null-safe pointer deref + member access | `ptr?->field` | | `?.*` | Null-safe deref member pointer | `obj?.*member_ptr` | | `?->*` | Null-safe pointer deref + member pointer deref | `ptr?->*member_ptr` | If the left-hand side is null, the entire expression evaluates to null instead of crashing. ```kairo var user: User? = find_user("alice") var name = user?.get_name() // string? null if user is null ``` The non-null equivalents follow the same pattern without the safety check: | Operator | Description | |---|---| | `.` | Member access | | `->` | Pointer dereference + member access | | `.*` | Dereference member pointer | | `->*` | Pointer dereference + member pointer dereference | --- ## Type Inspection | Keyword | Return type | Description | |---|---|---| | `sizeof T` | `usize` | Size of type `T` in bytes | | `alignof T` | `usize` | Alignment requirement of type `T` in bytes | | `typeof expr` | Context-dependent | Type identity see below | `typeof` has dual behavior depending on context: ```kairo var x = 42 var y: typeof x = 100 // type position compiler substitutes i32 var info = typeof x // expression position returns a TypeInfo struct info.get_pretty_name() // "i32" info.get_size() // 4 ``` See [Type System](/docs/language/type-system) for full `TypeInfo` details. --- ## Type Casting `as` `as` performs explicit type conversion. No implicit narrowing conversions exist in Kairo all narrowing casts must use `as`. Implicit widening (e.g., `i32` to `i64`) is permitted without `as`. ```kairo var x: i64 = 1000 var y: i8 = x as i8 // explicit narrowing may truncate var f: f64 = 3.14 var i: i32 = f as i32 // 3 truncates toward zero ``` `as` is overloadable for user-defined types: ```kairo class Vector2D { var x: f32 var y: f32 fn op as (self) -> string { return f"Vector2D({self.x}, {self.y})" } } var v = Vector2D { x: 1.0, y: 2.0 } var s = v as string // "Vector2D(1.0, 2.0)" ``` See [Casting](/docs/language/casting) for the full conversion rules. --- ## Operator Overloading Operators are overloaded by defining `fn op` methods on a class, [struct](/docs/language/structures) OR (via [extends](/docs/language/extends)). The syntax mirrors the operator being defined. #### All Overload-able Operators for User-Defined Types | Operator | Overload-able | Const-overloadable | Notes | |---|---|---|---| | `+` `-` `*` `/` `%` | ok | no | Returns a new value. | | Unary `+` `-` | ok | no | Returns a new value. | | `^^` | ok | no | Returns a new value. | | `<<` `>>` | ok | no | Returns a new value. | | `&` `\|` `^` `~` (bitwise) | ok | no | Returns a new value. | | `==` `!=` | ok | no | Returns `bool`. | | `<` `<=` `>` `>=` | ok | no | May be implemented via `<=>`. | | `<=>` | ok | no | Returns `Ordering`. | | `===` | ok | no | Returns `bool`. | | `l++` `r++` `l--` `r--` | ok | no | Mutating operators. | | `[]` | ok | ok | Returns a place into `self` (`*T` / `*const T`). | | `->` | ok | ok | Returns a place into `self` (`*T` / `*const T`). | | `.*` | ok | ok | Returns a place into `self` (`*T` / `*const T`). | | `->*` | ok | ok | Returns a place into `self` (`*T` / `*const T`). | | `in` (containment) | ok | no | Returns `bool`. | | `in` (iteration) | ok | no | Returns `yield T`. | | `as` | ok | no | Returns a converted value. | | `delete` | ok | no | Custom destructor. | | `await` | ok | no | Returns the awaited value. | | `=` | no | no | Generated by the compiler; cannot be overloaded. | | `+=` `-=` `*=` `/=` `%=` `&=` `\|=` `^=` `<<=` `>>=` | no | no | Desugar to the corresponding binary operator and assignment. | | `.` `::` | no | no | Language syntax; not overloadable. | | Unary `&` `*` | no | no | Built-in pointer operations. | | `&&` `\|\|` `!` | no | no | Preserve built-in short-circuit semantics. | | `?.` `?->` `?.*` `?->*` | no | no | Derived automatically from the corresponding non-null-safe operator. | | `..` `..=` | no | no | Implemented through the `Steppable` interface. | | `sizeof` `alignof` `typeof` | no | no | Compile-time language keywords. | ### Standard binary and unary operators ```kairo class Vec3 { var x: f32 var y: f32 var z: f32 fn op + (self, other: Vec3) -> Vec3 { return Vec3 { x: self.x + other.x, y: self.y + other.y, z: self.z + other.z } } fn op - (self) -> Vec3 { // unary negation return Vec3 { x: -self.x, y: -self.y, z: -self.z } } fn op == (self, other: Vec3) -> bool { return self.x == other.x && self.y == other.y && self.z == other.z } } ``` ### Increment and decrement Use the `l` (left/prefix) or `r` (right/postfix) modifier to specify which variant you are overloading. The compiler warns if the modifier is omitted. ```kairo fn op r-- (self) -> T // postfix: x-- fn op l-- (self) -> T // prefix: --x fn op l++ (self) -> T // prefix: ++x the ++ is on the left of the operand fn op r++ (self) -> T // postfix: x++ the ++ is on the right of the operand ``` # Const Overloading Operators Place-returning operators (`[]`, `->`, `.*`, `->*`) may overload on receiver const-ness, the one exception to the const-overload restriction that applies to named methods. The `const self` overload must return `*const T` where the `self` overload returns `*T`. This exception exists because operators cannot be renamed. See [Functions](/docs/language/functions#const-overloading-restriction) for details. ```kairo class Buffer { priv var data: [u8] fn op [] (self, index: usize) -> *u8 { return &self.data[index] } fn op [] (const self, index: usize) -> *const u8 { return &self.data[index] } } var buff = Buffer { data: [1, 2, 3] } var p: u8 = buff[1] // calls the const overload and compiler infers `*u8` -> `u8` // NOTE: this behavior is only for Place-returning operators, a named method returning `*T` would not be implicitly dereferenced buff[1] = 42 // calls the non-const overload ``` ### Special operators | Operator | Signature | Description | |---|---|---| | `as` | `fn op as (self) -> TargetType` | Type conversion takes no parameters | | `===` | `fn op === (self, other: T) -> bool` | Deep equality | | `in` (containment) | `fn op in (self, other: T) -> bool` | `if item in collection` checks membership | | `in` (iteration) | `fn op in (self) -> yield T` | `for x in collection` yields elements | | `delete` | `fn op delete (self)` | Custom destructor called when the value goes out of scope | The two `in` signatures are distinguished by context: the compiler uses the `-> bool` variant in `if` expressions and the `-> yield T` variant in `for` loops. ```kairo class IntSet { priv var data: [i32] fn op in (self, value: i32) -> bool { // containment check: "if 5 in my_set" for item in self.data { if item == value { return true } } return false } fn op in (self) -> yield i32 { // iteration: "for x in my_set" for item in self.data { yield item } } } var s = IntSet { data: [1, 2, 3] } if 2 in s { /* ... */ } // calls the bool variant for x in s { // calls the yield variant std::println(f"{x}") } ``` ### Delete Operator `op delete` defines custom destruction logic. If not defined, the compiler generates a default destructor. If any member has a deleted destructor (`fn op delete() = delete`), the containing type's destructor is also deleted and instances must be managed in an [unsafe](/docs/language/unsafe) context. > [!NOTE] > The `op delete` operator is **not** the same as C++'s `delete` operator. Kairo's `op delete` is simply a cleanup hook that can either be explicitly called or automatically invoked when a value goes out of scope. It is not used to free memory allocated with `std::create`. ```kairo class FileHandle { priv var fd: i32 fn op delete (self) { close(self.fd) } } ``` See [AMT](/docs/language/amt) for details on destruction order and allocator interaction. > [!CAUTION] > Overloading operators in an `unsafe` context is not permitted. All operator overloads must be safe. --- ## Precedence Operators are listed from highest precedence (tightest binding) to lowest. Operators on the same row have equal precedence and associate in the direction shown. | Precedence | Operators | Associativity | Description | |---|---|---|---| | 1 | `::` | Left | Scope resolution | | 2 | `()` `[]` `.` `->` `.*` `->*` `?.` `?->` `?.*` `?->*` | Left | Postfix / member access | | 3 | `++` `--` (postfix) | Left | Postfix increment/decrement | | 4 | `++` `--` (prefix) `!` `~` `+` `-` (unary) `*` `&` `sizeof` `alignof` `typeof` | Right | Prefix / unary | | 5 | `^^` | Right | Exponentiation | | 6 | `*` `/` `%` | Left | Multiplicative | | 7 | `+` `-` | Left | Additive | | 8 | `<<` `>>` | Left | Bitwise shift | | 9 | `<=>` | Left | Three-way comparison | | 10 | `<` `<=` `>` `>=` | Left | Relational | | 11 | `==` `!=` `===` | Left | Equality | | 12 | `&` | Left | Bitwise AND | | 13 | `^` | Left | Bitwise XOR | | 14 | `\|` | Left | Bitwise OR | | 15 | `&&` | Left | Logical AND | | 16 | `\|\|` | Left | Logical OR | | 17 | `..` `..=` | Left | Range | | 18 | `=` `+=` `-=` `*=` `/=` `%=` `&=` `\|=` `^=` `<<=` `>>=` | Right | Assignment | | 19 | `in` | Left | Containment / iteration | | 20 | `as` | Left | Type cast | > [!NOTE] > `==` binds tighter than `&&` and `||` compound conditions like `a == b && c == d` do not require > explicit parentheses. This matches C/C++ precedence. When in doubt, use parentheses. The compiler does not warn about precedence ambiguity, but explicit grouping improves readability. --- ## Evaluation Order Evaluation order of subexpressions is **undefined** in Kairo. Given `f(a(), b())`, there is no guarantee that `a()` executes before `b()`. This is inherited from C++ semantics. > [!WARNING] > Do not rely on evaluation order for correctness. Expressions with multiple side effects on the same variable > in a single statement are undefined behavior. --- ## Operators Not in Kairo For C++ developers the following C++ operators have no equivalent in Kairo: | C++ Operator | Kairo Alternative | |---|---| | `? :` (ternary) | `if`/`else` expressions | | `,` (comma operator) | Not supported use separate statements | | `new` / `delete` | `std::create` / automatic via AMT, or `op delete` for custom destructors | | `typeid` | `typeof expr` returns `TypeInfo` | | `const_cast` / `reinterpret_cast` / `static_cast` / `dynamic_cast` | `as` for safe casts; see [Casting](/docs/language/casting) | --- ## Control Flow URL: https://www.kairolang.org/docs/language/control-flow/ # Control Flow Kairo's control flow is block-scoped and brace-delimited. Every branch, loop, and error-handling construct uses `{ ... }` there are no single-statement bodies. Conditions are bare expressions (no parentheses required, though permitted for clarity). --- ## Conditionals ### `if` / `else if` / `else` ```kairo if x > 0 { std::println("positive") } else if x == 0 { std::println("zero") } else { std::println("negative") } ``` Braces are mandatory on all branches. The condition must evaluate to `bool` no implicit conversion from integers or pointers. ### `if` as an expression (ternary equivalent) Kairo has no ternary `? :` operator. Use `if`/`else` as an expression instead, the expression in each branch is the result: ```kairo var x = if condition { 10 } else { 20 } var y = if a > b { a } else if a == b { 0 } else { b } ``` When used as an expression, the `else` branch is required and all branches must produce the same type. Empty branches are not permitted in expression form. > [!TIP] > The compiler warns if `else if` nesting exceeds 3 levels. Consider restructuring deeply nested conditionals > into a `match` or separate function. ### Empty branches Empty branches are legal in statement form. The compiler will not warn this is intentional for cases where only one side of a conditional has work to do: ```kairo if condition { // intentionally empty } else { handle_false_case() } ``` --- ## `match` `match` is Kairo's multi-way dispatch construct. It handles value matching, enum variant dispatch, ADT destructuring, struct field extraction, and range checks all with compiler-enforced exhaustiveness. ```kairo match status_code { case 200 { std::println("OK") } case 404 { std::println("Not Found") } case 500 { std::println("Internal Server Error") } default { std::println(f"Unknown: {status_code}") } } ``` ### Value matching `match` operates on integers, strings, and booleans by comparing against literal values: ```kairo match command { case "start" { engine.start() } case "stop" { engine.stop() } case "reset" { engine.reset() } default { log_unknown(command) } } match is_valid { case true { proceed() } case false { abort() } } ``` ### Enum variant matching For plain enums, use the `.Variant` shorthand when the type is inferred from the match operand: ```kairo match dir { case .North { go_up() } case .South { go_down() } case .East { go_right() } case .West { go_left() } } ``` The fully qualified form `Direction::North` also works. The compiler verifies all variants are covered. ### ADT destructuring ADT enum variants carry payloads. Destructure them in `match` with `var` or `const` bindings inside parentheses. Field names must match the variant declaration: ```kairo enum LookupResult { Found { key: K, value: V }, NotFound { key: K }, Error { message: string }, } match result { case .Found(var key, var value) { std::println(f"{key} = {value}") } case .NotFound(var key) { std::println(f"{key} not found") } case .Error(const message) { // message cannot be reassigned bound as const log_error(message) } } ``` You can omit fields you do not need exhaustive field extraction is not required: ```kairo match result { case .Found(var value) { // only value is bound, key is ignored process(value) } default { } } ``` See [Enums](/docs/language/enums#adt-enums) for ADT enum declarations and construction. ### `where` guards Append `where` followed by a boolean expression to add a condition to a case. The branch only matches if both the pattern and the guard are satisfied: ```kairo match result { case .Found(var key, var value) where value > 0 { std::println(f"{key} has positive value") } case .Found(var key, var value) { std::println(f"{key} has non-positive value") } default { } } ``` Guards are evaluated after the pattern matches. A case with a guard that fails falls through to the next case this is the only situation where control moves between cases. ### Struct matching Structs have a single shape (no variants), so `match` on a struct is destructuring combined with guards. A struct `case` without a `where` guard always matches and is a compile error unless it is the only case or the last case (acting as a catch-all): ```kairo struct Response { var status: i32 var body: string } match response { case Response(var status, var body) where status == 200 { process(body) } case Response(var status) where status >= 400 { log_error(f"HTTP {status}") } default { // catch-all } } ``` ### Range matching Integer cases can match a range using `..` (exclusive end) or `..=` (inclusive end): ```kairo match http_status { case 200..=299 { std::println("success") } case 300..=399 { std::println("redirect") } case 400..=499 { std::println("client error") } case 500..=599 { std::println("server error") } default { std::println("unknown") } } ``` ### `match` as an expression Like `if`, `match` can be used as an expression. The last expression in each branch is the result value. All branches must produce the same type: ```kairo var label = match level { case .Debug { "debug" } case .Info { "info" } case .Warning { "warning" } case .Error { "error" } case .Fatal { "fatal" } } var priority = match code { case 200..=299 { 0 } case 400..=499 { 1 } case 500..=599 { 2 } default { -1 } } var message = match result { case .Found(var key, var value) { f"{key}: {value}" } case .NotFound(var key) { f"{key} not found" } case .Error(var message) { message } } ``` In expression form, exhaustiveness is required every possible value must be covered. For integers and strings, this means `default` is mandatory. ### Exhaustiveness The compiler verifies that all possible values are covered: | Matched type | Coverage rule | |---|---| | Plain enum | All variants covered, or `default` present | | ADT enum | All variants covered, or `default` present | | Integers / strings | `default` always required | | Booleans | `true` + `false` covers it | | Structs | `default` always required | A `default` case matches any value not covered by preceding cases. It must be the last case. ### No fall-through Each case is an isolated block. There is no fall-through between cases and no `@fallthrough` attribute. If you need multiple values to execute the same code, list them in a single case separated by commas: ```kairo match priority { case 1, 2, 3 { handle_high() } case 4, 5 { handle_medium() } default { handle_low() } } ``` ### `break` in `match` inside a loop Inside a `match` nested within a loop, `break` exits the **loop**, not the match. Match cases are already isolated blocks with no fall-through, so there is nothing to "break" out of within the match itself. ```kairo for item in items { match item.kind { case .Sentinel { break // exits the for loop } case .Data(var payload) { process(payload) } default { } } } ``` --- ## Loops ### `for` range and iterator Range-based and iterator-based `for` loops use the `in` keyword: ```kairo for i in 0..10 { // i = 0, 1, 2, ..., 9 } for i in 0..=10 { // i = 0, 1, 2, ..., 10 } for item in collection { // iterates using the collection's fn op in (self) -> yield T } ``` The range operators `..` and `..=` produce a `Range` object that implements the iterator protocol. Any type that defines `fn op in (self) -> yield T` is iterable. See [Operators](/docs/language/operators#ranges) for the `Steppable` interface and [Operators](/docs/language/operators#special-operators) for the `op in` overload. ### Multi-variable iteration If the collection yields tuples, destructure directly in the loop header: ```kairo for (key, value) in some_map { std::println(f"{key} = {value}") } for (a, b) in pairs { // a and b are the first and second elements of each yielded tuple } ``` The tuple arity in the loop header must match the arity yielded by the collection. ### `for` C-style Traditional three-part `for` loops are also supported: ```kairo for var i = 0; i < 10; i++ { // classic index loop } ``` The init clause declares a new variable scoped to the loop body. The condition and step are bare expressions. ### `while` ```kairo while condition { // loop body } ``` The condition is evaluated before each iteration. No implicit conversion from integers must be `bool`. ### `loop` `loop` is an unconditional infinite loop. It runs until an explicit `break` or `return`: ```kairo loop { var input = read_line() if input == "quit" { break } process(input) } ``` ### Labeled loops Labels allow `break` and `continue` to target a specific enclosing loop. The syntax is `label: for`/`while`/`loop`: ```kairo outer: for i in 0..10 { inner: for j in 0..10 { if some_condition { break outer // exits the outer loop entirely } if other_condition { continue inner // skips to next iteration of the inner loop } } } ``` Labels follow the same naming conventions as variables (`snake_case`). `break` and `continue` without a label target the innermost enclosing loop. --- ## `break` and `continue` | Statement | Behavior | |---|---| | `break` | Exits the innermost loop | | `break label` | Exits the loop identified by `label` | | `continue` | Skips to the next iteration of the innermost loop | | `continue label` | Skips to the next iteration of the loop identified by `label` | `break` and `continue` are statements, not expressions they do not produce values. Inside a `match` nested within a loop, `break` exits the **loop**, not the match (match cases are already isolated blocks with no fall-through by default). --- ## Error Handling ### `try` / `catch` `try`/`catch` handles panics from functions annotated with the `panic` specifier. The compiler statically verifies that `catch` blocks cover all panic types that can propagate from the `try` body uncovered types are a compile error. See [Panic](/docs/language/panic) for the full panic model. ```kairo try { risky_operation() } catch { // catch-all any panic type std::println("something went wrong") } ``` Catch blocks can filter by specific error types. The compiler checks exhaustiveness against the set of panic types declared by the called functions: ```kairo try { read_file("data.txt") } catch e: std::Error::IO { std::println(f"IO error: {e}") } catch e: std::Error::Runtime { std::println(f"Runtime error: {e}") } ``` A bare `catch` (no type) acts as a catch-all and satisfies exhaustiveness for any remaining types. ### `try`/`catch` as an expression Like `if`, `try`/`catch` can be used as an expression. The last expression in each block is the result value: ```kairo var result = try { parse_int("42") } catch e: std::Error::Runtime { -1 // fallback value } catch { 0 // default for any other error } ``` All branches must produce the same type. `finally` is not permitted in expression form. ### `finally` `finally` defines cleanup code that always runs whether the `try` body completes normally, panics, or returns early. ```kairo try { acquire_resource() do_work() } catch e: std::Error::Runtime { handle_error(e) } finally { release_resource() // always executes } ``` ### Standalone `finally` (scope exit) `finally` can also appear inside any function body without a preceding `try`. In this form, it acts as a scope exit block the body executes when the enclosing function returns, regardless of how it exits: ```kairo fn process_file(path: string) panic -> void { var fd = open(path) finally { close(fd) // runs on normal return, panic, or early return } // ... work with fd ... if bad_data { return // finally still runs } // ... more work ... } ``` This is equivalent to Go's `defer` the body executes when the enclosing function exits, regardless of how it exits (normal return, panic, or early return). Multiple `finally` blocks in the same function execute in reverse declaration order (LIFO), matching destructor semantics. --- ## `assert` `assert` evaluates a condition and panics if it is false. It takes an expression and an optional diagnostic message: ```kairo assert index < len, "index out of bounds" assert ptr != null ``` Assert behavior is globally configurable via the `-fassert-mode` compiler flag: | Mode | Behavior | |---|---| | `panic` | Panics with the provided message or a generated diagnostic | | `return` | Returns a default-constructed value of the function's return type | | `log` (default) | Logs the assertion failure and continues execution | > [!WARNING] > `return` mode silently swallows assertion failures and produces a default-constructed value. This can > mask bugs and produce incorrect results downstream. Use with caution `panic` mode is the default for > a reason. Asserts cannot appear at file scope they are only valid inside function bodies. --- ## `panic` `panic` is a statement that triggers an unrecoverable error in the current function. Functions that can panic must be annotated with the `panic` specifier in their signature, and the compiler enforces that callers handle the panic via `try`/`catch`. ```kairo fn divide(a: i32, b: i32) panic -> i32 { if b == 0 { panic std::Error::Runtime("division by zero") } return a / b } ``` See [Panic](/docs/language/panic) for the full panic model, including `Panickable`, desugaring semantics, and the zero-cost codegen strategy. --- ## `return` `return` exits the current function with a value. If the function returns `void`, `return` takes no operand. ```kairo fn max(a: i32, b: i32) -> i32 { return if a > b { a } else { b } } ``` ## Blocks and Scope A block is a brace-delimited sequence of statements. Every block introduces a new lexical scope variables declared inside are not visible outside, and destructors run at the closing brace in reverse declaration order. ```kairo { var x = 42 var y = compute(x) // x and y are live here } // x and y are destroyed and no longer accessible ``` Blocks are expressions when used as the right-hand side of a binding. The last expression in the block is the result value: ```kairo var result = { var tmp = expensive_computation() tmp * 2 // result = tmp * 2 } ``` Anonymous blocks are useful for limiting the lifetime of temporary resources without introducing a function: ```kairo fn process() { var config = load_config() { var lock = acquire_mutex() // critical section write_shared_state(config) } // lock is destroyed here mutex released do_unrelated_work() } ``` All control flow constructs (`if`, `match`, `for`, `while`, `loop`, `try`) create implicit blocks their bodies follow the same scoping and destruction rules. See [Variables](/docs/language/variables#scope-and-lifetime) and [AMT](/docs/language/amt) for full lifetime semantics. --- ## Jumps For low-level control flow (state machines, interpreters, hot loops), Kairo provides labeled jumps via compiler intrinsics: ```kairo label!(entry) // ... code ... jump!(entry) // unconditional jump to 'entry' ``` `jump!` can only target labels within the same function cross-function jumps are a compile error. Labels declared with `label!` are not the same as loop labels; they are raw branch targets with no structured control flow guarantees. > [!CAUTION] > `jump!` bypasses destructors and `finally` blocks. Jumping over a variable's declaration and then > referencing it is undefined behavior. Use structured control flow (`loop`, `break`, `return`) unless you > have a concrete reason not to. Kairo does not have a `goto` keyword. `label!` and `jump!` are compiler intrinsics surfaced with macro syntax to make jump usage explicit and greppable in a codebase. They are not user-defined macros see [Macros](/docs/language/macros) for the macro system. --- ## Compile-Time Branching `eval if` selects a branch at compile time. The condition must be a compile-time constant expression if it is not, the compiler emits an error. Only the selected branch is included in the final program; the other branches are discarded entirely (no codegen, no type-checking). ```kairo eval if platform == "linux" { fn init() { /* linux-specific init */ } } else if platform == "windows" { fn init() { /* windows-specific init */ } } else { fn init() { /* fallback */ } } ``` This is equivalent to `#if` / `#elif` / `#else` in C/C++, but operates on Kairo's compile-time evaluation system rather than a preprocessor. See [Eval](/docs/language/eval) for details on what qualifies as a compile-time constant. --- ## Branch Hints Kairo provides attributes to communicate branch likelihood to the compiler's optimizer. These map directly to LLVM's branch weight metadata and `__builtin_expect` semantics in the generated code. ```kairo @likely if hot_path { fast_operation() } else { slow_fallback() } @unlikely if rare_error { handle_error() } ``` | Attribute | Meaning | |---|---| | `@likely` | The condition is expected to be true in the common case | | `@unlikely` | The condition is expected to be false in the common case | | `@unreachable` | The branch should never execute if it does, behavior is undefined | `@unreachable` is a hard assertion to the optimizer that the marked branch is dead code. If execution reaches an `@unreachable` branch at runtime, the behavior is undefined the compiler is free to eliminate the branch entirely and may miscompile surrounding code under that assumption. ```kairo match direction { case .North { /* ... */ } case .South { /* ... */ } case .East { /* ... */ } case .West { /* ... */ } @unreachable default { // optimizer assumes this is dead code } } ``` Branch hints cannot be applied to `eval if` branches compile-time branches are resolved before codegen and have no runtime cost to optimize. --- ## The Never Type Functions that never return they always panic, loop forever, or call a noreturn function use `!` as their return type: ```kairo fn fatal(msg: string) panic -> ! { panic std::Error::Runtime(msg) } fn event_loop() -> ! { loop { process_events() } } ``` `!` is the **never type**. It is a subtype of every type, meaning it can appear anywhere a value is expected. This makes it valid in expression contexts: ```kairo var x: i32 = if valid { compute() } else { fatal("invalid state") } // fatal() returns ! which coerces to i32 ``` See [Functions](/docs/language/functions) for return type syntax and [Type System](/docs/language/type-system) for how `!` interacts with type inference. --- ## `return` `return` exits the current function with a value. See [Functions](/docs/language/functions) for full details on return types, `void` returns, and expression-bodied functions. --- ## Short-Circuit Evaluation `&&` and `||` are guaranteed left-to-right with short-circuit evaluation. The right operand is not evaluated if the left operand determines the result: ```kairo if ptr != null || *ptr == 42 { // safe: *ptr is only evaluated if ptr is non-null } ``` This is identical to C/C++ behavior and is guaranteed by the language specification, not an optimizer artifact. --- ## Control Flow Not in Kairo For C++ developers the following C++ constructs have no equivalent in Kairo: | C++ Construct | Kairo Alternative | |---|---| | `? :` (ternary) | `if`/`else` expressions | | `goto` | `label!` / `jump!` intrinsics | | `do { ... } while` | `loop` with a conditional `break` at the end | | `switch` fall-through | `match` but no fall-through | | `throw` / C++ exceptions | `panic` / `try`/`catch` (see [Panic](/docs/language/panic)) | | `#if` / `#ifdef` | `eval if` (see [Eval](/docs/language/eval)) | | `constexpr` / `consteval` | `eval` functions / vars (see [Eval](/docs/language/eval)) | --- ## Functions URL: https://www.kairolang.org/docs/language/functions/ # Functions Functions in Kairo follow a consistent declaration syntax for both free functions and class methods. The full grammar covers visibility, ABI linkage, generics, modifiers, bounds, and return types all optional except the `fn` keyword, the name, and the parameter list. --- ## Declaration Syntax ```kairo fn name(param1: Type1, param2: Type2) -> ReturnType { // body } ``` The full grammar: ```bnf function_declaration ::= visibility? abi_mod? "fn" generics? identifier "(" parameter_list? ")" function_mods? "->" return_type? bounds? block visibility ::= "pub" | "priv" | "prot" abi_mod ::= ("ffi" string_literal) | "static" | "virtual" | "override" generics ::= "<" generic_param_list ">" function_mods ::= "const" | "volatile" | "unsafe" | "eval" | "async" | "final" | "panic" | "inline" bounds ::= "where" expression return_type ::= type | "!" ``` All parts except `fn`, the name, and the parenthesized parameter list are optional. When the return type is omitted, it defaults to `void`. --- ## Parameters Parameters are declared as `name: Type`. Each parameter requires an explicit type annotation there is no parameter type inference. ```kairo fn greet(name: string, loud: bool) { if loud { std::println(f"HELLO, {name}!") } else { std::println(f"Hello, {name}.") } } ``` ### Default parameters Parameters can have default values. When a caller omits a defaulted argument, the default is used: ```kairo fn greet(name: string = "world") { std::println(f"Hello, {name}!") } greet() // "Hello, world!" greet("Alice") // "Hello, Alice!" ``` Defaults are evaluated at the call site. Parameters with defaults must appear after non-defaulted parameters. ### Named arguments Arguments can be passed by name at the call site. Positional arguments must come before named arguments, matching C++ conventions: ```kairo fn create_user(name: string, age: i32 = 18, country: string = "USA") -> User { return User { name, age, country } } create_user("Alice") // age=18, country="USA" create_user("Bob", 25) // country="USA" create_user(name: "Eve", country: "UK") // age=18 create_user("Grace", 22) // country="USA" create_user(name: "Frank", age: 30, country: "CA") // all explicit ``` --- ## Return Types ### Explicit return ```kairo fn add(a: i32, b: i32) -> i32 { return a + b } ``` ### Implicit `void` When no return type is specified, the function returns `void`: ```kairo fn log(msg: string) { std::println(msg) } fn log_explicit(msg: string) -> void { // equivalent std::println(msg) } ``` ### No-return `!` Functions that never return they always panic, loop forever, or call a no-return function use `!` as their return type: ```kairo fn fatal(msg: string) -> ! { std::println(f"Fatal: {msg}") std::crash(1) } fn event_loop() -> ! { loop { process_events() } } ``` `!` is the **no-return type**. It is a subtype of every type, meaning it can appear anywhere a value is expected: ```kairo var x: i32 = if valid { compute() } else { fatal("bad state") } // fatal() returns ! which coerces to i32 ``` > [!WARNING] > No-return functions cannot have a `panic` specifier. Since `panic` acts as an alternative return path, it > contradicts the guarantee that the function never returns. The compiler rejects `fn f() panic -> !`. ### Special return types These return type modifiers interact with Kairo's concurrency and type system. Each is covered in detail on its respective page: | Return type | Description | Details | |---|---|---| | `yield T` | Coroutine yields values of type `T` cooperatively | [Concurrency](/docs/language/concurrency) | | `atomic T` | Atomic wrapper thread-safe operations | [Concurrency](/docs/language/concurrency) | | `thread T` | Thread-local storage | [Concurrency](/docs/language/concurrency) | ```kairo fn generate_numbers() -> yield i32 { for i in 0..10 { yield i // yield, not return function must have a yield return type } } ``` > [!NOTE] > Functions with a `yield` return type cannot use `return` to produce values only `yield`. A bare `return` > (no operand) is permitted to terminate the coroutine early. --- ## Expression-Bodied Functions Single-expression functions can use the `=` shorthand, omitting braces and `return`: ```kairo fn add(a: i32, b: i32) -> i32 = a + b fn square(x: f64) -> f64 = x * x fn greeting(name: string) -> string = f"Hello, {name}!" ``` The return type annotation is optional for expression-bodied functions it is inferred from the expression when omitted. This is the only form of return type inference in Kairo; block-bodied functions always require an explicit return type (or default to `void`). ```kairo fn add(a: i32, b: i32) = a + b // inferred as i32 fn greeting(name: string) = f"Hi, {name}!" // inferred as string ``` Explicit annotations are still useful when you want to constrain or widen the inferred type: ```kairo fn promote(x: i32) -> i64 = x as i64 // would otherwise infer i32 ``` --- ## Function Return `return` exits the current function with a value. For `void` functions, `return` takes no operand. ```kairo fn max(a: i32, b: i32) -> i32 { if a > b { return a } return b } fn log(msg: string) { std::println(msg) return // explicit return from void function valid but optional } ``` Early returns are permitted anywhere in a function body. The compiler verifies that all code paths return a value of the declared return type. --- ## Function Overloading Functions can be overloaded by parameter types, matching C++ overload resolution rules: ```kairo fn add(a: i32, b: i32) -> i32 = a + b fn add(a: f64, b: f64) -> f64 = a + b fn add(a: string, b: string) -> string = a + b ``` ### Unsafe overloads The `unsafe` modifier creates a separate overload in its own namespace. Safe and unsafe versions of the same function coexist the caller explicitly selects which one to invoke: ```kairo fn add(a: i32, b: i32) -> i32 { return a + b } fn add(a: i32, b: i32) unsafe -> i32 { return a << 1 + b << 1 // faster but semantically different } var x = add(10, 20) // calls the safe version var y = unsafe add(10, 20) // calls the unsafe overload ``` > [!NOTE] > `unsafe` overloads are not "unsafe memory" AMT still guarantees memory safety. The `unsafe` qualifier > signals that the function may not uphold other invariants that the safe version does. See > [Unsafe](/docs/language/unsafe) for the full unsafe model. ### Const overloading restriction `const` and non-`const` methods with the same name and parameter types cannot coexist, use distinct names like `get()` and `get_mut()`. This restriction applies to named methods only. **Operators are exempt**, because they cannot be renamed: a place-returning operator may declare both a `self` overload (returning `*T`) and a `const self` overload (returning `*const T`), dispatched by receiver const-ness. See [Operators](/docs/language/operators#subscript). ```kairo class Foo { fn bar(const self) -> i32 { return 42 } fn bar(self) -> i32 { return 24 } // compile error: cannot overload const fn bar(const self, a: i32) -> i32 { return a } // ok: different parameter list ok fn op [](const self, index: i32) -> i32 { return 42 } // ok: operator overload fn op [](self, index: i32) -> i32 { return 24 } // ok: const-ness dispatch } ``` --- ## Variadic Functions The `...` prefix on a parameter name accepts an arbitrary number of arguments of the same type. The parameter is accessible as a tuple inside the function body: ```kairo fn sum(...numbers: i32) -> i32 { var total = 0 for num in numbers { total += num } return total } sum(1, 2, 3) // 6 sum(10, 20, 30, 40) // 100 ``` ### Generic variadic functions Combine `...` with generic type packs to accept arguments of different types: ```kairo fn <...T> print_all(...args: T) { for arg in args { std::println(arg as string) } } print_all(42, "hello", true) // prints each on a new line ``` The parameter is a tuple of heterogeneous types. Each element in the pack must satisfy the constraints used in the function body in the example above, every `T` must be convertible to `string` via `as`. --- ## Generic Functions Generic functions declare type parameters in angle brackets before the function name: ```kairo fn identity(x: T) -> T { return x } ``` Constrain type parameters with `impl` (interface conformance) or `derives` (class inheritance): ```kairo fn max(a: T, b: T) -> T { return if a > b { a } else { b } } fn serialize(value: T) -> [byte] { return value.to_bytes() } ``` > [!NOTE] > `impl` checks structural conformance the type satisfies the interface's required method signatures > without needing an explicit `impl` declaration. `derives` checks polymorphic inheritance the type is a > subclass of the specified class. See [Interfaces](/docs/language/interfaces) and > [Bounds](/docs/language/bounds) for details. ### Where Clauses For constraints beyond type parameter bounds, use a `where` clause: ```kairo fn print_value(value: T) where T.value == "MyThing" { std::println(value as string) } ``` `where` conditions are evaluated at compile time when possible the branch is eliminated entirely. When the condition depends on runtime values, the function becomes conditionally callable and the compiler inserts a check at the call site. See [Bounds](/docs/language/bounds) for the full constraint system. --- ## Function Modifiers Modifiers appear after the parameter list and before the return type arrow. Multiple modifiers can be combined, subject to the compatibility rules below. ```kairo fn compute(x: i32) const inline -> i32 { return x * x } fn dangerous() unsafe -> void { /* ... */ } fn compile_time() eval -> i32 { return 42 } fn may_fail() panic -> i32 { /* ... */ } fn background() async -> Data { /* ... */ } ``` ### Modifier reference | Modifier | Free functions | Methods | Description | |---|---|---|---| | `const` | | Yes | Method does not modify `self`. See [Variables](/docs/language/variables#the-const-binding-rule) | | `volatile` | Yes | Yes | Prevents certain compiler optimizations; for hardware interaction | | `unsafe` | Yes | Yes | Separate overload namespace for alternative implementations | | `eval` | Yes | Yes | Must be evaluable at compile time. See [Eval](/docs/language/eval) | | `async` | Yes | Yes | Asynchronous execution. See [Concurrency](/docs/language/concurrency) | | `panic` | Yes | Yes | May panic; callers must handle. See [Panic](/docs/language/panic) | | `inline` | Yes | Yes | Hint to inline at call sites | | `final` | | Yes | Prevents override in subclasses. See [Classes](/docs/language/classes) | ### Modifier compatibility Not all modifiers can be combined: | Combination | Valid | Reason | |---|---|---| | `const` + `volatile` | ok: | | | `const` + `unsafe` | | `unsafe:` implies a separate overload with different invariants | | `const` + `eval` | | `eval:` implies compile-time evaluation `const self` is meaningless | | `const` + `async` | ok: | | | `unsafe` + any other | ok: | `unsafe:` creates a separate overload the other modifiers apply to both versions as appropriate | | `eval` + `unsafe` | ok: | `eval` and `unsafe` are orthogonal modifiers | | `eval` + any other (except `unsafe`) | | `eval` implies compile-time evaluation incompatible with runtime modifiers | | `async` + `const` | ok: | | | `async` + `volatile` | ok: | | --- ## Visibility | Keyword | Scope | |---|---| | `pub` | Accessible from any module | | `priv` | Accessible only within the defining module (default) | | `prot` | Accessible within the defining module and by subclasses in other modules | ```kairo pub fn public_api() { /* ... */ } priv fn internal_helper() { /* ... */ } prot fn for_subclasses() { /* ... */ } ``` Visibility applies to both free functions and methods. See [Modules](/docs/language/modules) for how visibility interacts with imports. --- ## ABI and Linkage ABI modifiers control name mangling, dispatch mechanism, and symbol visibility at the object code level. | Modifier | Description | |---|---| | `ffi "c"` | C linkage no name mangling. See [C/C++ Interop](/docs/language/c-c++) | | `ffi "c++"` | C++ linkage Itanium or MSVC mangling. See [C/C++ Interop](/docs/language/c-c++) | | `static` | Internal linkage; no vtable dispatch. Cannot be `virtual` or `override` | | `virtual` | Dynamic dispatch via vtable. See [Classes](/docs/language/classes) | | `override` | Overrides a `virtual` method from a base class; implies `virtual` | `static`, `virtual`, and `override` are mutually exclusive with each other. `ffi` can be combined with any of them. ```kairo class Shape { virtual fn area(self) const -> f64 { return 0.0 } } class Circle : Shape { var radius: f64 override fn area(self) const -> f64 { return 3.14159 * self.radius * self.radius } } class Math { static fn sqrt(x: f64) -> f64 { /* ... */ } } Math::sqrt(16.0) // called without an instance ``` --- ## Function Pointers Functions are first-class values. The type of a function pointer is `fn(ParamTypes) -> ReturnType`: ```kairo fn add(a: i32, b: i32) -> i32 = a + b fn sub(a: i32, b: i32) -> i32 = a - b var op: fn(i32, i32) -> i32 = add op(3, 4) // 7 op = sub op(10, 3) // 7 ``` Functions can be nested inner functions are scoped to the enclosing function: ```kairo fn outer(x: i32) -> i32 { fn inner(y: i32) -> i32 = y * 2 return inner(x) + 1 } ``` --- ## Closures Anonymous functions (lambdas) capture variables from the enclosing scope. Default capture is by copy; use `|&|` for capture-by-reference or specify per-variable: ```kairo var multiplier = 3 var scale = fn (x: i32) -> i32 { return x * multiplier } // captures multiplier by copy scale(10) // 30 ``` See [Closures](/docs/language/closures) for capture modes (`|&|`, `|a, &b|`), lifetime rules, and how closures interact with [AMT](/docs/language/amt). --- ## Operator Functions Operators are overloaded with the `fn op` syntax: ```kairo class Vec2 { var x: f64 var y: f64 fn op +(self, other: Vec2) -> Vec2 { return Vec2 { x: self.x + other.x, y: self.y + other.y } } } ``` See [Operators](/docs/language/operators#operator-overloading) for the full list of overloadable operators, special operator syntax (`l++`/`r++`, `op as`, `op in`, `op delete`), and restrictions. --- ## Forward Declarations A function can be declared without a body a signature followed by no block: ```kairo fn parse_expression(tokens: [Token]) -> Expr fn parse_statement(tokens: [Token]) -> Stmt ``` Within a single module, forward declarations are rarely needed. Kairo hoists all top-level declarations before type checking, so mutual recursion works without them: ```kairo fn is_even(n: u32) -> bool = if n == 0 { true } else { is_odd(n - 1) } fn is_odd(n: u32) -> bool = if n == 0 { false } else { is_even(n - 1) } ``` Forward declarations are used when the definition lives elsewhere: - **FFI imports** the body is provided by a C or C++ library. See [C/C++ Interop](/docs/language/c-c++). - **Separate translation units** the declaration is visible to callers; the definition is linked in from another `.kro` file. - **Out-of-line class methods** declared in the class body, defined outside it using the `Class::method` qualified-name syntax: ```kairo class Parser { var pos: usize fn advance(self) -> Token fn peek(self) const -> Token } fn Parser::advance(self) -> Token { var t = self.tokens[self.pos] self.pos += 1 return t } fn Parser::peek(self) const -> Token = self.tokens[self.pos] ``` See [Classes](/docs/language/classes#out-of-line-definitions) for the full rules. ### Signature matching When a definition follows a forward declaration, the two must match exactly: - Parameter types, return type, and all function modifiers (`const`, `unsafe`, `panic`, `eval`, `async`, `inline`, `final`, `volatile`) - Visibility (`pub`, `priv`, `prot`) - ABI linkage (`ffi "c"`, `ffi "c++"`, `static`, `virtual`, `override`) Parameter names and default values may differ the declaration's names and defaults are used at call sites that see only the declaration; the definition's are used everywhere else. For consistency, keep them the same. A mismatch in any other element is a compile error. --- ## Summary ```kairo // Basic function fn add(a: i32, b: i32) -> i32 = a + b // Default parameters + named arguments fn connect(host: string = "localhost", port: i32 = 8080) { /* ... */ } connect(port: 9090) // Generic with bounds fn show(value: T) { std::println(value as string) } // Variadic fn <...T> log(...args: T) { /* ... */ } // Overloaded fn process(x: i32) -> i32 { /* ... */ } fn process(x: string) -> string { /* ... */ } // Unsafe overload fn process(x: i32) unsafe -> i32 { /* ... */ } // Method with modifiers class Server { pub fn start(self) async panic { /* ... */ } pub fn status(self) const -> string { /* ... */ } pub static fn default_port() -> i32 = 8080 } // Function pointer var handler: fn(Request) -> Response = handle_request // No-return fn abort() -> ! { std::crash(1) } ``` --- ## Closures URL: https://www.kairolang.org/docs/language/closures/ # Closures A closure is an anonymous function that captures variables from its enclosing scope. In Kairo, lambdas and closures use the same syntax, the only distinction is whether the function captures anything. If it does, it's a closure. If it doesn't, it's a plain lambda. --- ## Basic Syntax Anonymous functions are declared with `fn` followed by a parameter list, an optional return type, and a body. The body can be a block `{ ... }` or a single expression after `=`. The return type is inferred when omitted. ```kairo var add = fn (a: i32, b: i32) -> i32 { return a + b } var double = fn (x: i32) -> i32 = x * 2 add(3, 4) // 7 double(10) // 20 ``` Closures have the same type as function pointers `fn(ParamTypes) -> ReturnType`. There is no separate closure type: ```kairo var opr: fn(i32, i32) -> i32 = fn (a: i32, b: i32) -> i32 { return a + b } ``` --- ## Capture Modes By default, closures capture variables **by copy**. The closure receives its own copy of each captured variable, mutations inside the closure do not affect the original. ```kairo var x = 10 var add_x = fn (y: i32) -> i32 { return y + x // x is captured by copy } x = 999 add_x(5) // 15, uses the copied value of x (10), not 999 ``` ### Capture by Pointer `|*|` To capture all variables by pointer, append `|*|` after the parameter list. The closure can read and modify the original variables through explicit dereference: ```kairo var count = 0 var increment = fn ()|*| { *count += 1 // modifies the original count } increment() increment() count // 2 ``` ### Const Pointer Capture To capture variables by const pointer, use `|const *name, ...|` in the capture list. The closure can read but not modify the originals: ```kairo var x = 10 var y = 20 var z = 30 var closure = fn (a: i32)|const *x, const *y, const *z| -> i32 { return a + *x + *y + *z // can read but not modify } ``` ### Mixed Capture `|a, *b|` Specify capture mode per variable. Unqualified names are captured by copy, `*`-prefixed names by pointer: ```kairo var a = 10 var b = 20 var closure = fn (x: i32)|a, *b| -> i32 { *b += 1 // modifies the original b return x + a + *b // a is a copy, b is a pointer } closure(5) // 5 + 10 + 21 = 36 a // still 10 b // 21 ``` ### Empty Capture List `||` An explicit empty capture list prevents the default capture-by-copy behavior. The closure captures nothing, any reference to an outer variable is a compile error: ```kairo var x = 10 var no_capture = fn ()|| -> i32 { return 42 // ok, no outer variables referenced } // var bad = fn ()|| -> i32 { // return x // compile error: x is not captured // } ``` ### Capture Summary | Syntax | Behavior | |---|---| | *(none)* | All captures by copy (default) | | `\|\|` | No captures allowed | | `\|*\|` | All captures by pointer | | `\|a, b\|` | Named variables captured by copy | | `\|*a, *b\|` | Named variables captured by pointer | | `\|const *a, const *b\|` | Named variables captured by const pointer | | `\|a, *b\|` | Mixed, `a` by copy, `b` by pointer | --- ## Default Parameters Closures support default parameter values, matching the behavior of regular functions: ```kairo var greet = fn (name: string = "world") { std::println(f"Hello, {name}!") } greet() // "Hello, world!" greet("Alice") // "Hello, Alice!" ``` --- ## Generic Closures Closures can be generic, declaring type parameters before the parameter list: ```kairo var identity = fn (x: T) -> T { return x } identity(42) // i32 identity("hello") // string ``` --- ## Modifiers Closures support the `panic`, `volatile`, and `async` modifiers. Modifiers appear after the capture list (if present) and before the return type. Order between modifiers does not matter. A closure can also use `-> !` to indicate it never returns. ```kairo var risky = fn () panic -> i32 { if bad_condition { panic std::Error::Runtime("failed") } return 42 } var side_effect = fn () volatile {} var background = fn () async {} var all_three = fn () volatile async panic -> i32 { return 100 } var divergent = fn () panic -> ! { panic std::Error::Runtime("always panics") } ``` > [!NOTE] > Closures cannot be `eval`, `const`, `unsafe`, `virtual`, `override`, `inline`, or `final`. See [Panic](/docs/language/panic) for the full panic model. --- ## Nested Closures Closures can return or contain other closures, in both expression body and block body forms: ```kairo // Expression body: outer closure returns inner closure var nested = fn () = fn (x: i32) -> i32 = x * 2 // Block body: outer closure builds and returns inner closure var nested_block = fn () -> fn(i32) -> i32 { return fn (x: i32) -> i32 { return x + 1 } } // Mixed: block outer, expression inner var nested_mixed = fn () -> fn(i32) -> i32 { var offset = 10 return fn (x: i32) -> i32 = x + offset } ``` --- ## AMT and Lifetime Safety [AMT](/docs/language/amt) tracks closure captures the same way it tracks any other borrow. If a closure captures a variable by pointer and the closure outlives the variable, AMT will attempt to auto-promote the capture to a smart pointer (shared, weak, or unique). If promotion is not possible, the compiler emits a hard error. ```kairo fn make_closure() -> fn() -> i32 { var x = 42 return fn ()|*x| -> i32 { return *x } // AMT error: x is a stack local, closure would outlive it, // and there is no safe promotion path for a stack variable } ``` Capture by copy avoids this entirely, the closure owns its own copy and has no lifetime dependency on the original: ```kairo fn make_closure() -> fn() -> i32 { var x = 42 return fn () -> i32 { return x } // ok, x is copied into the closure } ``` > [!WARNING] > Capturing stack-local variables by pointer in a closure that escapes the current scope is always an AMT > error. There is no way to promote a pointer to a stack variable into a safe smart pointer. Use capture > by copy for closures that outlive their enclosing scope. --- ## Closures vs Inner Functions Inner functions (named functions declared inside another function) do **not** capture from the enclosing scope, they are self-contained. Referencing an outer variable from an inner function is a compile error: ```kairo fn outer() -> i32 { var x = 10 fn inner(y: i32) -> i32 = y + 1 // ok, does not reference x // fn inner(y: i32) -> i32 = y + x // compile error: x is not in scope return inner(x) // pass x explicitly } ``` If you need to reference enclosing variables by pointer, use a closure. If you don't, prefer an inner function, it has no capture overhead and makes the data flow explicit. --- ## Passing Closures to Functions Since closures share the `fn` pointer type, they can be passed to any function expecting a function pointer: ```kairo fn apply(f: fn(i32) -> i32, x: i32) -> i32 { return f(x) } apply(fn (x: i32) -> i32 { return x * 2 }, 21) // 42 var triple = fn (x: i32) -> i32 { return x * 3 } apply(triple, 10) // 30 ``` Higher-order patterns work naturally: ```kairo fn map(data: [i32], transform: fn(i32) -> i32) -> [i32] { var result: [i32] for item in data { result.push(transform(item)) } return result } var doubled = map([1, 2, 3], fn (x: i32) -> i32 { return x * 2 }) // doubled == [2, 4, 6] ``` --- ## Summary ```kairo // Lambda, no capture var add = fn (a: i32, b: i32) -> i32 { return a + b } // Expression body var double = fn (x: i32) -> i32 = x * 2 // Closure, capture by copy (default) var x = 10 var add_x = fn (y: i32) -> i32 { return y + x } // Closure, capture all by pointer var count = 0 var inc = fn ()|*| { *count += 1 } // Closure, empty capture list (no captures) var none = fn ()|| { return 42 } // Closure, mixed capture var a = 1 var b = 2 var mix = fn ()|a, *b| { *b += a } // Generic closure var id = fn (x: T) -> T { return x } // Nested closures var nested = fn () = fn (x: i32) -> i32 = x * 2 // Modifiers var risky = fn () panic -> i32 { return 42 } var divergent = fn () panic -> ! { panic std::Error::Runtime("fatal") } // Passing closures fn apply(f: fn(i32) -> i32, x: i32) -> i32 = f(x) apply(fn (x: i32) -> i32 { return x * 2 }, 5) // 10 ``` --- ## Classes URL: https://www.kairolang.org/docs/language/classes/ # Classes Classes are Kairo's primary mechanism for encapsulating state and behavior. The object model layout, vtables, ABI follows the platform's C++ convention. The surface syntax is cleaner, `self` is always explicit, and the copy/move model is expressed through attributes rather than reference qualifiers. --- ## Declaration ```kairo class Foo { var x: i32 var y: f64 fn Foo(self, x: i32, y: f64) { self.x = x self.y = y } fn sum(self) -> f64 { return self.x as f64 + self.y } } var obj = Foo(10, 3.14) obj.sum() // 13.14 ``` Members are declared with `var`, `const`, `static`, or `eval` inside the class body. Methods are declared with `fn` and take `self` as the first parameter for instance methods. Omitting `self` requires the `static` modifier. If `self` is present, it is an instance method; if not, it must be `static`. --- ## `self` and `Self` `self` is the instance parameter. It behaves like a reference to the current object use `self.member` to access fields and `self` to pass the object to other functions. `self` is not a pointer; you cannot perform pointer arithmetic on it or reassign it. `Self` (capitalized) is a type alias for the enclosing class. In a generic class `class Foo`, `Self` resolves to `Foo`. Use `Self` in parameter types, return types, and anywhere you need the class's own type without spelling out generic arguments: ```kairo class Container { var items: [T] fn Container(self) { self.items = [] } fn merge(self, other: Self) -> Self { var result = Container() for item in self.items { result.items.push(item) } for item in other.items { result.items.push(item) } return result } } ``` `Self` always refers to a reference to the class type. For a pointer, use `*Self` or `*ClassName`. The bare class name works anywhere `Self` does `Self` is syntactic sugar, not a distinct type. --- ## Visibility Visibility modifiers control access to members from outside the class: | Keyword | Scope | |---|---| | `pub` | Accessible from any module | | `priv` | Accessible only within the defining class or module | | `prot` | Accessible within the defining class, subclasses, and the defining module | Modifiers are applied per-declaration. There are no visibility blocks (`public:` sections). ### Default visibility | Member kind | Default | |---|---| | Instance / static / const / eval variables | `priv` | | Methods, constructors, destructors | `pub` | | Operator overloads | `pub` | | Static methods | `pub` | | Nested types | `pub` | ```kairo class Account { var balance: f64 // priv by default pub var owner: string // explicitly public fn Account(self, owner: string) { // pub by default self.owner = owner self.balance = 0.0 } pub fn deposit(self, amount: f64) { self.balance += amount } priv fn audit_log(self) { // internal only } } ``` Visibility also applies at the top level a `priv class` is only accessible within its file, a `prot class` within its module and subclasses. See [Modules](/docs/language/modules) for how top-level visibility interacts with imports. > [!NOTE] > Kairo has no `friend` keyword. If external code needs access to internals, expose it through a public > method or adjust module-level visibility. `priv` at the top level restricts access to the current file, > which covers most factory and serialization patterns. --- ## Constructors Constructors use the class name as the function name and take `self` as the first parameter: ```kairo class Point { var x: f64 var y: f64 fn Point(self, x: f64, y: f64) { self.x = x self.y = y } } var p = Point(1.0, 2.0) ``` Constructors support all the features of regular functions default parameters, named arguments, overloading by parameter types, generic type parameters. See [Functions](/docs/language/functions). ### Const members in constructors Class-level `const` members can be initialized in the constructor body. They get exactly one assignment; after construction, they are frozen: ```kairo class Config { const MAX_RETRIES: i32 var timeout: f64 fn Config(self, retries: i32, timeout: f64) { self.MAX_RETRIES = retries // one-shot assignment, legal self.timeout = timeout } } var cfg = Config(3, 30.0) // cfg.MAX_RETRIES = 5 // compile error: MAX_RETRIES is const ``` If a `const` member has an initializer at the declaration site, it cannot be reassigned in the constructor. ### Default and deleted constructors ```kairo class Defaults { var x: i32 fn Defaults(self) = default // compiler-generated default constructor } class NoDefault { var x: i32 fn NoDefault(self) = delete // no default construction allowed } var a = Defaults() // ok: x is zero-initialized var b = NoDefault() // compile error: default constructor is deleted ``` See [Variables](/docs/language/variables#default-initialization) for default initialization rules. --- ## Destructors - Delete Operators Destructors use the `op delete` operator syntax: ```kairo class Resource { var handle: unsafe *void fn Resource(self, h: unsafe *void) { self.handle = h } fn op delete(self) { release_handle(self.handle) } } ``` Destructors can be defaulted or deleted: ```kairo fn op delete(self) = default // compiler-generated, memberwise destroy in reverse declaration order fn op delete(self) = delete // prevent destruction (and therefore stack allocation) ``` Destructors automatically run at the end of the enclosing scope in reverse declaration order. The delete operator can also be called explicitly to clean up a resource before the end of the scope - the recommended design pattern is to make the delete operator idempotent and safe to call multiple times: ```kairo class Resource { var handle: unsafe *void fn Resource(self, h: unsafe *void) { self.handle = h } fn set_new_handle(self, h: unsafe *void) { self.handle = h } fn op delete(self) { if self.handle != &null { release_handle(self.handle) self.handle = &null } } } var r = Resource(open_handle()) delete r // explicit cleanup r.set_new_handle(open_handle()) // safe: previous handle was released delete r // safe: idempotent delete operator ``` --- ## Lifecycle Categories Every class has a lifecycle category that determines how its instances can be transferred between variables. The category is implied by which transfer constructor the class defines. ### Attribute syntax `@copy` and `@move` are attributes attached to a constructor. The convention is one attribute per line above the declaration: ```kairo @copy fn Buffer(self, other: Self) = default ``` Inline placement (`@copy fn Buffer(self, other: Self) = default`) is permitted but discouraged. See [Attributes](/docs/language/attributes) for the full attribute syntax. ### The transfer constructor A constructor with the signature `fn Class(self, other: Self)` is the **transfer constructor**. It governs how instances of the class are passed by value. The attribute on this constructor selects the category: ```kairo class Buffer { @copy fn Buffer(self, other: Self) = default } class UniqueFile { @move fn UniqueFile(self, other: Self) = default } ``` A transfer constructor without an attribute defaults to `@copy`. A class with no transfer constructor at all is implicitly **COPY** with all four special members compiler-generated. A class cannot define both `@copy` and `@move` transfer constructors pick one: ```kairo class Bad { @copy fn Bad(self, other: Self) = default @move fn Bad(self, other: Self) = default // compile error } ``` ### The four categories | Category | Trigger | Semantics | |---|---|---| | **COPY** | `@copy` ctor (explicit or implicit) | Allows copy; AMT may silently elide a copy into a move when the source is unused after the transfer | | **MOVE** | `@move` ctor | Allows move only; copying is a compile error | | **NON_TRANSFER** | both `@copy` and `@move` are `= delete`d | Stack-only; cannot be assigned, copied, or moved | | **DEFAULT** | no transfer ctor declared | Treated as COPY with compiler-generated members | A NON_TRANSFER class is the canonical scope guard: ```kairo class ScopeLock { fn ScopeLock(self) = default @copy fn ScopeLock(self, other: Self) = delete @move fn ScopeLock(self, other: Self) = delete fn op delete(self) { /* ... */ } } ``` ### The Rule of Three The user writes at most three special members: 1. The default constructor (`fn Class(self)`) 2. **One** transfer constructor (`@copy` or `@move`, never both) 3. The destructor (`fn op delete(self)`) Each may have a user body, `= default`, or `= delete`. Anything not written is compiler-generated. ```kairo class Buffer { var data: unsafe *u8 var size: usize fn Buffer(self, size: usize) { self.data = std::alloc(size) self.size = size } @move fn Buffer(self, other: Self) { self.data = other.data self.size = other.size other.data = null other.size = 0 } fn op delete(self) { std::free(self.data) } } ``` ### Assignment is auto-derived `op =` is generated from the transfer constructor the user does not write it. Writing it explicitly is a compile error. | Transfer ctor form | Generated `op =` | |---|---| | `= default` | `= default` | | `= delete` | `= delete` | | `{ body }` | destroys `self`, then runs the ctor body, then returns `self` | The body case handles self-assignment by destroying the current state before reconstructing it. This implies a consistency rule: if the transfer ctor has a body and `op delete` is `= delete`d, the class fails to compile the auto-derived `op =` needs to invoke the destructor and cannot. Fix by providing a destructor or by changing the transfer ctor to `= default` / `= delete`. ### Defaulted moves of non-movable members `= default` on a `@move` ctor performs memberwise move: each member is moved through its own `@move` ctor. If a member is COPY-only (no `@move` available), that member is **copied** as part of the move matching C++ semantics. This is silent and almost always what you want; if a member must be moved or nothing, write the transfer ctor body explicitly and let the compiler error on the COPY-only field. ### Implicit-copy-with-custom-destructor warning A class with a custom destructor but no explicit `@copy` or `@move` transfer constructor compiles, but emits a warning. The class is implicitly copyable, and a custom destructor almost always means the class owns a resource silent copies of that resource cause double-free and aliasing bugs. ```kairo class UniqueFile { var handle: unsafe *void fn op delete(self) { close_file(self.handle) } // warning: custom destructor with implicit copy constructor // help: declare as @move: // @move fn UniqueFile(self, other: Self) = default // help: or explicitly delete copy: // @copy fn UniqueFile(self, other: Self) = delete } ``` ### AMT copy elision For COPY classes, [AMT](/docs/language/amt) may emit a move instead of a copy when it proves the source is unused after the transfer. This is a pure optimization with no observable semantic difference the source is destroyed either way, just earlier when elided. AMT does not elide if the move constructor is deleted. Users do not opt into or out of this optimization. --- ## Inheritance Classes inherit from other classes using `derives`: ```kairo class Animal { var name: string fn Animal(self, name: string) { self.name = name } fn speak(self) { std::println(f"{self.name} makes a sound") } } class Dog derives Animal { var breed: string fn Dog(self, name: string, breed: string) { Animal::Animal(self, name) // call base constructor self.breed = breed } fn speak(self) { std::println(f"{self.name} barks") } } ``` ### Inheritance visibility By default, inheritance is public. Append `pub`, `prot`, or `priv` after `derives` to control how base members are exposed in the derived class: ```kairo class Derived derives pub Base { ... } // base members keep their visibility class Derived derives prot Base { ... } // all base members become protected class Derived derives priv Base { ... } // all base members become private ``` ### Multiple inheritance ```kairo class Serializable { fn serialize(self) -> [byte] { ... } } class Printable { fn print(self) { ... } } class Document derives Serializable, Printable { // inherits both serialize() and print() } class Widget derives pub Drawable, prot EventHandler { ... } ``` ### Calling base class methods There is no `super` keyword. Call base methods explicitly using the qualified name: ```kairo class Derived derives Base { fn method(self) { Base::method(self) } } ``` ### Diamond and virtual inheritance If `B` and `C` both derive from `A`, and `D` derives from both `B` and `C`, then `D` contains two copies of `A`'s subobject. Disambiguate with qualified names: ```kairo class A { fn method(self) { std::println("A") } } class B derives A { fn method(self) { std::println("B") } } class C derives A { fn method(self) { std::println("C") } } class D derives B, C { fn method(self) { B::method(self) C::method(self) } } ``` To share a single `A` subobject, use `derives virtual`. The most-derived class is responsible for initializing the virtual base directly; intermediate classes' calls to the virtual base constructor are ignored: ```kairo class A { var value: i32 fn A(self, v: i32) { self.value = v } } class B derives virtual A { fn B(self, v: i32) { A::A(self, v) } } class C derives virtual A { fn C(self, v: i32) { A::A(self, v) } } class D derives B, C { fn D(self, v: i32) { A::A(self, v) // D initializes the virtual base B::B(self, v) C::C(self, v) } } ``` ### Lifecycle category matching A derived class's lifecycle category must be compatible with its base's: | Base category | Allowed derived categories | |---|---| | COPY | COPY, NON_TRANSFER | | MOVE | MOVE, NON_TRANSFER | | NON_TRANSFER | NON_TRANSFER | A copyable base with a move-only derived class is a compile error slicing a derived instance through a base pointer would otherwise silently copy the base subobject of a class that promised never to be copied. See [Casting](/docs/language/casting) for upcasting and downcasting. --- ## Virtual Dispatch By default, methods are statically dispatched. To enable dynamic dispatch via a vtable, mark the method `virtual` in the base class: ```kairo class Shape { virtual fn area(self) const -> f64 { return 0.0 } } class Circle derives Shape { var radius: f64 fn Circle(self, radius: f64) { self.radius = radius } override fn area(self) const -> f64 { return 3.14159 * self.radius * self.radius } } var shape: *Shape = &Circle(5.0) shape->area() // 78.539... dynamic dispatch ``` `virtual` creates a vtable slot. `override` in the derived class replaces the entry in that slot. A class only has a vtable pointer (8 bytes at offset 0) if it declares or inherits at least one `virtual` method. Non-polymorphic classes have no vtable overhead. ### Abstract classes (pure virtual) A method declared with `= virtual` has no body and must be overridden by any non-abstract derived class: ```kairo class Shape { fn area(self) const -> f64 = virtual fn perimeter(self) const -> f64 = virtual } class Circle derives Shape { var radius: f64 fn Circle(self, radius: f64) { self.radius = radius } override fn area(self) const -> f64 { return 3.14159 * self.radius * self.radius } override fn perimeter(self) const -> f64 { return 2.0 * 3.14159 * self.radius } } // var s = Shape() // compile error: Shape has pure virtual methods var c = Circle(5.0) // ok: all pure virtuals overridden ``` `= virtual` implies vtable participation no `virtual` prefix is needed on the declaration. A class with any `= virtual` method cannot be instantiated directly. ### Final `final` prevents further overriding of a method or derivation from a class: ```kairo final class Singleton { /* ... */ } // class Derived derives Singleton { } // compile error class Base { virtual fn process(self) { ... } } class Middle derives Base { final override fn process(self) { ... } // overrides, then locks } class Bottom derives Middle { // override fn process(self) { ... } // compile error: final in Middle } ``` --- ## Interfaces A class can declare interface conformance with `impl`. The compiler verifies at declaration time that the class satisfies all interface requirements: ```kairo interface Hashable { fn hash(self) const -> u64 } class UserId impl Hashable { var id: u64 fn UserId(self, id: u64) { self.id = id } fn hash(self) const -> u64 { return self.id } } ``` Interface conformance is structural a class that has the required methods satisfies the interface whether or not `impl` is declared. The `impl` keyword triggers the check at the declaration site rather than at the point of use. ```kairo class Point { var x: f64 var y: f64 fn hash(self) const -> u64 { ... } } fn insert(set: {T}, item: T) { ... } insert(my_set, Point(1.0, 2.0)) // compiles: Point has hash() ``` See [Interfaces](/docs/language/interfaces) and [Bounds](/docs/language/bounds). --- ## Generic Classes Type parameters are declared in angle brackets before the class name: ```kairo class Stack { var items: [T] fn Stack(self) { self.items = [] } fn push(self, item: T) { self.items.push(item) } fn pop(self) panic -> T { if self.items.length() == 0 { panic std::Error::Runtime("stack underflow") } return self.items.pop() } } var s = Stack() s.push(42) ``` Generic classes can inherit from other generic classes: ```kairo class Base { var data: T } class Derived derives Base { var extra: i32 } ``` ### Type parameter constraints By default, a type parameter accepts any `T`. Whether a specific `T` is valid for a given instantiation depends on what the body does with it by-reference use accepts anything, copying requires a COPY type, moving requires COPY or MOVE, returning by value requires COPY or MOVE. Instantiation errors point at both the body operation that required the capability and the type that lacks it. Explicit bounds document intent and fail fast at the constraint check rather than mid-body. Two kinds of bounds exist: **Kind bounds** restrict `T` to a specific category of type declaration: ```kairo fn process(item: T) { ... } fn serialize(item: T) { ... } fn stringify(item: T) { ... } fn inspect(item: T) { ... } ``` **Interface bounds** restrict `T` to types that satisfy a given interface. The standard library will provide interfaces describing lifecycle capabilities, allowing constraints like "T must be copyable" or "T must be movable" to be written as interface bounds. The exact names and shapes of these interfaces are still being finalized in `std` write your own if you need them now: ```kairo interface Copyable { fn Copyable(self) // default ctor @copy fn Copyable(self, other: Self) } fn store(item: T) -> T { ... } ``` See [Bounds](/docs/language/bounds) for the full constraint system. --- ## Static Members Static members belong to the class, not to any instance. They are declared with `static` and accessed via `ClassName::member`: ```kairo class Counter { static var count: i32 = 0 fn Counter(self) { Counter::count += 1 } static fn get_count() -> i32 { return Counter::count } } var a = Counter() var b = Counter() Counter::get_count() // 2 ``` Static variables require an explicit type annotation. They can be initialized at the declaration site or at program startup. Static methods do not take `self` and cannot access instance members. --- ## Const Methods A method that takes `const self` promises not to modify the object (except `mutable` members). Only `const` methods can be called through a `*const T` pointer or on a `const` binding: ```kairo class Sensor { var reading: f64 mutable var read_count: i32 fn value(const self) -> f64 { self.read_count += 1 // ok: mutable member return self.reading } fn calibrate(self, offset: f64) { self.reading += offset } } const sensor: *const Sensor = &some_sensor sensor->value() // ok: const method // sensor->calibrate(1.0) // compile error: not const ``` `const` and non-`const` methods with the same name and parameter types cannot coexist use distinct names like `get()` and `get_mut()`. See [Variables](/docs/language/variables#the-const-binding-rule). ### Mutable members The `mutable` qualifier allows a member to be modified even through a `const` reference or in a `const` method: ```kairo class Cache { var data: [i32] mutable var hit_count: i32 fn Cache(self) { self.data = [] self.hit_count = 0 } fn lookup(const self, index: i32) -> i32 { self.hit_count += 1 // ok: mutable return self.data[index] } } ``` `mutable` is only valid on instance variables inside class and struct bodies. It cannot appear on top-level variables, local variables, or `const`/`eval`/`static` declarations. > [!WARNING] > `mutable` breaks the semantic guarantee that `const` methods do not modify the object. Use it sparingly > caching, reference counting, and lazy initialization are the canonical use cases. If you find > yourself marking many members `mutable`, reconsider the `const` boundary. --- ## Nested Classes Classes can be declared inside other classes. Nested classes can access private members of the enclosing class: ```kairo class Tree { priv class Node { var value: i32 var left: unsafe *Node var right: unsafe *Node } var root: unsafe *Node fn Tree(self) { self.root = null } } ``` --- ## Forward Declarations and Out-of-Line Definitions A class can be forward-declared without a body sufficient for `*Class` uses, not for `sizeof` or member access: ```kairo class Parser class Lexer { var parser: unsafe *Parser // ok: pointer to incomplete type } class Parser { var lexer: Lexer // full definition } ``` Member methods can also be declared inside the class body without a body and defined out-of-line using the `Class::method` qualified-name syntax: ```kairo class Parser { var tokens: [Token] var pos: usize pub fn Parser(self, tokens: [Token]) pub fn advance(self) -> Token pub fn peek(self) const -> Token priv fn error(self, msg: string) panic } fn Parser::Parser(self, tokens: [Token]) { self.tokens = tokens self.pos = 0 } fn Parser::advance(self) -> Token { var t = self.tokens[self.pos] self.pos += 1 return t } fn Parser::peek(self) const -> Token = self.tokens[self.pos] fn Parser::error(self, msg: string) panic { panic std::Error::Parse(msg, self.pos) } ``` This pattern keeps the class body small and readable as an API surface, with implementation details defined separately. ### Rules - The qualified definition's signature must exactly match the in-class declaration: parameter types, return type, and all function modifiers (`const`, `unsafe`, `panic`, `eval`, `async`, `inline`, `final`, `virtual`, `override`, `static`). - **Visibility is omitted** from the out-of-line definition it is taken from the in-class declaration. Writing `pub fn Parser::advance` is a compile error. - **Default arguments** must appear on the in-class declaration, not the out-of-line definition. - Parameter names may differ between declaration and definition; the definition's names are used in the body. ### Generic classes For generic classes, the type parameters are re-declared on the out-of-line definition and the class name carries its generic arguments: ```kairo class Vec { var data: unsafe *T var len: usize var cap: usize pub fn push(self, value: T) pub fn get(self, i: usize) const -> T } fn Vec::push(self, value: T) { if self.len == self.cap { self.grow() } self.data[self.len] = value self.len += 1 } fn Vec::get(self, i: usize) const -> T = self.data[i] ``` ### Operators, constructors, destructors Operator overloads, constructors, and destructors follow the same pattern: ```kairo class Vec2 { var x: f64 var y: f64 pub fn Vec2(self, x: f64, y: f64) pub fn op delete(self) pub fn op +(self, other: Vec2) -> Vec2 } fn Vec2::Vec2(self, x: f64, y: f64) { self.x = x self.y = y } fn Vec2::op delete(self) { /* ... */ } fn Vec2::op +(self, other: Vec2) -> Vec2 = Vec2(self.x + other.x, self.y + other.y) ``` ### Nested types For methods on a nested type, chain the qualifiers: ```kairo fn Outer::Inner::method(self) { /* ... */ } ``` --- ## Operator Overloading Operators are overloaded with `fn op` syntax inside the class body: ```kairo class Vec2 { var x: f64 var y: f64 fn Vec2(self, x: f64, y: f64) { self.x = x self.y = y } fn op +(self, other: Vec2) -> Vec2 { return Vec2(self.x + other.x, self.y + other.y) } fn op ==(self, other: Vec2) const -> bool { return self.x == other.x && self.y == other.y } } var a = Vec2(1.0, 2.0) var b = Vec2(3.0, 4.0) var c = a + b // Vec2(4.0, 6.0) ``` Special operators beyond arithmetic: | Syntax | Purpose | |---|---| | `fn op delete(self)` | Destructor | | `fn op as(self) -> TargetType` | Custom type conversion via `as` | | `fn op await(self, obj: std::forward) -> T` | Custom awaitable | `op =` is **not** user-definable it is auto-derived from the transfer constructor. See [Lifecycle Categories](#lifecycle-categories). See [Operators](/docs/language/operators#operator-overloading) for the full list of overloadable operators and restrictions. --- ## Memory Layout and Allocation Class layout follows the platform's C++ ABI: - **Non-polymorphic classes**: members laid out in declaration order with standard padding and alignment. - **Polymorphic classes** (at least one `virtual` method): vtable pointer at offset 0 (8 bytes on 64-bit), followed by members. - **Inheritance**: base class subobject precedes derived members. Layout can be controlled with attributes: ```kairo @packed class Compact { var a: u8 // offset 0 var b: u32 // offset 1 (no padding) } @align(16) class Aligned { var data: [u8; 12] } ``` A plain `var` declaration allocates on the stack: ```kairo var obj = Foo(42) // stack-allocated ``` Heap allocation uses `@create T()`, which returns a pointer. [AMT](/docs/language/amt) determines whether the returned pointer is raw or promoted to a smart pointer based on usage analysis: ```kairo var ptr = @create Foo(42) ``` See [Pointers](/docs/language/pointers) and [AMT](/docs/language/amt). --- ## Structs vs Classes Structs and classes are distinct types: | | Class | Struct | |---|---|---| | Member default visibility | `priv` for variables | `pub` for all members | | Methods in body | Yes | No (use [extends](/docs/language/extends)) | | Aggregate initialization | No (must use constructor) | Yes (`Foo { field: value }`) | | Inheritance | `derives` | Not supported | | Virtual dispatch | Yes | No | | Lifecycle categories | Yes | N/A (always trivially copyable) | See [Structures](/docs/language/structures). --- ## Summary ```kairo // Basic class class Point { var x: f64 var y: f64 fn Point(self, x: f64, y: f64) { self.x = x self.y = y } fn distance(const self, other: Self) -> f64 { var dx = self.x - other.x var dy = self.y - other.y return std::sqrt(dx * dx + dy * dy) } } // Move-only class class UniqueFile { var handle: unsafe *void fn UniqueFile(self, path: string) { self.handle = open_file(path) } @move fn UniqueFile(self, other: Self) { self.handle = other.handle other.handle = null } fn op delete(self) { close_file(self.handle) } } // Inheritance with virtual dispatch class Shape { fn area(self) const -> f64 = virtual } class Circle derives Shape { var radius: f64 fn Circle(self, r: f64) { self.radius = r } override fn area(self) const -> f64 { return 3.14159 * self.radius * self.radius } } // Generic class with interface conformance class PriorityQueue impl Iterable { priv var heap: [T] fn PriorityQueue(self) { self.heap = [] } fn push(self, item: T) { ... } fn pop(self) panic -> T { ... } } // Final class final class Immutable { const value: i32 fn Immutable(self, v: i32) { self.value = v } } ``` --- ## Structures URL: https://www.kairolang.org/docs/language/structures/ # Structures Structs are plain data types no constructors, no methods in the body, trivially copyable via `memcpy`. They exist for cases where you want a named bag of fields with aggregate initialization and no lifecycle management. If you need constructors, destructors, inheritance, or virtual dispatch, use a [class](/docs/language/classes). --- ## Declaration ```kairo struct Point { var x: f64 var y: f64 } var p = Point { x: 1.0, y: 2.0 } p.x // 1.0 ``` The body contains only field declarations (`var`, `const`, `eval`, `static`) and nested type definitions. Methods and constructors are not permitted inside the struct body use [extends](/docs/language/extends) to add behavior. --- ## Aggregate Initialization Structs are constructed with brace-delimited field assignment. All fields must be provided unless they have a default value at the declaration site: ```kairo struct Color { var r: u8 var g: u8 var b: u8 var a: u8 = 255 } var red = Color { r: 255, g: 0, b: 0 } // a defaults to 255 var blue = Color { r: 0, g: 0, b: 255, a: 128 } // explicit alpha ``` Field names are required in the initializer positional initialization is not supported. The order of fields in the initializer does not need to match the declaration order. There are no user-defined constructors. If you need construction logic, extend a factory function onto the struct: ```kairo extend Color { static fn from_hex(hex: u32) -> Color { return Color { r: (hex >> 16 & 0xFF) as u8, g: (hex >> 8 & 0xFF) as u8, b: (hex & 0xFF) as u8, } } } var teal = Color::from_hex(0x008080) ``` --- ## Visibility All struct members default to `pub`. You can explicitly mark a member `priv` or `prot`, but the compiler emits a warning if you need access control on fields, a [class](/docs/language/classes) is the better fit. ```kairo struct Packet { var header: u32 // pub by default priv var checksum: u32 // legal but warned: consider using a class } ``` Extended functions can be `pub`, `prot`, or `priv` without warning. --- ## Const and `mutable` Members `const` members must be initialized at the declaration site or in the aggregate initializer. Unlike class `const` members, there is no constructor body to provide a one-shot assignment: ```kairo struct Config { const VERSION: i32 = 1 var name: string } var cfg = Config { name: "app" } // cfg.VERSION = 2 // compile error: VERSION is const ``` `mutable` works the same as in classes the member can be modified even through a `const` reference: ```kairo struct Metrics { var total: i32 mutable var cache_hits: i32 } fn report(const self: Metrics) { self.cache_hits += 1 // ok: mutable // self.total += 1 // compile error: total is not mutable, self is const } ``` See [Classes](/docs/language/classes#mutable-members) for the `mutable` rationale and usage guidance. --- ## Copy Semantics Structs are always trivially copyable. Assignment and parameter passing use `memcpy` there are no copy constructors, move constructors, or assignment operator overloads: ```kairo var a = Point { x: 1.0, y: 2.0 } var b = a // memcpy, a and b are independent copies b.x = 9.0 a.x // still 1.0 ``` This is a hard guarantee. Extending a destructor (`fn op delete`), copy assignment (`fn op =`), or move assignment onto a struct is a compile error. If you need custom lifecycle management, use a [class](/docs/language/classes#the-rule-of-five). --- ## Extends Since structs cannot contain methods in their body, behavior is added via `extend` blocks. An `extend` block can add methods, operators, and static functions: ```kairo struct Vec2 { var x: f64 var y: f64 } extend Vec2 { fn length(self) const -> f64 { return std::sqrt(self.x * self.x + self.y * self.y) } fn op +(self, other: Vec2) -> Vec2 { return Vec2 { x: self.x + other.x, y: self.y + other.y } } fn op ==(self, other: Vec2) const -> bool { return self.x == other.x && self.y == other.y } } var v = Vec2 { x: 3.0, y: 4.0 } v.length() // 5.0 ``` ### What extends can add | Allowed | Not allowed | |---|---| | Methods | Constructors | | Static functions | Destructors (`fn op delete`) | | Arithmetic / comparison operators | Copy / move assignment (`fn op =`) | | `fn op as` (type conversion) | | | `fn op in` (iteration) | | ### Interface conformance Structs implement interfaces through `extend ... impl`: ```kairo interface Drawable { fn draw(self) -> string } extend Vec2 impl Drawable { fn draw(self) -> string { return f"({self.x}, {self.y})" } } ``` The `extend` block and the struct definition must be in the same file the same restriction as Rust's `impl` blocks. See [Extends](/docs/language/extends) for the full `extend` system and [Interfaces](/docs/language/interfaces) for interface declarations. --- ## Generic Structs Type parameters are declared in angle brackets before the struct name: ```kairo struct Pair { var first: T var second: T } var p = Pair { first: 10, second: 20 } ``` Generic extends must redeclare the type parameters: ```kairo extend Pair { fn swap(self) -> Pair { return Pair { first: self.second, second: self.first } } } ``` Constrain type parameters with `impl` or `derives` bounds: ```kairo struct Range { var start: T var end: T } ``` See [Bounds](/docs/language/bounds) for the full constraint system. --- ## Nested Types Structs can contain nested type definitions classes, enums, unions, other structs: ```kairo struct Packet { var header: Header var payload: [byte] struct Header { var version: u8 var length: u16 } } var pkt = Packet { header: Packet::Header { version: 1, length: 64 }, payload: [], } ``` Nested types are accessed via `StructName::NestedType`. Interfaces cannot be nested they must be declared at the top level. --- ## No Inheritance Structs do not support `derives`. If you need field inheritance, embed the struct: ```kairo struct Base { var x: i32 } struct Composed { var base: Base var y: i32 } var c = Composed { base: Base { x: 10 }, y: 20 } c.base.x // 10 ``` This keeps aggregate initialization unambiguous and avoids layout questions that inheritance introduces. If you need polymorphism or a type hierarchy, use [classes](/docs/language/classes#inheritance). --- ## Destructuring Struct fields can be destructured into individual bindings: ```kairo struct Color { var r: u8 var g: u8 var b: u8 } var color = Color { r: 255, g: 128, b: 0 } var { r, g, b } = color // r = 255, g = 128, b = 0 ``` Use `_` to discard fields you do not need. See [Variables](/docs/language/variables#destructuring) for the full destructuring syntax. --- ## Memory Layout Struct layout follows the platform's C++ ABI members in declaration order with standard padding and alignment rules. Control layout with attributes: ```kairo @packed struct Tight { var a: u8 // offset 0 var b: u32 // offset 1 (no padding) } @align(16) struct Aligned { var data: [u8; 12] } ``` Structs are always value types. A plain `var` declaration allocates on the stack. Heap allocation uses `@create T()`: ```kairo var local = Point { x: 1.0, y: 2.0 } // stack var *heap = @create Point{ x: 1.0, y: 2.0 } // heap ``` See [Pointers](/docs/language/pointers) and [AMT](/docs/language/amt) for allocation and pointer semantics. --- ## Forward Declarations Structs can be forward-declared for use in pointer types before the full definition is available: ```kairo struct Node // forward declaration struct Tree { var root: unsafe *Node } struct Node { var value: i32 var left: unsafe *Node var right: unsafe *Node } ``` --- ## Structs vs Classes | | Struct | Class | |---|---|---| | Member default visibility | `pub` | `priv` for variables | | Methods in body | No (use [extends](/docs/language/extends)) | Yes | | Constructors | No (aggregate init only) | Yes | | Destructors | No | Yes | | Copy semantics | `memcpy` (trivial) | Rule of five | | Inheritance | No | `derives` | | Virtual dispatch | No | Yes | | Aggregate initialization | Yes | No | Use structs for plain data configuration records, protocol headers, math vectors, tuple-like types. Use classes when you need lifecycle control, inheritance, or encapsulation. --- ## Summary ```kairo // Basic struct struct Point { var x: f64 var y: f64 } var p = Point { x: 1.0, y: 2.0 } // Generic struct struct Pair { var first: T var second: T } // Extend with methods and operators extend Point { fn length(self) const -> f64 { return std::sqrt(self.x * self.x + self.y * self.y) } fn op +(self, other: Point) -> Point { return Point { x: self.x + other.x, y: self.y + other.y } } } // Extend with interface conformance extend Point impl Drawable { fn draw(self) -> string { return f"({self.x}, {self.y})" } } // Layout control @packed struct Header { var magic: u16 var version: u8 var flags: u8 } // Composition over inheritance struct Rect { var origin: Point var size: Point } var r = Rect { origin: Point { x: 0.0, y: 0.0 }, size: Point { x: 100.0, y: 50.0 }, } ``` --- ## Enums URL: https://www.kairolang.org/docs/language/enums/ # Enums Enums define a closed set of named variants. In their simplest form, each variant is an integer discriminant -- equivalent to a C++ `enum class`. Variants can also carry structured data (ADT enums), making them Kairo's mechanism for type-safe tagged unions. Dispatch on variants with `match`. See [Control Flow](/docs/language/control-flow#match) for the complete `match` syntax. --- ## Plain Enums A plain enum is a set of named integer constants: ```kairo enum Direction { North, East, South, West, } var dir = Direction::North ``` Variants are comma-separated and scoped to the enum name. The trailing comma is optional. ### Discriminant values Variants are assigned sequential integers starting from 0 by default. Explicit values can be assigned with `=`: ```kairo enum HttpStatus { Ok = 200, NotFound = 404, InternalError = 500, ServiceUnavailable, // 501 } ``` Discriminant expressions must be compile-time constants. Duplicate values are a compile error. Compound expressions are valid for bit flags: ```kairo enum FileMode derives u8 { Read = 1 << 0, // 1 Write = 1 << 1, // 2 Execute = 1 << 2, // 4 RW = (Read | Write), // 3 All = (Read | Write | Execute), // 7 } var perms: FileMode = .RW if perms & FileMode::Read != 0 { // has read permission } ``` --- ## Underlying Type The default underlying type is `u32`. The compiler promotes to a wider unsigned integer if the number of variants exceeds what `u32` can represent, though a warning is emitted for enums exceeding 2^32 variants. Specify an explicit underlying type with `derives` followed by an integer type: ```kairo enum Opcode derives u8 { Nop = 0x00, Load = 0x01, Store = 0x02, Halt = 0xFF, } ``` Only integer types (`u8`, `u16`, `u32`, `u64`, `i8`, `i16`, `i32`, `i64`, `usize`, `isize`) are permitted. The compiler errors if any discriminant does not fit in the specified type. > [!NOTE] > `derives` on an enum specifies the underlying integer type. This is distinct from `derives` on a > [class](/docs/language/classes#inheritance), which declares inheritance. --- ## ADT Enums Variants can carry structured data. Each payload is defined as a set of named fields inside braces: ```kairo enum ParseResult { Success { value: T, bytes_consumed: i32 }, Error { message: string, position: i32 }, EndOfInput, } ``` Variants without a payload (`EndOfInput` above) are plain discriminants. Variants with a payload carry an anonymous struct alongside the discriminant. A single enum can freely mix both. ### Construction ADT variants are constructed with the variant name followed by brace-delimited field assignment, matching [struct](/docs/language/structures) aggregate initialization: ```kairo var result = ParseResult::Success { value: 3.14, bytes_consumed: 4 } var err = ParseResult::Error { message: "unexpected token", position: 12 } var eof = ParseResult::EndOfInput ``` Field names are required. The order of fields in the initializer does not need to match the declaration order. When the enum type can be inferred from context, the `.Variant` shorthand works: ```kairo var result: ParseResult = .Success { value: 3.14, bytes_consumed: 4 } ``` ### Destructuring with `match` Use `match` to dispatch on variants and extract payload fields. Destructured fields use `var` or `const` to control mutability of the bound variable: ```kairo match result { case .Success(var value, var bytes_consumed) { std::println(f"parsed {value} from {bytes_consumed} bytes") } case .Error(const message, const position) { std::println(f"error at {position}: {message}") // message = "other" // compile error: message is const } case .EndOfInput { std::println("nothing left to parse") } } ``` Field names in the destructuring pattern must match the names in the variant declaration. You can omit fields you do not need the compiler does not require exhaustive field extraction: ```kairo match result { case .Error(const message) { // only message is bound, position is ignored log_error(message) } // ... default { } } ``` See [Control Flow](/docs/language/control-flow#match) for the full `match` syntax, including guards (`where`), ranges, and expression form. ### `match` as an expression ADT enums work in expression-form `match`. Each branch must produce the same type: ```kairo var description = match result { case .Success(var value, var bytes_consumed) { f"parsed {value} ({bytes_consumed} bytes)" } case .Error(var message, var position) { f"error at {position}: {message}" } case .EndOfInput { "end of input" } } ``` See [Control Flow](/docs/language/control-flow#match-as-an-expression) for the full expression-form rules. ### Memory layout An ADT enum stores a discriminant tag plus storage sized to the largest variant's payload. The tag is the same integer type as the underlying type (default `u32`). A variant with no payload consumes no extra storage beyond the tag. ``` ParseResult layout: [tag: u32] [payload: max(sizeof Success, sizeof Error)] [padding] ``` The compiler generates a destructor that switches on the tag to destroy the active variant's fields. Copy and move operations follow the same pattern. --- ## Generic Enums Enums with payloads can be generic. Type parameters are declared in angle brackets before the enum name: ```kairo enum LookupResult { Found { key: K, value: V }, NotFound { key: K }, Error { message: string }, } var r = LookupResult::Found { key: "count", value: 42 } ``` Plain enums (no payloads) cannot be generic there is nothing for a type parameter to parameterize. Constrain type parameters with `impl` or `derives` bounds: ```kairo enum CacheEntry { Valid { data: T, expires: i64 }, Expired { last_data: T }, Empty, } ``` See [Bounds](/docs/language/bounds) for the full constraint system. --- ## Shorthand Syntax When the compiler can infer the enum type from context, the `.Variant` shorthand is available in construction, `match` cases, comparison, and assignment: ```kairo var dir: Direction = .North match dir { case .North { go_up() } case .South { go_down() } case .East { go_right() } case .West { go_left() } } if dir == .North { ... } dir = .South ``` The fully qualified form (`Direction::North`) always works and is required when the type cannot be inferred. --- ## Comparison and Assignment Enum values support equality comparison and assignment. Ordering operators (`<`, `>`, `<=`, `>=`) are not available by default extend them if needed: ```kairo var a = Direction::North var b = Direction::South if a == b { ... } // ok if a != b { ... } // ok // if a < b { ... } // compile error: no ordering defined ``` For ADT enums, equality compares the tag only by default. Extend `op ==` to include payload comparison if needed. See [Operators](/docs/language/operators#operator-overloading) for operator extension. --- ## Extends Enums cannot contain methods in their body. Use `extend` blocks to add behavior, matching the same pattern as [structs](/docs/language/structures#extends): ```kairo enum LogLevel { Debug, Info, Warning, Error, Fatal, } extend LogLevel { fn is_severe(self) const -> bool { return self == .Error || self == .Fatal } fn prefix(self) const -> string { match self { case .Debug { return "[DEBUG]" } case .Info { return "[INFO]" } case .Warning { return "[WARN]" } case .Error { return "[ERROR]" } case .Fatal { return "[FATAL]" } } } static fn from_string(s: string) -> LogLevel { match s { case "debug" { return .Debug } case "info" { return .Info } case "warning" { return .Warning } case "error" { return .Error } case "fatal" { return .Fatal } default { return .Info } } } } ``` ### What extends can add | Allowed | Not allowed | |---|---| | Methods | Constructors | | Static functions | Destructors | | Comparison / arithmetic operators | Copy / move assignment | | `fn op as` (type conversion) | | ### Interface conformance Enums can implement interfaces through `extend ... impl`: ```kairo interface Loggable { fn to_log_string(self) const -> string } extend LogLevel impl Loggable { fn to_log_string(self) const -> string { return self.prefix() } } ``` The `extend` block and the enum definition must be in the same file. See [Extends](/docs/language/extends) for the full `extend` system and [Interfaces](/docs/language/interfaces) for interface declarations. --- ## No Associated Data in Variants (Plain Enums Only) For plain enums without the `{ field: Type }` payload syntax, variants are strictly integer constants. If you need per-variant data, use an [ADT enum](#adt-enums). For untagged memory overlays where you manage the active member yourself, use [Unions](/docs/language/unions). --- ## No Inheritance Enums do not support inheriting from other enums. The `derives` keyword on an enum is reserved exclusively for specifying the [underlying type](#underlying-type). --- ## Casting Plain enum values can be cast to their underlying integer type and back: ```kairo var dir = Direction::North var raw = dir as u32 // 0 var back = raw as Direction // Direction::North ``` Casting an integer to an enum that has no matching discriminant is undefined behavior. ADT enums cannot be cast to integers directly they carry payloads that have no integer representation. Cast the tag explicitly if you need the discriminant value. See [Casting](/docs/language/casting) for the full casting model. --- ## Forward Declarations Enums can be forward-declared when the underlying type is specified: ```kairo enum Opcode derives u8 // forward declaration size is known fn decode(op: Opcode) { ... } enum Opcode derives u8 { Nop = 0x00, Load = 0x01, Store = 0x02, Halt = 0xFF, } ``` Without an explicit underlying type, forward declaration is not permitted the compiler needs the size, which depends on the variant count and payload sizes. --- ## Summary ```kairo // Plain enum enum Direction { North, East, South, West, } // Explicit discriminants + underlying type enum Opcode derives u8 { Nop = 0x00, Load = 0x01, Store = 0x02, Halt = 0xFF, } // Bit flags enum FileMode derives u8 { Read = 1 << 0, Write = 1 << 1, Execute = 1 << 2, All = (Read | Write | Execute), } // ADT enum with payloads enum LookupResult { Found { key: K, value: V }, NotFound { key: K }, Error { message: string }, } // Construction var r = LookupResult::Found { key: "count", value: 42 } // Match with destructuring match r { case .Found(var key, var value) { std::println(f"{key} = {value}") } case .NotFound(var key) { std::println(f"{key} not found") } case .Error(var message) { std::println(f"error: {message}") } } // Extended with methods extend Direction { fn opposite(self) -> Direction { match self { case .North { return .South } case .South { return .North } case .East { return .West } case .West { return .East } } } } ``` --- ## Unions URL: https://www.kairolang.org/docs/language/unions/ # Unions Unions overlay multiple fields at the same memory address, sized to the largest member. They are a low-level memory layout tool for hardware registers, binary protocol parsing, and type punning. The user is responsible for tracking which field is active there is no compiler-managed tag. For type-safe tagged unions with compiler-enforced variant tracking, use [ADT enums](/docs/language/enums#adt-enums). --- ## Declaration ```kairo union Register { var as_u32: u32 var as_bytes: [u8; 4] var as_f32: f32 } ``` All fields share the same starting address. The union's size is the size of its largest member, plus any alignment padding. --- ## Usage Read and write fields with plain assignment. No special syntax or `unsafe` block is required: ```kairo var r: Register r.as_u32 = 0xDEADBEEF var byte0 = r.as_bytes[0] // reads overlapping memory as u8 r.as_f32 = 3.14f32 // r.as_u32 now contains the bit representation of 3.14 as f32 ``` Reading a field other than the one last written is type punning the value you get is the raw bit reinterpretation. This is legal but the result depends entirely on the underlying representation. > [!CAUTION] > The compiler does not track which field was last written. Reading the wrong field is not a compile > error it produces whatever bits happen to be in memory. This is the intended use case for unions, > but it means correctness is entirely on the caller. --- ## Trivial Types Only Union fields must be trivially copyable. The following types are permitted: - Integers (`i8`--`i512`, `u8`--`u512`, `isize`, `usize`) - Floats (`f16`--`f512`) - Booleans (`bool`) - Characters (`char`) - Bytes (`byte`) - Pointers (`*T`, `unsafe *T`) - Fixed-size arrays of trivial types (`[T; N]`) - Structs where all fields are trivially copyable - Other unions - Enums (plain, without ADT payloads) Non-trivially copyable types are a compile error: ```kairo union Bad { var text: string // compile error: string has a destructor var data: [i32] // compile error: vector owns heap memory } ``` If you need a union of non-trivial types, use an [ADT enum](/docs/language/enums#adt-enums) the compiler manages construction, destruction, and active-variant tracking for you. --- ## Unions Must Be Named Unions cannot exist as standalone anonymous types. They must always be declared with a name: ```kairo union Pixel { // ok: named union var rgba: u32 var channels: [u8; 4] } ``` --- ## Nesting in Structs and Classes Unions are commonly embedded in structs or classes for structured access to overlapping data: ```kairo struct Packet { var opcode: u8 var payload: PacketData } union PacketData { var as_int: i64 var as_float: f64 var as_bytes: [u8; 8] } var pkt: Packet pkt.opcode = 0x01 pkt.payload.as_int = 42 ``` For a type-safe version of this pattern (where the opcode determines which payload field is valid), use an [ADT enum](/docs/language/enums#adt-enums) instead. --- ## Generic Unions Unions can be generic: ```kairo union Either { var left: T var right: U } var e: Either e.left = 42 ``` Type parameters must resolve to trivially copyable types. The compiler errors at instantiation time if a type argument is not trivially copyable. --- ## Memory Layout Union layout follows the platform's C++ ABI: - Size equals the largest member's size, rounded up for alignment - All fields start at offset 0 - Alignment is the strictest alignment of any member Layout attributes work the same as on structs and classes: ```kairo @packed union Compact { var a: u8 var b: u32 // no padding between a and b at the union level } @align(16) union Aligned { var data: [u8; 12] var value: i32 } ``` --- ## Forward Declarations Unions can be forward-declared for use in pointer types: ```kairo union Payload // forward declaration struct Message { var kind: u8 var data: unsafe *Payload } union Payload { var as_int: i64 var as_bytes: [u8; 8] } ``` --- ## No Extends Unions do not support `extend` blocks. They are raw memory overlays with no behavior adding methods would blur the line between unions and [classes](/docs/language/classes). If you need methods on overlapping data, wrap the union in a struct or class and add behavior there. --- ## No Inheritance Unions do not support `derives`. They cannot inherit from or be inherited by other types. --- ## Summary ```kairo // Basic union union Color { var rgba: u32 var channels: [u8; 4] } var c: Color c.rgba = 0xFF0000FF c.channels[0] // 0xFF (on little-endian) // Generic union union Reinterpret { var a: A var b: B } var bits: Reinterpret bits.a = 3.14f32 std::println(f"bits of 3.14: 0x{bits.b:X}") // Nested in a struct struct HardwareRegister { var address: usize var value: RegValue } union RegValue { var raw: u32 var fields: RegFields } struct RegFields { var low: u16 var high: u16 } var reg: HardwareRegister reg.address = 0x4000_0000 reg.value.raw = 0xDEAD_BEEF reg.value.fields.low // 0xBEEF (little-endian) reg.value.fields.high // 0xDEAD (little-endian) ``` --- ## Interfaces URL: https://www.kairolang.org/docs/language/interfaces/ # Interfaces Interfaces define a set of method signatures that a type must satisfy. They are zero-cost conformance contracts no vtable, no runtime dispatch, no storage overhead. A type conforms to an interface if it has all the required methods with matching signatures, whether or not it explicitly declares conformance. Interfaces are a compile-time mechanism. For runtime polymorphism, use a base [class](/docs/language/classes) with `virtual` methods and dispatch through a base pointer. Interfaces and virtual dispatch solve different problems and can be used together on the same type. --- ## Declaration ```kairo interface Serializable { fn serialize(self) const -> [byte] fn byte_size(self) const -> i32 } ``` An interface body contains method signatures, static function signatures, and operator signatures. No method bodies, no variables, no constants, no nested types. Every member is implicitly `pub` the `priv` and `prot` modifiers are compile errors on interface members. Explicit `pub` is permitted but has no effect. --- ## Conformance A type declares interface conformance with `impl` on a [class](/docs/language/classes) or through `extend ... impl` on a [struct](/docs/language/structures), [enum](/docs/language/enums), or class. The compiler verifies at declaration time that all required methods are present with matching signatures: ```kairo class Document impl Serializable { var content: string fn Document(self, content: string) { self.content = content } fn serialize(self) const -> [byte] { return self.content.to_bytes() } fn byte_size(self) const -> i32 { return self.content.length() } } ``` > [!NOTE] > Conformance is structural, not nominal. A type that has all the required methods with matching signatures satisfies the interface implicitly. The check happens at the point of use. See [Structural conformance](#structural-conformance) section below for details. For structs and enums, conformance goes through `extend`: ```kairo struct Point { var x: f64 var y: f64 } extend Point impl Serializable { fn serialize(self) const -> [byte] { // serialization logic } fn byte_size(self) const -> i32 { return 16 // two f64s } } ``` See [Extends](/docs/language/extends) for the full `extend` system. ### Structural conformance Explicit `impl` is not required. A type that has all the required methods with matching signatures satisfies the interface implicitly. The check happens at the point of use: ```kairo class Logger { fn serialize(self) const -> [byte] { ... } fn byte_size(self) const -> i32 { ... } } // Logger never mentions Serializable, but satisfies it structurally fn write_to_disk(data: T) { ... } write_to_disk(Logger()) // compiles: Logger has serialize() and byte_size() ``` The difference between explicit and implicit conformance is when the check happens at declaration time (`class Foo impl Bar`) or at first use (`fn `). The runtime behavior is identical. --- ## Generic Interfaces Interfaces can declare type parameters: ```kairo interface Container { fn add(self, item: T) fn get(self, index: i32) const -> T fn size(self) const -> i32 } class IntBuffer impl Container { var data: [i32] fn IntBuffer(self) { self.data = [] } fn add(self, item: i32) { self.data.push(item) } fn get(self, index: i32) const -> i32 { return self.data[index] } fn size(self) const -> i32 { return self.data.length() } } ``` Type parameters on interfaces can have constraints: ```kairo interface Registry { fn lookup(self, key: T) const -> U fn store(self, key: T, value: U) } ``` Generic defaults are not permitted on interface type parameters. See [Bounds](/docs/language/bounds) for the full constraint system. --- ## Operators in Interfaces Interfaces can require operator overloads: ```kairo interface Equatable { fn op ==(self, other: Self) const -> bool } interface Convertible { fn op as(self) -> string } ``` `Self` in an interface signature refers to the conforming type. A class that `impl Equatable` must provide `fn op ==(self, other: Self) const -> bool` where `Self` resolves to that class. See [Operators](/docs/language/operators#operator-overloading) for the full list of overloadable operators. --- ## Constructors in Interfaces Interfaces can require constructor signatures: ```kairo interface Defaultable { fn Defaultable(self) } interface Cloneable { fn Cloneable(self, source: T) } ``` A conforming type must have a constructor that matches the parameter list. The constructor name in the implementation uses the type's own name, not the interface name: ```kairo class Config impl Defaultable { var timeout: i32 fn Config(self) { // satisfies Defaultable's constructor requirement self.timeout = 30 } } ``` ### Lifecycle attributes on constructor requirements Constructor requirements can carry `@copy` or `@move` attributes to require a specific lifecycle category. This is how you express "T must be copyable" or "T must be movable" as an interface bound: ```kairo interface Copyable { fn Copyable(self) // default ctor @copy fn Copyable(self, other: Self) } interface Moveable { fn Moveable(self) @move fn Moveable(self, other: Self) } fn store(item: T) -> T { ... } fn consume(item: T) { ... } ``` The standard library will provide canonical lifecycle interfaces; these are just examples. See [Lifecycle Categories](/docs/language/classes#lifecycle-categories) for the underlying model. --- ## Static Methods in Interfaces Interfaces can require static methods: ```kairo interface Parseable { static fn from_string(input: string) -> T static fn from_bytes(data: [byte]) -> T } class Timestamp impl Parseable { var epoch: i64 fn Timestamp(self, epoch: i64) { self.epoch = epoch } static fn from_string(input: string) -> Timestamp { // parsing logic return Timestamp(0) } static fn from_bytes(data: [byte]) -> Timestamp { // parsing logic return Timestamp(0) } } ``` --- ## Return Types with `Self` Interface methods can use `Self` as a return type. `Self` resolves to the conforming type a class that `impl Chainable` and returns `Self` returns its own type: ```kairo interface Chainable { fn then(self, next: Self) -> Self } class Pipeline impl Chainable { fn then(self, next: Self) -> Self { // Self resolves to Pipeline return self } } ``` Kairo has no user-visible reference types [AMT](/docs/language/amt) infers reference semantics where needed. In practice this means `Self` returns enable fluent chaining without `->` or explicit dereferencing: ```kairo Pipeline().then(other).then(another).run() ``` To return a pointer to the conforming type, use `*Self`: ```kairo interface Cloneable { fn clone(self) -> *Self } ``` --- ## Interface Inheritance Interfaces can inherit from other interfaces with `derives`. A type that conforms to the derived interface must satisfy all inherited interfaces as well: ```kairo interface Readable { fn read(self, buffer: [byte], count: i32) -> i32 } interface Seekable { fn seek(self, position: i64) fn tell(self) const -> i64 } interface Stream derives Readable, Seekable { fn close(self) fn flush(self) } ``` A class that `impl Stream` must provide `read`, `seek`, `tell`, `close`, and `flush`: ```kairo class FileStream impl Stream { var fd: i32 fn FileStream(self, path: string) { ... } fn read(self, buffer: [byte], count: i32) -> i32 { ... } fn seek(self, position: i64) { ... } fn tell(self) const -> i64 { ... } fn close(self) { ... } fn flush(self) { ... } } ``` Interface inheritance is purely additive the derived interface's requirements are the union of its own methods and all inherited methods. There is no diamond problem because interfaces carry no implementation or state. Multiple inheritance is permitted: ```kairo interface Loggable derives Serializable, Printable { fn log_level(self) const -> i32 } ``` --- ## Using Interfaces as Bounds The primary use of interfaces is constraining generic type parameters with `impl`: ```kairo fn save(data: T, path: string) { var bytes = data.serialize() write_file(path, bytes) } ``` `impl` checks structural conformance the type satisfies the interface's required method signatures. This is distinct from `derives`, which checks class inheritance. See [Bounds](/docs/language/bounds) for the full constraint system. ```kairo fn save(data: T) { ... } // T has serialize() and byte_size() fn process(data: T) { ... } // T is a subclass of Base ``` --- ## Declaration Scope Interfaces must be declared at module scope. They cannot be nested inside classes, structs, enums, or other interfaces: ```kairo interface Valid { fn check(self) const -> bool } class Outer { // interface Invalid { ... } // compile error: interfaces cannot be nested } ``` See [Modules](/docs/language/modules) for module organization. --- ## No Default Implementations Interface methods have no bodies. Every method is a requirement that the conforming type must fulfill: ```kairo interface Hashable { fn hash(self) const -> u64 // fn hash(self) const -> u64 { return 0 } // compile error: no default implementations } ``` This keeps interfaces as zero-cost contracts. There is no method resolution order, no inheritance of behavior, and no hidden dispatch. If you need shared implementation, use a base [class](/docs/language/classes) with `derives`. --- ## No Variables or Constants Interfaces cannot declare variables, constants, static variables, or eval bindings: ```kairo interface Invalid { // var x: i32 // compile error // const Y: i32 = 10 // compile error // static z: i32 = 0 // compile error fn valid_method(self) // ok } ``` --- ## Summary ```kairo // Basic interface interface Drawable { fn draw(self) const -> string fn bounds(self) const -> (f64, f64, f64, f64) } // Generic interface with constraints interface SortedContainer { fn insert(self, item: T) fn min(self) const -> T fn max(self) const -> T } // Interface with operators interface Arithmetic { fn op +(self, other: Self) -> Self fn op -(self, other: Self) -> Self fn op ==(self, other: Self) const -> bool } // Interface inheritance interface Flushable { fn flush(self) } interface BufferedWriter derives Flushable { fn write(self, data: [byte]) fn buffer_size(self) const -> i32 } // Class conformance class Renderer impl Drawable { fn draw(self) const -> string { return "rendering" } fn bounds(self) const -> (f64, f64, f64, f64) { return (0.0, 0.0, 100.0, 100.0) } } // Struct conformance via extend extend Point impl Drawable { fn draw(self) const -> string { return f"({self.x}, {self.y})" } fn bounds(self) const -> (f64, f64, f64, f64) { return (self.x, self.y, self.x, self.y) } } // Generic bound fn render_all(items: [T]) { for item in items { std::println(item.draw()) } } ``` --- ## Type System URL: https://www.kairolang.org/docs/language/type-system/ # Type System Kairo is statically typed with full type inference. Every value has a single concrete type known at compile time. The type system is nominal two types are the same if they have the same name and generic arguments, not if they happen to have the same structure. --- ## Type Aliases `type` declares a transparent alias for an existing type. The alias and the original type are fully interchangeable: ```kairo type Byte = u8 type Matrix = [[T]] type TokenVec = [T] var b: Byte = 42 // Byte and u8 are the same type var m: Matrix = [[1.0, 2.0], [3.0, 4.0]] var v: TokenVec = [1, 2, 3] ``` Aliases can be generic. Type parameters are declared in angle brackets before the alias name. ### Strict aliasing mode By default, aliases are transparent. The `-fno-type-aliasing` compiler flag makes aliases into distinct types that require explicit construction: ```kairo type MyString = string // With -ftype-aliasing (default): var a: string = "hello" var b: MyString = a // ok: transparent alias // With -fno-type-aliasing: var a: string = "hello" var b: MyString = MyString(a) // explicit construction required b = a // compile error: type mismatch ``` ### Restrictions Type aliases must reference existing named types. Inline anonymous type definitions are not permitted: ```kairo type Pair = (i32, i32) // ok: aliases a tuple type // type Anon = struct { var x: i32 } // compile error: cannot alias anonymous type definitions ``` If you need a named struct, declare a struct. Type aliases are for giving new names to existing types, not for defining new ones. --- ## Type Inference The compiler infers types from initializers, return expressions, and generic arguments. Explicit annotations are optional where inference succeeds. ### Variable inference ```kairo var x = 42 // i32 var y = 3.14 // f64 var z = "hello" // string var v = [1, 2, 3] // [i32] var p = (1.0, true) // (f64, bool) ``` See [Primitives](/docs/language/primitives) for default literal types. ### Generic argument inference Generic type arguments are inferred from the arguments at the call site: ```kairo fn identity(x: T) -> T { return x } identity(42) // T inferred as i32 identity("hello") // T inferred as string ``` This extends to complex generic patterns: ```kairo fn first(items: [T]) -> T { return items[0] } var nums: [i32] = [1, 2, 3] first(nums) // T inferred as i32 from [T] matching [i32] ``` Explicit generic arguments are required only when inference is ambiguous or when no arguments provide type information: ```kairo var s = Stack() // no arguments to infer from, must specify ``` ### Return type inference Return types are never inferred for block-bodied functions or forward declarations. They must be declared explicitly, or they default to `void`: ```kairo fn add(a: i32, b: i32) -> i32 { return a + b } // explicit fn log(msg: string) { std::println(msg) } // defaults to void ``` Expression-bodied functions (`fn foo() = expr`) are the one exception: if the return type annotation is omitted, it is inferred from the expression. ```kairo fn square(x: i32) = x * x // inferred as i32 fn name() = "Kairo" // inferred as string fn typed(x: i32) -> i64 = x as i64 // explicit still allowed ``` See [Functions](/docs/language/functions#expression-bodied-functions) --- ## Implicit Conversions Kairo minimizes implicit conversions. The following are the only implicit conversions in the language: ### Numeric widening Integer and float types can be implicitly widened to a larger type of the same signedness: ```kairo var a: i32 = 42 var b: i64 = a // ok: i32 to i64 var x: f32 = 1.0f32 var y: f64 = x // ok: f32 to f64 ``` Narrowing conversions require an explicit `as` cast. See [Casting](/docs/language/casting). ### `T` to `T?` - Non-nullable to Nullable A non-nullable value is implicitly convertible to its nullable counterpart: ```kairo fn maybe_log(msg: string?) { ... } var s = "hello" maybe_log(s) // string implicitly converts to string? ``` The reverse (`T?` to `T`) requires explicit unwrapping see [Variables](/docs/language/variables#nullable-types). ### Derived-to-base pointer A pointer to a derived class is implicitly convertible to a pointer to its base class: ```kairo class Animal { ... } class Dog derives Animal { ... } fn feed(animal: *Animal) { ... } var dog = Dog("Rex", "Labrador") feed(&dog) // *Dog implicitly converts to *Animal ``` This is always safe the base subobject is at a known offset. The reverse (base-to-derived) requires an explicit cast because it can fail at runtime. See [Casting](/docs/language/casting). ### No other implicit conversions The following conversions are all explicit (require `as`): - Enum to integer or integer to enum - `T?` to `T` (use `unwrap!()`, `??`, or null checking) - Base-to-derived pointer (downcast) - Any pointer to a different pointer type - Integer narrowing or float-to-integer --- ## Subtyping Kairo has a limited subtyping hierarchy: - `!` (never type) is a subtype of every type - A derived class is a subtype of its base class(es) Interface-satisfying types are not subtypes of the interface. Interfaces are constraints checked at compile time via `impl` bounds, not runtime-dispatchable types. For runtime polymorphism, use base class pointers with [virtual dispatch](/docs/language/classes#virtual-dispatch) or `unsafe *void` for type-erased pointers. --- ## The Never Type (`!`) `!` is the return type of functions that never return they panic, loop forever, or call a no-return function: ```kairo fn fatal(msg: string) panic -> ! { panic std::Error::Runtime(msg) } ``` `!` is a subtype of every type, making it valid in expression contexts: ```kairo var x: i32 = if valid { compute() } else { fatal("bad state") } ``` `!` can only appear as a function return type. It cannot be used as a variable type, type parameter, or in any other type position: ```kairo // var x: ! // compile error // var y: Foo // compile error ``` See [Functions](/docs/language/functions#no-return) for no-return function semantics. --- ## Void - The Absence of a Value - Unit Types `void` represents the absence of a value. It is the default return type for functions with no explicit return type. `void` can be used as a type parameter, but can not be used as a variable type: ```kairo var handle: unsafe *void = get_opaque_handle() var safe_handle: *void = get_tracked_handle() var y: Foo // valid - ok var x: void // compile error ``` See [Primitives](/docs/language/primitives#void) for details. --- ## Nullable Types in the Type System `T?` is syntactic sugar for the compiler-intrinsic `Nullable` tagged union. `Nullable` is not directly usable as a type name use the `?` suffix: ```kairo var x: i32? = 42 var y: string? = null ``` ### Nested nullables `T??` is a parser error because `??` is the null-coalescing operator. Use parentheses for nested nullables: ```kairo // var x: i32?? // parse error: ?? is an operator var x: (i32?)? = null // ok: Nullable> ``` Nested nullables are rarely useful. If you find yourself writing `(T?)?`, reconsider the data model. See [Variables](/docs/language/variables#nullable-types) for null checking, safe access, and unwrapping. --- ## Tuple Types Tuples are fixed-size, heterogeneous, ordered groups of values: ```kairo var point: (f64, f64) = (1.0, 2.0) var record: (i32, string, bool) = (42, "answer", true) ``` Single-element tuples do not exist. `(T)` is just `T` the parentheses are grouping, not a tuple constructor: ```kairo var x: (i32) = 42 // same as var x: i32 = 42 ``` There is no unit type or empty tuple `()`. Functions that return nothing use `void`. See [Primitives](/docs/language/primitives#tuples) for tuple syntax and [Variables](/docs/language/variables#destructuring) for tuple destructuring. --- ## Function Types Function pointer types are written as `fn(ParamTypes) -> ReturnType`: ```kairo var add: fn(i32, i32) -> i32 = fn (a: i32, b: i32) -> i32 { return a + b } var log: fn(string) -> void = std::println ``` The `panic` modifier can appear on function types to indicate the function may panic. Callers must handle the panic via `try`/`catch`: ```kairo var risky: fn(i32) panic -> i32 = some_panicking_function try { risky(42) } catch e { // handle } ``` No other modifiers (`const`, `async`, `eval`, `unsafe`) are valid on function pointer types. `const` is a property of methods (it constrains `self`), not of the function signature. `unsafe` selects a different overload namespace, not a different type. `async` and `eval` require named function declarations. See [Functions](/docs/language/functions#function-pointers) and [Closures](/docs/language/closures) for function pointer usage. --- ## Collection Types Collection literal syntax maps to built-in types: | Syntax | Type | Description | |---|---|---| | `[T]` | Vector | Growable contiguous array (ptr + len + cap) | | `[T; N]` | Array | Fixed-size, stack-allocated, `N` must be compile-time | | `{K: V}` | Map | Hash map | | `{T}` | Set | Hash set | These are primitives in the type system, not generic library types. The standard library provides type aliases for the explicit names: ```kairo // In module std: type Vector = [T] type Array = [T; N] type HashMap = {K: V} type HashSet = {T} ``` The literal syntax and the aliased names are interchangeable. `N` in `[T; N]` must be a compile-time evaluable expression. Runtime-dependent array sizes are not supported use `[T]` (vector) for dynamically sized collections: ```kairo eval SIZE = 256 var buffer: [u8; SIZE] = [0; SIZE] // ok: SIZE is compile-time // var dynamic: [u8; runtime_val] // compile error: N must be compile-time ``` See [Eval](/docs/language/eval) for compile-time evaluation rules. --- ## Typeof Identity Semantics `typeof` has dual behavior depending on whether it appears in a type position or an expression position: ### Type position In a type position, `typeof` resolves to the compile-time type of the expression: ```kairo var x = 42 var y: typeof x = 100 // typeof x resolves to i32 at compile time ``` ### Expression position In an expression position, `typeof` returns a `TypeInfo` object: ```kairo var x = 42 var info = typeof x info.get_pretty_name() // "i32" info.get_size() // 4 info.get_align() // 4 ``` ### TypeInfo `TypeInfo` is a built-in class that describes a type at runtime: ```kairo class TypeInfo { fn get_name(self) const -> string // mangled ABI name fn get_pretty_name(self) const -> string // human-readable (e.g. "HashMap") fn get_size(self) const -> usize fn get_align(self) const -> usize fn get_abi_format(self) const -> string // ABI format string for interop fn get_kind(self) const -> TypeKind } enum TypeKind { Primitive, Struct, Class, Enum, Union, Function, Interface, } ``` `get_name()` returns the mangled name following the platform's C++ ABI (Itanium on Unix, MSVC on Windows). `get_pretty_name()` returns a human-readable string including generic arguments. > [!IMPORTANT] > The `TypeInfo` API is still being finalized. Additional methods for querying members, base classes, > and interface conformance may be added in a future update. ### Compile-time `typeof` `typeof` in `eval if` conditions is resolved at compile time: ```kairo fn process(x: T) { eval if typeof T == i32 { // only compiled when T is i32 fast_int_path(x) } else { generic_path(x) } } ``` See [Eval](/docs/language/eval) for compile-time evaluation and [Control Flow](/docs/language/control-flow#compile-time-branching) for `eval if`. --- ## Self `Self` is a type alias that resolves to the enclosing type. It is available in: - [Class](/docs/language/classes#self-and-self) bodies and methods - [Interface](/docs/language/interfaces#operators-in-interfaces) signatures - [Extend](/docs/language/extends) blocks `Self` always refers to a reference to the type. For a pointer, use `*Self`. In a generic class `class Foo`, `Self` resolves to `Foo`: ```kairo class Hold { var value: T fn Hold(self, v: T) { self.value = v } fn replace(self, other: Self) { // Self is Hold self.value = other.value } } ``` `Self` is not available in free functions or at the top level. --- ## Type Identity Two types are the same if they have the same fully qualified name and the same generic arguments. This is nominal identity, not structural: ```kairo struct Point { var x: f64; var y: f64 } struct Vec2 { var x: f64; var y: f64 } // Point and Vec2 are different types despite identical structure // var p: Point = Vec2 { x: 1.0, y: 2.0 } // compile error ``` Generic types are identified by their monomorphized form. `Foo` and `Foo` are different types. `Foo` in one translation unit is the same type as `Foo` in another identity follows the platform's C++ ABI mangling rules. Type aliases are transparent (unless `-fno-type-aliasing` is set) the alias and the original type are the same type for identity purposes. --- ## Summary ```kairo // Type alias type Meters = f64 type Pair = (T, T) // Type inference var x = 42 // i32 var y = [1.0, 2.0] // [f64] fn id(x: T) -> T = x id(42) // T inferred as i32 // Implicit conversions var n: i64 = 42i32 // numeric widening var s: string? = "hello" // T to T? var a: *Animal = &Dog("Rex", "Lab") // derived-to-base pointer // Nullable var x: i32? = null var y = x ?? 0 // null coalescing // typeof var info = typeof x info.get_pretty_name() // "Nullable" var z: typeof x = 10 // type position: resolves to i32? // Function types var f: fn(i32) -> i32 = fn (x: i32) -> i32 { return x * 2 } var g: fn(i32) panic -> i32 = risky_fn // Collections var vec: [i32] = [1, 2, 3] var arr: [u8; 4] = [0, 0, 0, 0] var map: {string: i32} = {"a": 1} var set: {i32} = {1, 2, 3} ``` --- ## Casting URL: https://www.kairolang.org/docs/language/casting/ # Casting All explicit type conversions in Kairo use the `as` keyword. There are no separate cast operators like C++'s `static_cast`, `dynamic_cast`, `reinterpret_cast`, and `const_cast` `as` handles all conversion categories, with safety determined by the source and target types. For implicit conversions (numeric widening, `T` to `T?`, derived-to-base pointers), see [Type System](/docs/language/type-system#implicit-conversions). --- ## Numeric Casts ### Widening Integer and float widening is implicit no `as` required: ```kairo var a: i32 = 42 var b: i64 = a // implicit ``` `as` is permitted but redundant for widening conversions. ### Narrowing (truncation) Narrowing conversions require an explicit `as`. The cast truncates by keeping the low bits of the source value: ```kairo var x: i64 = 1000 var y: i8 = x as i8 // truncates to low 8 bits: -24 var big: u32 = 0xDEADBEEF var small: u8 = big as u8 // 0xEF ``` Truncation never panics. The `as` keyword is the programmer explicitly accepting potential data loss. ### Float to integer Float-to-integer casts truncate toward zero, matching C++ behavior. Out-of-range values saturate instead of producing undefined behavior: ```kairo var f: f64 = 3.9 var i: i32 = f as i32 // 3 (truncates toward zero) var neg: f64 = -2.7 var n: i32 = neg as i32 // -2 var huge: f64 = 1.0e18 var s: i32 = huge as i32 // i32 max (2147483647) saturates ``` ### Integer to float Integer-to-float casts may lose precision for large values but never fail: ```kairo var x: i64 = 9007199254740993 // 2^53 + 1 var f: f64 = x as f64 // rounded f64 cannot represent this exactly ``` ### Signed/unsigned conversion Casting between signed and unsigned integers of the same width reinterprets the bit pattern: ```kairo var s: i8 = -1 var u: u8 = s as u8 // 255 (same bits, different interpretation) ``` --- ## Pointer Casts ### Upcasting (derived to base) Derived-to-base pointer conversion is implicit no `as` required: ```kairo class Animal { ... } class Dog derives Animal { ... } var dog = Dog("Rex", "Labrador") var animal: *Animal = &dog // implicit upcast ``` ### Downcasting (base to derived) Base-to-derived casts come in two forms: **Asserting downcast** panics if the runtime type does not match. The function must have the `panic` specifier or the cast must be inside a `try` block: ```kairo fn process(animal: *Animal) panic { var dog = animal as *Dog // panics if animal is not a Dog dog->fetch() } ``` **Checked downcast** returns a nullable pointer. `&null` if the runtime type does not match: ```kairo fn process(animal: *Animal) { var dog = animal as *Dog? // null if animal is not a Dog if dog != &null { dog->fetch() } } ``` Both forms perform a runtime type check using the vtable (the class must have at least one `virtual` method). Downcasting a non-polymorphic class is a compile error. ### Raw pointer cast Casting to `unsafe *T` reinterprets the pointer with no type checking equivalent to C++'s `reinterpret_cast`. The compiler performs no validation: ```kairo var ptr: *i32 = &some_value var raw = ptr as unsafe *void // type erasure var back = raw as unsafe *i32 // reinterpret caller must ensure correctness ``` Casting between `unsafe *T` types is always permitted. The result is the same pointer value with a different type no runtime check, no adjustment. > [!CAUTION] > Raw pointer casts bypass AMT's safety guarantees. Casting to `unsafe *void` erases type information > permanently the compiler cannot verify the correctness of a subsequent cast back. Use only for > C/C++ interop, custom allocators, and other low-level scenarios. ### Pointer to integer Casting a pointer to an integer extracts the numeric address. No `unsafe` block is required: ```kairo var ptr: *i32 = &some_value var addr = ptr as usize // ok: numeric address var truncated = ptr as u8 // warning: truncation of pointer value ``` `usize` is the natural target type since it matches pointer width. Casting to a narrower integer produces a truncation warning. ### Integer to pointer Casting an integer to a pointer fabricates a pointer from a numeric address. The result must be an `unsafe` pointer safe pointers require provenance tracking that an integer cannot provide: ```kairo var addr: usize = 0x7FFE_0000_1000 var ptr = addr as unsafe *i32 // ok: fabricating an unsafe pointer // var bad = addr as *i32 // compile error: cannot create safe pointer from integer ``` --- ## Enum Casts ### Plain enums Plain enums can be cast to their underlying integer type and back: ```kairo enum Direction derives u8 { North = 0, East = 1, South = 2, West = 3, } var raw = Direction::North as u8 // 0 var dir = 2u8 as Direction // Direction::South ``` Casting an integer to an enum that has no matching discriminant is undefined behavior. The compiler does not insert a runtime check. The cast target must match the underlying type. `Direction::North as i32` requires the enum to be backed by `i32`, or an intermediate cast: `Direction::North as u8 as i32`. ### ADT enums ADT enums cannot be cast to integers. The discriminant tag is an internal implementation detail. If you need the tag value, expose it through an `extend` method: ```kairo enum ParseResult { Success { value: T, consumed: i32 }, Error { message: string }, EndOfInput, } // ParseResult::Success { ... } as u32 // compile error: ADT enums cannot be cast to integers extend ParseResult { fn tag(self) const -> u32 { match self { case .Success { 0 } case .Error { 1 } case .EndOfInput { 2 } } } } ``` --- ## Nullable Collapsing Cast Casting a nullable value `T?` to its underlying type `T` produces a non-null value. If the source is null, a default-constructed value of `T` is used instead: ```kairo var x: i32? = null var y = x as i32 // 0 (default-constructed i32) var s: string? = "hello" var t = s as string // "hello" ``` The target type `T` must be trivially default-constructible. If the type has a deleted default constructor, the collapsing cast is a compile error: ```kairo class NoDefault { fn NoDefault(self) = delete } var obj: NoDefault? = null // var x = obj as NoDefault // compile error: NoDefault has no default constructor ``` This differs from `unwrap!()`, which panics on null instead of default-constructing: | | `x as T` | `unwrap!(x)` | |---|---|---| | Source is non-null | Returns the value | Returns the value | | Source is null | Returns `T()` (default) | Panics | | Requires default constructor | Yes | No | | Requires `panic` / `try` | No | Yes | See [Variables](/docs/language/variables#nullable-types) for other nullable operations (`?.`, `??`, null checking). --- ## User-Defined Conversions (`op as`) Types can define custom conversions by overloading the `op as` operator: ```kairo class Temperature { var celsius: f64 fn Temperature(self, c: f64) { self.celsius = c } fn op as(self) -> f64 { return self.celsius } fn op as(self) -> string { return f"{self.celsius}C" } } var temp = Temperature(100.0) var f = temp as f64 // 100.0 var s = temp as string // "100.0C" ``` `op as` can be overloaded for multiple target types. The compiler selects the overload based on the target type in the `as` expression. `op as` must take only `self` as a parameter and return the target type. See [Operators](/docs/language/operators#special-operators) for the full operator overloading reference. --- ## Cast Summary | Cast | Syntax | Safety | Behavior on failure | |---|---|---|---| | Numeric widening | Implicit | Safe | N/A | | Numeric narrowing | `x as i8` | Truncates | Low bits kept | | Float to int | `x as i32` | Truncates/saturates | Saturates on overflow | | Int to float | `x as f64` | May lose precision | Rounded | | Derived-to-base ptr | Implicit | Safe | N/A | | Base-to-derived ptr (asserting) | `ptr as *Derived` | Runtime check | Panics | | Base-to-derived ptr (checked) | `ptr as *Derived?` | Runtime check | Returns `&null` | | Raw pointer cast | `ptr as unsafe *T` | No check | Reinterpret | | Pointer to integer | `ptr as usize` | Safe | Address value | | Integer to pointer | `n as unsafe *T` | Unsafe | Fabricated pointer | | Plain enum to int | `e as u8` | Safe | Discriminant value | | Int to plain enum | `n as Direction` | UB if no match | No runtime check | | ADT enum to int | N/A | Compile error | Use `extend` method | | Nullable collapse | `x as T` | Safe | Default-constructs on null | | User-defined | `x as TargetType` | Depends on `op as` | Calls user code | | `T` to `T?` | Implicit | Safe | N/A | | `T?` to `T` | `x as T` | Collapsing cast | Default on null | --- ## Casts Not in Kairo | C++ Cast | Kairo Equivalent | |---|---| | `static_cast(x)` | `x as T` | | `dynamic_cast(p)` | `p as *T?` (checked) or `p as *T` (asserting) | | `reinterpret_cast(p)` | `p as unsafe *T` | | `const_cast(p)` | Not supported `const` cannot be stripped at runtime | --- ## Requires Clauses URL: https://www.kairolang.org/docs/language/requires/ # Requires Clauses `requires` attaches compile-time constraints to declarations. A `requires` clause specifies a condition that must hold at compile time if it doesn't, the program does not compile. There is no runtime fallback, no dispatch, no branching. `requires` is a static gate. For runtime-conditional dispatch, see [Where Clauses](/docs/language/where). | Property | Guarantee | |---|---| | Evaluation time | Compile time, always | | Memory safe | Yes | | Runtime cost | Zero | | Valid on functions | Yes | | Valid on types | Yes | | Valid on interface methods | Yes | | Valid on interfaces | Yes (constrains conforming types) | --- ## Basic Syntax A `requires` clause appears after the parameter list and return type, before the function body: ```kairo fn divide(a: i32, b: i32) -> i32 requires b != 0 { return a / b } ``` If the condition cannot be evaluated at compile time, the compiler emits a hard error: ``` error: requires expression is not compile-time evaluable --> src/math.k:1:32 1 | fn divide(a: i32, b: i32) -> i32 2 | requires b != 0 { | ^^^^^^ depends on runtime value 'b' note: for runtime-conditional dispatch, use 'where' ``` Multiple conditions are combined with boolean operators in a single expression: ```kairo fn safe_index(arr: [i32], i: i32) -> i32 requires i >= 0 && i < arr.length() { return arr[i] } ``` --- ## Compile-Time Expressions Any expression the compiler can evaluate statically is valid in a `requires` clause: | Expression | Example | |---|---| | `sizeof T)` | `requires sizeof(T <= 64` | | `alignof T)` | `requires alignof(T >= 16` | | Arithmetic and comparisons | `requires N > 0 && N <= 1024` | | `eval` variables and functions | `requires is_power_of_two(N)` | | Type trait checks | `requires T impl Comparable` | | `typeof` queries | `requires typeof T == i32` | If the expression depends on a runtime value, it belongs in a `where` clause, not `requires`. --- ## Type Constraints `impl` and `derives` bounds can appear in `requires` clauses as an alternative to inline bounds on type parameters. The compiler desugars them identically: ```kairo // These are equivalent: fn save(data: T) { ... } fn save(data: T) requires T impl Serializable { ... } ``` The `requires` form is preferred when constraints are long or involve relationships between multiple type parameters: ```kairo fn convert(input: T) -> U requires T impl Serializable && U impl Deserializable { var bytes = input.serialize() return U::from_bytes(bytes) } ``` --- ## Requires on Types `requires` on a class, struct, or enum is checked at instantiation time. A violation is a hard compile error at the point of use: ```kairo class SmallBuffer requires sizeof T <= 128 { var data: [T; 16] } SmallBuffer() // ok: sizeof i32 == 4 SmallBuffer<[u8; 256]>() // compile error: sizeof [u8; 256] == 256, exceeds 128 struct Aligned16 requires alignof T >= 16 { var value: T } enum Compact requires sizeof T <= 8 { Some { value: T }, None, } ``` Type-level constraints are always `requires`. There is no runtime dispatch on type instantiation either the type is valid or it isn't. --- ## Requires on Interface Methods `requires` on an interface method constrains what conforming types must satisfy. The conforming type must implement the method with an identical `requires` clause: ```kairo interface Accumulator { fn add(self, value: i32) -> Self requires value > 0 } class Counter impl Accumulator { var total: i32 fn Counter(self) { self.total = 0 } fn add(self, value: i32) -> Self requires value > 0 { self.total += value return self } } ``` Because interfaces are zero-cost structural contracts, `requires` is the only valid constraint keyword on interface members. `where` is not permitted runtime dispatch inside an interface method would violate the zero-cost guarantee. --- ## Requires on Interfaces A `requires` clause on an interface declaration constrains which types can conform. A type that does not satisfy the `requires` cannot implement the interface, even if it has all the required methods: ```kairo interface SmallStorable requires sizeof T <= 64 { fn store(self) -> [byte] fn load(data: [byte]) -> T } ``` This is distinct from `requires` on an individual method it applies to the entire interface as a precondition on conformance. --- ## Requires and eval `requires` expressions are evaluated by the same compile-time engine as `eval`. Any `eval` function or variable is usable in a `requires` clause: ```kairo eval fn is_power_of_two(n: i32) -> bool { return n > 0 && (n & (n - 1)) == 0 } fn aligned_buffer() -> [u8; N] requires is_power_of_two(N) { return [0; N] } aligned_buffer<256>() // ok: 256 is a power of two aligned_buffer<300>() // compile error: 300 is not a power of two ``` See [Eval](/docs/language/eval) for compile-time evaluation rules. --- ## Requires and panic `requires` and `panic` serve different purposes and can coexist. `requires` filters invalid inputs before the function body executes. `panic` handles valid inputs that produce errors during processing: ```kairo fn parse_port(input: string) panic -> i32 requires input.length() > 0 { var port = std::parse(input) if port < 0 || port > 65535 { panic std::Error::Runtime("port out of range") } return port } ``` The `requires` clause rejects empty strings at compile time (when the argument is a constant) or documents the precondition for the caller. The `panic` handles the runtime error case of a non-empty string that isn't a valid port. --- ## Summary ```kairo // Compile-time size constraint on a generic fn stack_alloc() -> T requires sizeof T <= 4096 { // ... } // Type constraint in requires clause fn transform(input: T) -> U requires T impl Readable && U impl Writable { // ... } // Requires on a class class InlineBuffer requires sizeof T <= 64 { var data: [T; 8] } // Requires on an interface method interface Validator { fn validate(self, input: string) -> bool requires input.length() > 0 } // Requires with eval function eval fn fits_in_cache_line(n: i32) -> bool = n <= 64 struct CacheAligned requires fits_in_cache_line(sizeof T) { var value: T } ``` --- ## Where Clauses URL: https://www.kairolang.org/docs/language/where/ # Where Clauses `where` attaches runtime-conditional constraints to function declarations. A `where` clause specifies a condition that is checked at the call site if it fails, execution falls through to the next matching overload. The compiler folds multiple `where`-constrained overloads into a single function with branch dispatch. For compile-time constraints, see [Requires Clauses](/docs/language/requires). | Property | Guarantee | |---|---| | Evaluation time | Runtime | | Memory safe | Yes | | Thread safe | Yes | | Undefined behavior | No | | Requires unsafe | No | | Valid on functions | Yes | | Valid on types | No use `requires` | | Valid on interfaces | No interfaces are zero-cost, use `requires` | --- ## Basic Syntax A `where` clause appears after the parameter list and return type, before the function body: ```kairo fn sqrt(x: f64) -> f64 where x >= 0.0 { return std::math::sqrt(x) } fn sqrt(x: f64) -> f64 { return 0.0 // fallback for negative input } ``` At the call site, the compiler generates a branch. If the condition holds, the constrained overload runs. If not, execution falls to the next candidate. Multiple conditions are combined with boolean operators: ```kairo fn safe_divide(a: i32, b: i32) -> i32 where b != 0 && a >= 0 { return a / b } fn safe_divide(a: i32, b: i32) -> i32 { return 0 // fallback } ``` --- ## Fallback Requirement Every `where`-constrained overload must have a fallback an overload with no `where` clause (or a `where` clause that covers the remaining cases). If no fallback exists, the compiler emits an error: ``` error: no fallback overload for 'sqrt' when where condition fails --> src/math.k:1:1 1 | fn sqrt(x: f64) -> f64 2 | where x >= 0.0 { note: add an overload without a 'where' clause to handle all remaining cases ``` The fallback is the compiler's guarantee that every call site has a valid code path. --- ## Declaration Order Determines Priority When multiple `where`-constrained overloads exist for the same function, the compiler checks them in declaration order. The first satisfied condition wins. The unconstrained overload is always the final fallback: ```kairo fn categorize(x: i32) -> string where x > 100 { return "high" } fn categorize(x: i32) -> string where x > 0 { return "positive" } fn categorize(x: i32) -> string { return "non-positive" } ``` Conceptual codegen: ```kairo fn categorize(x: i32) -> string { if x > 100 { return "high" } else if x > 0 { return "positive" } else { return "non-positive" } } ``` Declaration order is the programmer's explicit priority control. The compiler does not attempt to determine which condition is "more specific" it chains them sequentially in the order they appear in source. > [!WARNING] > A `where` condition that is a strict subset of a preceding condition will never fire. The > compiler emits a warning when it can statically determine that a `where` clause is unreachable: > > ```kairo > fn process(x: i32) -> string where x > 50 { return "high" } > fn process(x: i32) -> string where x > 100 { return "very high" } // warning: unreachable > fn process(x: i32) -> string { return "low" } > ``` --- ## Overlapping Conditions If two `where` clauses have overlapping conditions that cannot be statically determined to be disjoint, the compiler emits a warning: ```kairo fn handle(x: i32) -> string where x > 0 && x < 100 { return "small positive" } fn handle(x: i32) -> string where x > 50 { return "over 50" } // warning: overlaps with above fn handle(x: i32) -> string { return "other" } ``` Declaration order still determines the result `x == 75` hits the first overload but the warning signals that the intent may not match the behavior. --- ## Where in Match The `where` keyword also appears in `match` arms as a guard condition. Match guards are always evaluated at runtime and are independent of the overload dispatch system they are simple boolean filters on a matched pattern, not function overloads: ```kairo match result { case .Found(var key, var value) where value > 0 { process(key, value) } case .Found(var key, var value) { skip(key) } default { } } ``` A match guard that fails causes the arm to be skipped and evaluation continues to the next arm. See [Control Flow](/docs/language/control-flow#match) for the full `match` syntax. --- ## Runtime Dispatch and Timing `where` dispatch introduces a branch at the call site. In most cases this is a single conditional jump that the branch predictor handles efficiently. However, if the constrained function processes sensitive data, the branch structure can leak information through execution time differences. > [!CAUTION] > Runtime `where` dispatch creates observable branching. If the function handles sensitive data > (cryptographic keys, authentication tokens, private user data), the timing difference between > the constrained and fallback paths may be measurable by an attacker. Use constant-time > implementations in those cases and do not rely on `where` dispatch for security-critical > branching. --- ## Where and panic `where` clauses and `panic` are orthogonal. `where` selects which overload runs based on a runtime condition. `panic` signals an error from within a function body. They can coexist: ```kairo fn connect(host: string, port: i32) panic -> Connection where port > 0 && port <= 65535 { var conn = tcp_connect(host, port) if !conn.is_valid() { panic std::Error::IO("connection failed") } return conn } fn connect(host: string, port: i32) -> Connection { // fallback: invalid port, return a no-op connection return Connection::invalid() } ``` --- ## Where and unsafe `where` constraints and `unsafe` overloads are in separate namespaces. Safe overloads dispatch among themselves based on `where` conditions. Unsafe overloads are selected explicitly by the caller with the `unsafe` keyword and do not participate in `where`-based dispatch: ```kairo // Safe dispatch: where selects between these fn access(data: [i32], index: i32) -> i32 where index >= 0 && index < data.length() { return data[index] } fn access(data: [i32], index: i32) -> i32 { return 0 // fallback } // Unsafe overload: separate namespace, caller selects explicitly fn access(data: [i32], index: i32) unsafe -> i32 { return data[index] // no bounds check } var a = access(data, 5) // safe dispatch var b = unsafe access(data, 5) // unsafe overload, no where dispatch ``` See [Unsafe](/docs/language/unsafe) for the unsafe overload model. --- ## What where Cannot Do `where` is only valid on free functions and methods. The following are compile errors: ```kairo // compile error: where on a type use requires instead class Buffer where sizeof T <= 128 { var data: [T; 16] } // compile error: where on an interface method interfaces are zero-cost, use requires interface Validator { fn validate(self, input: string) -> bool where input.length() > 0 } ``` Type instantiation is always compile-time. Interface conformance is a zero-cost structural contract. Neither admits runtime dispatch. Use `requires` for both. --- ## Summary ```kairo // Basic runtime dispatch with fallback fn sqrt(x: f64) -> f64 where x >= 0.0 { return std::math::sqrt(x) } fn sqrt(x: f64) -> f64 { return 0.0 } // Multiple constrained overloads declaration order is priority fn classify(temp: f64) -> string where temp > 100.0 { return "boiling" } fn classify(temp: f64) -> string where temp > 0.0 { return "liquid" } fn classify(temp: f64) -> string { return "frozen" } // Where with panic fn parse_positive(input: string) panic -> i32 where input.length() > 0 { var n = std::parse(input) if n <= 0 { panic std::Error::Runtime("not positive") } return n } fn parse_positive(input: string) -> i32 { return 0 } // Where coexisting with unsafe overload fn divide(a: i32, b: i32) -> i32 where b != 0 { return a / b } fn divide(a: i32, b: i32) -> i32 { return 0 } fn divide(a: i32, b: i32) unsafe -> i32 { return a / b } // no check var result = divide(10, 0) // 0 (fallback) var fast = unsafe divide(10, 2) // 5 (unsafe, no dispatch) ``` --- ## Pointers & Raw Pointers URL: https://www.kairolang.org/docs/language/pointers/ # Pointers & Raw Pointers Kairo has two pointer types: safe pointers (`*T`) with compiler-tracked lifetime and null checking, and raw pointers (`unsafe *T`) with no tracking and no checks. Both are 8 bytes on 64-bit platforms and hold a memory address. --- ## Safe Pointers (`*T`) `*T` is the default pointer type. The compiler tracks its provenance via [AMT](/docs/language/amt) and inserts null checks on dereference: ```kairo var x = 42 var p: *i32 = &x // p points to x *p = 100 // dereference and assign null check inserted std::println(*p) // 100 ``` ### Non-Null Guarantee `*T` cannot hold null. Attempting to assign `&null` to a `*T` is a compile error: ```kairo var p: *i32 = &null // compile error: *T is non-null var x = 42 var p: *i32 = &x // ok: p is guaranteed valid *p = 100 // no null check needed ``` If you need a pointer that might be absent, use `*T?`: ```kairo var p: *i32? = get_pointer() // might be null if p? { std::println(*p) // safe: compiler has verified p is non-null } ``` For raw nullable pointers with no compiler tracking, use `unsafe *T`: ```kairo var raw: unsafe *i32 = &null // ok: raw pointers can be null if raw != &null { std::println(*raw) } ``` ### Member access Use `->` to access members through a pointer: ```kairo class Config { pub var port: i32 fn Config(self, port: i32) { self.port = port } } var cfg = Config(8080) var ptr: *Config = &cfg ptr->port // 8080 ``` --- ## Raw Pointers (`unsafe *T`) `unsafe *T` is an untracked pointer with no null checks, no bounds checks, and no AMT provenance tracking. It is equivalent to a raw C/C++ pointer: ```kairo var x = 42 var p: unsafe *i32 = unsafe &x // create raw pointer from safe binding *p = 100 // no null check ``` Dereferencing a null `unsafe *T` is undefined behavior. The compiler will not insert a check. Raw pointers are required for [C/C++ interop](/docs/language/c-c++), custom allocators, hardware register access, and any scenario where AMT tracking is not possible or not desired. ### Creating raw pointers ```kairo // From a safe binding var x = 42 var raw: unsafe *i32 = unsafe &x // From heap allocation var raw: unsafe *i32 = unsafe std::alloc(sizeof i32) // From an integer address var raw = 0x7FFE_0000_1000 as unsafe *i32 // Null var raw: unsafe *i32 = &null ``` --- ## Const Pointers The `const` binding rule applies to pointers left-to-right. `const` on the binding prevents reassigning the pointer. `*const T` prevents modifying the pointed-to value: ```kairo var ptr: *i32 = &x // ptr is mutable, *ptr is mutable const ptr: *i32 = &x // ptr is const (cannot reassign), *ptr is mutable *ptr = 10 // ok ptr = &y // compile error var ptr: *const i32 = &x // ptr is mutable (can reassign), *ptr is const *ptr = 10 // compile error ptr = &y // ok const ptr: *const i32 = &x // also remember this is semantically identical to `var ptr: const *const i32 = &x`, but the parser would error on this since this goes against readability and is not idiomatic Kairo. // both const *ptr = 10 // compile error ptr = &y // compile error ``` See [Variables](/docs/language/variables#the-const-binding-rule) for the full `const` model. --- ## Pointer Arithmetic ### On safe pointers (`*T`) Safe pointers support offset arithmetic with integers. The offset is in units of `sizeof T`: ```kairo var arr: [i32; 4] = [10, 20, 30, 40] var p: *i32 = &arr[0] var second = *(p + 1) // 20 var third = *(p + 2) // 30 p = p - 1 // ok: reposition pointer ``` Pointer-to-pointer arithmetic (`p1 - p2`) is not permitted on safe pointers use `unsafe *T` for that. > [!CAUTION] > Safe pointer arithmetic is bounds-checked by AMT only when provenance is trackable. If the pointer > originates from a context where AMT cannot determine the allocation bounds, the arithmetic compiles > but bounds safety is not guaranteed. Prefer array/vector indexing over pointer arithmetic when > possible. ### On raw pointers (`unsafe *T`) Raw pointers support all arithmetic operations with no checks: ```kairo var p: unsafe *i32 = get_buffer() var q: unsafe *i32 = p + 10 // offset by 10 i32s (40 bytes) var distance = q - p // 10 (pointer difference in units of sizeof i32) ``` Out-of-bounds access through raw pointer arithmetic is undefined behavior. --- ## Array-Style Indexing Pointers support bracket indexing, which desugars to offset + dereference: ```kairo var p: *i32 = &arr[0] p[0] // same as *(p + 0) p[2] // same as *(p + 2) ``` Bounds checking follows the same rules as pointer arithmetic AMT checks when provenance is trackable, no checks on `unsafe *T`. --- ## Void Pointers `*void` and `unsafe *void` are opaque pointers that hold an address without type information. They cannot be dereferenced cast to a typed pointer first: ```kairo var handle: unsafe *void = get_opaque_handle() // *handle // compile error: cannot dereference void pointer var typed = handle as unsafe *i32 var value = *typed // ok: typed pointer ``` Void pointers are used for type-erased APIs, opaque handles, and C interop where the concrete type is not known at the Kairo call site. --- ## Double Pointers Pointers to pointers are legal and follow the same rules recursively: ```kairo var x = 42 var p: *i32 = &x var pp: **i32 = &p **pp = 100 // x is now 100 ``` `const` applies at each level independently: ```kairo const pp: *const *i32 = &p // pp cannot be reassigned // *pp (the inner pointer) cannot be reassigned // **pp (the i32) can be modified ``` --- ## Smart Pointer Promotion [AMT](/docs/language/amt) analyzes pointer usage and automatically promotes safe pointers to smart pointers when needed. The smart pointer types are compiler intrinsics exposed through the standard library: | Type | Description | |---|---| | `std::Unique` | Single-owner, exclusive access, freed on drop | | `std::Shared` | Reference-counted, multiple owners, freed when count reaches zero | | `std::Weak<*T>` | Non-owning reference to a `Shared` allocation, does not prevent deallocation | AMT decides which smart pointer type to use based on how the pointer is used across the program. The programmer does not need to annotate or choose AMT handles it automatically: ```kairo fn make_config() -> *Config { var cfg = @create Config(8080) return cfg // AMT determines ownership: likely Unique or Shared } ``` If AMT cannot determine a safe promotion path (e.g., the pointer escapes in a way that prevents tracking), it emits a compile error rather than allowing unsafe behavior. Smart pointer types can be used explicitly to override AMT's decision or to validate compiler behavior: ```kairo var ptr: std::Unique = @create Config(8080) var shared: std::Shared = @create Config(8080) ``` See [AMT](/docs/language/amt) for the full lifetime and promotion model, and [Ownership](/docs/language/ownership) for borrowing semantics. --- ## Heap Allocation Stack allocation is the default. Heap allocation uses `@create T()`: ```kairo var stack_val = Config(8080) // stack-allocated var heap_ptr = @create Config(8080) // heap-allocated, AMT chooses pointer type ``` For raw heap allocation without AMT tracking: ```kairo var raw = unsafe std::alloc(sizeof i32 * 10) // raw allocation, 10 i32s // must be manually freed unsafe std::free(raw) ``` See [AMT](/docs/language/amt) for how allocation interacts with lifetime tracking. --- ## Pointer Comparison | Operator | Behavior | |---|---| | `==` | Compares addresses do both pointers point to the same location? | | `!=` | Negation of `==` | | `===` | Deep equality dereferences both pointers and compares the values | ```kairo var a = 42 var b = 42 var p = &a var q = &b p == q // false: different addresses p === q // true: both point to 42 var r = &a p == r // true: same address ``` `===` is defined only on `*T`, which cannot be null, so there is no null case to handle. For raw pointers (`unsafe *T`), use `==` for address comparison and dereference manually after a null check. `*T?` deep equality goes through the nullable system null-check with `?` first, then `===` the unwrapped pointers. See [Operators](/docs/language/operators#comparison) for the full comparison model. --- ## Function Pointers Function pointers (`fn(T) -> R`) are a separate type from `*T`. They are opaque values that cannot be cast to data pointers or vice versa: ```kairo fn add(a: i32, b: i32) -> i32 = a + b var f: fn(i32, i32) -> i32 = add // var p = f as unsafe *void // compile error: function pointers are not data pointers ``` See [Functions](/docs/language/functions#function-pointers) for function pointer syntax and [Closures](/docs/language/closures) for closures as function pointers. --- ## Pointers and Nullable Types `*T` is non-null by construction. To represent a pointer that might not exist, use `*T?` which wraps the pointer in the standard `Nullable` system: | | `*T` | `*T?` | `unsafe *T` | |---|---|---|---| | Can be null | No | Yes | Yes | | Null check mechanism | N/A | `?.`, `??`, `unwrap!()`, `val?` | `ptr != &null` (manual) | | Dereference null | Cannot happen | Compile error (must check first) | Undefined behavior | | AMT tracked | Yes | Yes | No | `*T?` supports the same null-handling operators as any other nullable type. `unsafe *T` uses manual null comparison, it does not participate in the `Nullable` system. ```kairo var p: *i32 = &x // always valid, no null check needed var q: *i32? = find_ptr() // might be null must check before use var r: unsafe *i32 = &null // raw pointer, nullable, no tracking if q? { std::println(*q) // compiler knows q is non-null here } var val = q ?? &fallback // use fallback pointer if q is null var forced = unwrap!(q) // panics if null ``` See [Variables](/docs/language/variables#nullable-types) for the full nullable type system. --- ## Summary ```kairo // Safe pointer var x = 42 var p: *i32 = &x *p = 100 // Raw pointer var raw: unsafe *i32 = unsafe &x // Null var null_ptr: *i32 = &null if null_ptr != &null { std::println(*null_ptr) } // Const pointer var ptr: *const i32 = &x // mutable pointer to const value const ptr: *i32 = &x // const pointer to mutable value // Pointer arithmetic var arr: [i32; 4] = [10, 20, 30, 40] var p: *i32 = &arr[0] *(p + 2) // 30 // Heap allocation var heap = @create Config(8080) // Smart pointer (explicit) var unique: std::Unique = @create Config(8080) // Void pointer var opaque: unsafe *void = get_handle() var typed = opaque as unsafe *i32 // Double pointer var pp: **i32 = &p **pp = 999 // Comparison p == q // address comparison p === q // deep value comparison ``` --- ## Ownership URL: https://www.kairolang.org/docs/language/ownership/ # Ownership > [!WARNING] > The ownership model is enforced by [AMT](/docs/language/amt), which is not yet implemented. > AMT development begins at **Stage 2** of the compiler roadmap. This page describes the > intended design. See [AMT](/docs/language/amt) for the implementation timeline. Kairo's ownership model governs how values are created, transferred, borrowed, and destroyed. It works in conjunction with [AMT](/docs/language/amt) to provide memory safety without lifetime annotations and without a separate reference type. The model has two parts: **transfer semantics** (how values move between bindings) and **pointer aliasing** (how multiple pointers to the same value interact). Transfer semantics are determined by the type's lifecycle category. Pointer aliasing is tracked by AMT at compile time. > [!NOTE] > This page covers *who owns a value and when it dies*. It does not cover the safety of individual > pointer *accesses* (bounds, use-after-free, null, data races) those are dereference obligations > discharged by AMT's proof engine, described in [AMT The Dereference Obligation](/docs/language/amt#the-dereference-obligation). > The distinction matters: ownership and escape violations are resolved by *transforming the program* > (debug) or a *hard error* (release), never by a runtime check. Access violations may fall back to a > runtime check. When this page says a violation is "a hard error," it means specifically that no > ownership transformation can rescue it see [AMT The Residual Table](/docs/language/amt#the-residual-table) > for the full classification. --- ## Transfer Semantics Every type in Kairo has a lifecycle category that determines what happens when a value is assigned to a new binding, passed to a function, or returned. The category is set by which transfer constructor the type defines. See [Classes Lifecycle Categories](/docs/language/classes#lifecycle-categories) for the declaration syntax. ### COPY types A type with a `@copy` transfer constructor (explicit or implicit) is copyable. Assignment produces an independent copy both the source and destination are live after the assignment: ```kairo class Buffer { var data: [i32] @copy fn Buffer(self, other: Self) = default } var a = Buffer() var b = a // copy: a and b are independent b.data.push(42) a.data.length() // 0 a is unaffected ``` AMT may **elide a copy into a move** when it proves the source is not used after the transfer and the elision is unobservable. This is a pure optimization with no observable semantic difference. AMT does not elide when the destructor has timing-sensitive side effects see [AMT Copy Elision](/docs/language/amt#copy-elision) for the exact rule. The programmer does not opt into or control copy elision. AMT applies it when safe. ### MOVE types A type with a `@move` transfer constructor can only be moved. MOVE types represent unique ownership of a resource. Moving transfers ownership without duplicating the underlying resource, preventing double destruction and expensive deep copies. Assignment transfers ownership the source is invalidated and cannot be used: ```kairo class UniqueFile { var handle: i32 @move fn UniqueFile(self, other: Self) = default } var a = UniqueFile() var b = a // move: ownership transfers to b // a is invalidated any use after this line is a compile error a.handle // compile error: a has been moved b.handle // ok: b owns the value ``` A type cannot define both `@copy` and `@move` pick one. A type with neither is implicitly COPY with compiler-generated members. ### NON_TRANSFER types A type with both `@copy` and `@move` explicitly deleted cannot be assigned, copied, or moved. The auto-derived `op =` is also `= delete`d as a consequence there is no transfer constructor for it to be generated from. These are scope-bound values: they live and die in the scope where they are created: ```kairo class ScopeLock { @copy fn ScopeLock(self, other: Self) = delete @move fn ScopeLock(self, other: Self) = delete } var lock = ScopeLock() // var copy = lock // compile error: copy deleted // var moved = lock // compile error: move deleted // lock = ScopeLock() // compile error: op = is deleted // lock lives until end of scope, then destructor runs ``` > [!NOTE] > `NON_TRANSFER` types have a specific restriction under threading: they cannot be transfer-captured > into a spawned task (there is nothing to transfer), and may only be shared read-only by address. > See [AMT Threading](/docs/language/amt#threading). ### Structs Structs have value semantics. Assignment copies their object representation byte-for-byte via `memcpy`. Because structs cannot define constructors, destructors, or transfer constructors, this copy is always valid. ```kairo struct Point { var x: f64; var y: f64 } var a = Point { x: 1.0, y: 2.0 } var b = a // memcpy always, unconditionally b.x = 9.0 a.x // 1.0 independent copy ``` See [Structures](/docs/language/structures#copy-semantics). --- ## Function Parameters Function parameters follow the same transfer rules as assignment. Passing a COPY type copies it. Passing a MOVE type moves it the caller cannot use the value after the call: ```kairo fn consume(file: UniqueFile) { // file is owned by consume } var f = UniqueFile() consume(f) // f is invalidated moved into consume f.handle // compile error: f has been moved ``` ```kairo fn inspect(buf: Buffer) { // buf is an independent copy } var b = Buffer() inspect(b) b.data.length() // ok: b is still live, inspect got a copy ``` ### Last-use move optimization For MOVE types, AMT detects when a value is passed to a function and never used again. In this case, the value is moved rather than requiring explicit annotation: ```kairo var m = Moveable() foo(m) // m is moved AMT sees m is not referenced after this line // m is invalidated from here ``` This only applies when the value is genuinely unused after the call. If any subsequent code references the value, it is a compile error (MOVE types cannot be copied). ### Pass-by-pointer optimization When a function takes a parameter by value and the type is larger than a pointer (8 bytes on 64-bit), the compiler may silently pass a pointer instead of copying. This is a **codegen optimization only** the source-level semantics are always by-value. The optimization applies only when AMT can prove the by-value semantics are preserved for the duration of the call: - The parameter is not aliased *and mutated* through any other live pointer while the call is in progress i.e. AMT proves no write reaches the same allocation during the call. - The parameter is not modified inside the callee (or the function takes it as `const`). - The parameter is not stored, returned, or captured. - The function is not `async`. The first condition is stronger than "no other pointer exists." Kairo permits arbitrary mutable aliasing in single-threaded code (see [Pointer Aliasing](#pointer-aliasing)), so AMT cannot rely on the absence of aliases it must prove that no *write* through an alias is observable during the call. If it cannot prove this, the parameter is copied as written. The programmer does not control this optimization and cannot observe it. --- ## Pointer Aliasing Kairo allows multiple pointers to the same value. There is no Rust-style exclusivity rule (one mutable xor many immutable) **in single-threaded code**. Multiple `*T` to the same location is legal, and writing through one pointer is visible through all others: ```kairo var x = 42 var p: *i32 = &x var q: *i32 = &x *p = 100 std::println(*q) // 100 defined behavior ``` > [!IMPORTANT] > This freedom is single-threaded only. The moment an allocation is shared across a thread boundary > (captured into a `spawn`/`thread`), AMT switches to a stricter rule: one writer, or many readers, > never both. Arbitrary mutable aliasing is permitted within one thread because a single-threaded > race is impossible; across threads it is undefined behavior that no runtime check can rule out, so > the rule tightens exactly at the boundary. See [AMT Threading](/docs/language/amt#threading). This applies uniformly regardless of `const`: ```kairo var x = 42 var p: *i32 = &x var q: *const i32 = &x *p = 100 std::println(*q) // 100 *const prevents mutation through q, not through p ``` `const` is a **semantic check on the binding**, not an aliasing constraint. `*const T` prevents the holder from mutating through that pointer. It does not prevent other pointers from mutating the same value. AMT does not change behavior based on `const` qualifiers it tracks provenance and lifetime independently of mutability. ### What AMT enforces AMT does not restrict aliasing patterns in single-threaded code. What it does enforce: **Provenance validity.** A pointer must refer to memory that is still live. Using a pointer after its target has been destroyed is a hard error. This is clause C1 of the dereference obligation, discharged by epoch tracking AMT proves the allocation has not been freed or relocated since the pointer was derived: ```kairo var p: *i32 { var x = 42 p = &x } *p = 10 // hard error: x is destroyed (epoch incremented), p is dangling ``` When provenance is intact but the epoch cannot be proven equal statically, this access falls back to a runtime epoch check rather than a hard error see [AMT Provenance and Epochs](/docs/language/amt#provenance-and-epochs). It is a hard error only when provenance is lost entirely. **Iterator invalidation.** A pointer into a container's buffer is invalidated by operations that may reallocate the buffer: ```kairo var v: [i32] = [1, 2, 3] var p: *i32 = &v[0] v.push(4) // may reallocate v's internal buffer std::println(*p) // hard error: p's provenance is invalidated by push ``` AMT detects this through `.amt` summaries: `push`'s summary records that it may reallocate the backing buffer, so any live pointer into that buffer is flagged at the call site. **Invalidation is a hard error even when AMT can trace the reallocation** it is not a runtime-check fallback. The realloc is provable statically, re-validating every live pointer across every potentially-reallocating call would be expensive, and a use-after-realloc is almost always a real bug, so AMT errors rather than checks. Where the buffer is mutated through a path AMT cannot trace (an opaque container, a raw FFI call), AMT also errors rather than allowing an unprovable access. See [AMT Analysis scope](/docs/language/amt#dereference-obligation). **Stack escape.** A pointer to a stack-allocated value cannot outlive the value. What happens when it tries depends on whether the value is **heap-promotable**: ```kairo fn make() -> *i32 { var x = 42 return &x // debug: AMT rewrites `var x = 42` to a heap allocation and promotes the // escaping pointer (Unique) // release: hard error a heap allocation in a release binary must be visible // in the source, so AMT shows the fix instead of inserting it silently } ``` A stack escape of a heap-promotable value is **debug-transformable / release-error** AMT can lift `x` to the heap, which turns the escape into a legal ownership transfer. A stack escape with *no value to lift* (a borrow with no owner, an escape into an opaque sink where AMT cannot establish the heap-allocation pattern) is a hard error in both modes there is nothing to transform. This is the split the [AMT Stack Pointers](/docs/language/amt#stack-pointers) section details in full. Either way, the fix in release is to allocate explicitly or restructure. > [!NOTE] > **Data-race detection across threads** is enforced by AMT's strict threading mode. When an > allocation is shared across a `spawn`/`thread` boundary, AMT requires a single writer (or > read-only sharing) and treats a second writer, or any unsynchronized write+read, as a hard error. > The synchronization primitives that discharge these obligations depend on Kairo's concurrency > runtime, which is not yet finalized the analysis shape is fixed, the runtime seam is not. See > [AMT Threading](/docs/language/amt#threading) and [Concurrency](/docs/language/concurrency). ### What AMT does not enforce AMT does not prevent multiple mutable pointers to the same value in single-threaded code. This is intentional many valid patterns require mutable aliasing (parent/child pointers, graph structures, cache-and-source patterns). The tradeoff: Kairo allows more programs than Rust at the cost of not statically preventing all aliasing bugs. AMT catches the ones that are provably wrong (dangling, invalidation, and across a thread boundary races) and lets the rest through. ### noalias optimization When AMT proves that two pointers do not alias (point to different allocations or non-overlapping regions), it attaches `noalias` metadata to the LLVM IR. This enables the backend optimizer to perform more aggressive transformations (load/store reordering, vectorization) without the programmer writing anything. This is invisible the source code does not change, and the behavior is identical with or without the tag. --- ## Smart Pointer Promotion and Aliasing When AMT promotes a heap pointer to a smart pointer (in debug mode), the aliasing pattern determines which smart pointer type is chosen. In release, the same analysis produces a hard error naming the type to annotate AMT does not silently change pointer types in a release binary. See [AMT Ownership and Promotion](/docs/language/amt#ownership-and-promotion). ```kairo // Single owner, no aliasing -> Unique var cfg = @create Config(8080) return cfg // AMT: cfg has one owner -> Unique ``` ```kairo // Aliased, both pointers escape -> Shared var cfg = @create Config(8080) server_a.config = cfg server_b.config = cfg // AMT: cfg is aliased across two live bindings -> Shared ``` ```kairo // Aliased, but the alias dies before escape -> Unique var cfg = @create Config(8080) { var tmp: *Config = cfg validate(tmp) } // tmp is dead cfg has single ownership at this point return cfg // AMT: alias was short-lived, cfg is sole owner -> Unique ``` The promotion trigger is not "multiple pointers exist" but "multiple pointers exist AND the aliasing pattern requires shared ownership for safety." A short-lived alias that dies before the owner escapes does not force `Shared`. Because AMT is whole-program, the cases where promotion is actually needed are narrow. Most pointers have fully contained lifetimes and require no promotion at all. See [AMT Promotion Decision](/docs/language/amt#ownership-and-promotion) for the full decision tree. --- ## Closure Captures Closures capture variables from their enclosing scope. The capture mode determines the ownership relationship between the closure and the captured variable. ### Capture by transfer (`|=|`) `|=|` captures all referenced variables by their type's transfer semantics COPY types are copied, MOVE types are moved. Captures happen at closure creation time, not at invocation: ```kairo var buf = Buffer() // COPY type var file = UniqueFile() // MOVE type var closure = fn ()|=| { buf.data.push(1) // operates on the closure's copy file.close() // operates on the moved-in file } buf.data.length() // ok: buf was copied, original is still live file.handle // compile error: file was moved into the closure ``` ### Capture by address (`|&|`) `|&|` captures all referenced variables by address. The closure holds `*T` to each captured variable `&` here is the address-of operator, the same `&` used everywhere else in the language. Mutations through the pointer affect the original: ```kairo var count = 0 var inc = fn ()|&| { count += 1 // modifies the original count through a pointer } inc() inc() count // 2 ``` AMT tracks address captures the same way it tracks any other pointer. If the closure escapes and the captured variable is stack-allocated, the heap-promotable / non-promotable split applies exactly as it does for any stack escape (see [Stack escape](#what-amt-enforces) above): a promotable value is heap-lifted in debug and a hard error in release; a non-promotable one is a hard error in both modes: ```kairo fn make_closure() -> fn() -> i32 { var x = 42 return fn ()|&| -> i32 { return x } // debug: x is heap-lifted and the capture promoted // release: hard error capture by transfer or heap-allocate explicitly } ``` Capture by transfer sidesteps the escape entirely the closure owns its own copy with no lifetime dependency on the enclosing frame: ```kairo fn make_closure() -> fn() -> i32 { var x = 42 return fn ()|=| -> i32 { return x } // ok: x is copied into the closure, no lifetime dependency } ``` > [!NOTE] > When a closure is captured into a spawned task, address captures fall under AMT's threading rules: > a `const &` (read-only) capture shares the allocation read-only across the boundary; a mutating > `&` capture makes the allocation a shared *mutable* allocation and triggers the single-writer > analysis. See [AMT Threading](/docs/language/amt#threading). ### Per-variable capture Mix capture modes per variable. Unqualified names use transfer semantics, `&`-prefixed names capture by address: ```kairo var a = Buffer() // COPY var b = 0 var closure = fn ()|a, &b| { a.data.push(1) // closure's own copy of a b += 1 // modifies the original b through a pointer } ``` See [Closures](/docs/language/closures) for the full capture syntax. --- ## Destruction Order Values are destroyed at the end of their enclosing scope in **reverse declaration order**. This applies to stack-allocated, heap-allocated, and smart-pointer-promoted values alike: ```kairo fn example() { var a = Resource("first") var b = Resource("second") var c = Resource("third") } // destruction order: c, b, a ``` For smart pointers promoted by AMT: - `std::Unique`: the object destructor runs at the end of the owning binding's lexical scope, in reverse declaration order. - `std::Shared`: each binding decrements the reference count at its own scope boundary in reverse declaration order; the *object* destructor and deallocation run once, when the last `Shared` reference's scope ends. The decrement order is lexical and deterministic; the object destructor fires at the final owner, which is not necessarily the last-declared binding in any single scope. See [AMT Destruction Timing](/docs/language/amt#destruction-timing). - `std::Weak<*T>`: invalidated at scope exit. Does not affect the reference count. There is no drop-at-last-use optimization for observable destructors. Destruction of a value whose destructor has side effects (file close, lock release, flush) is tied to lexical scope, so the effect is predictable from reading the source. A value whose destructor is trivial may be reclaimed at last use, since that is unobservable see [AMT Destruction Timing](/docs/language/amt#destruction-timing) for the trivial / non-trivial split. --- ## Moved-From State After a value is moved, the source binding is **invalidated**. Any use of a moved-from binding is a compile error there is no "valid but unspecified" state like C++: ```kairo var a = UniqueFile() var b = a // move a.handle // compile error: a has been moved a = UniqueFile() // ok: a can be reassigned to a new value a.handle // ok: a is live again ``` A moved-from binding can be reassigned. After reassignment, it is live again with the new value. But between the move and the reassignment, any access is a hard error. This is enforced by AMT in both debug and release builds, and it is a **static** determination, not a runtime check there is no runtime fallback for a moved-from access the way there is for an unprovable bounds access. A use-after-move is a logic error the compiler proves at compile time, not a safety property discharged at runtime; the compiler statically tracks which bindings are live and which have been moved. --- ## Summary | Type category | Assignment | Source after | Function param | |---------------|------------|--------------|-----------------------| | COPY | Copy | Live | Copy (caller keeps) | | MOVE | Move | Invalidated | Move (caller loses) | | NON_TRANSFER | Error | N/A | Error | | Struct | memcpy | Live | memcpy (caller keeps) | Escape and ownership violations resolve by *transforming the program* in debug (heap-lift, smart pointer promotion) and a *hard error with the fix* in release. They are never runtime checks that is reserved for access obligations like bounds. See [AMT The Residual Table](/docs/language/amt#the-residual-table). ```kairo // COPY: both sides live after transfer var a = Copyable() var b = a // copy a.method() // ok b.method() // ok // MOVE: source invalidated after transfer var x = Moveable() var y = x // move // x.method() // compile error y.method() // ok // Pointer aliasing (single-threaded): allowed, AMT checks provenance var val = 42 var p = &val var q = &val *p = 100 std::println(*q) // 100 // Closure capture by transfer var m = Moveable() var f = fn ()|=| { m.use() } // m is moved into f // Closure capture by address var n = 0 var g = fn ()|&| { n += 1 } g() // n is 1 ``` --- ## AMT URL: https://www.kairolang.org/docs/language/amt/ # AMT (Automatic Memory Tracking) AMT is a compile-time proof engine. For every pointer dereference in the program, it attempts to prove that the access is safe. When it succeeds, no runtime code is emitted. When it can prove everything except one residual property, it emits the minimal runtime code required to discharge exactly that property and nothing else. When it cannot prove the access safe and no runtime check or code transformation can establish safety, it is a hard compile error. The system is not a pointer tracker that decides where to insert checks. It is a proof engine that asks one question at every dereference: > What is the strongest proof I have that this access is safe? The answer to that question and only the answer determines what code is generated. AMT never asks "should I insert a check here." It asks "which proof obligations remain after static analysis," and emits code for the residual. AMT operates on safe pointers (`*T`) only. Raw pointers (`unsafe *T`) carry no obligations and are never analyzed. The programmer owns their correctness. > [!WARNING] > AMT is not yet implemented. The compiler is currently in the **Stage 1** parsing phase. > AMT development begins at **Stage 2**. The roadmap: > > - **Stage 0** (stable): C++ compiler, transpiles Kairo to C++. > - **Stage 1** (current): self-hosted compiler frontend parser, AST, diagnostics. > - **Stage 1.5**: Stage 1 migrated to Kairo's standard library, fully self-hosting. > - **Stage 2**: compiler rewritten using Kairo's extended feature set. **AMT work begins here.** > > This page describes the intended design and target semantics. The analysis is the target, not the > current state. A separate formal paper will carry the full proofs; this page states the model and > the obligations precisely enough to implement against, but is not itself the proof. --- ## The Dereference Obligation Every safe-pointer dereference `*p` where `p: *T` carries a single safety obligation, stated as a conjunction of independent clauses. The access is sound if and only if every clause holds: ``` deref(p: *T) is sound ⟺ epoch(prov(p)) = live_epoch(alloc(prov(p))) (C1) provenance / use-after-free ∧ 0 ≤ byte_offset(p) (C2) no underflow ∧ byte_offset(p) + sizeof T) ≤ size(alloc(prov(p))) (C3 no overflow / bounds ∧ p ≠ null (C4) nullability ∧ (¬shared(alloc(prov(p))) ∨ readonly_extent ∨ synced) (C5) data-race freedom ∧ (¬tainted(prov(p)) ∨ laundered(p)) (C6) provenance integrity ``` Where: - `prov(p)` is the **provenance** of `p`: which allocation it points into and the epoch at which that pointer value was derived. - `alloc(prov(p))` is the originating allocation; `size(...)` is its byte size; `live_epoch(...)` is its current generation (see [Provenance and Epochs](#provenance-and-epochs)). - `byte_offset(p)` is the offset of `p` from the allocation base, in bytes. Stated in bytes so the `sizeof T` scaling in C3 is explicit and the element-count-times-size multiply cannot silently overflow. Each clause is a distinct **proof domain** with its own analysis. AMT discharges as many clauses as it can statically. The clauses that remain the **residual** determine what is generated. The mapping from a residual clause to a code-generation response is the entire specification of AMT's behavior, and it is given in [The Residual Table](#the-residual-table). The critical design point: the obligation is checked at the **dereference**, never at pointer arithmetic. Forming a pointer value (`var q = p + i`) computes a new provenance fact and emits no code. Only `*q` discharges the obligation against that provenance. This lets AMT carry symbolic offset information through arbitrarily many SSA transformations and discharge or eliminate the obligation as late as possible see [Pointer Arithmetic](#pointer-arithmetic). --- ## The Proof Lattice AMT is a forward dataflow analysis over the program's SSA graph. For each pointer at each program point it computes a **fact tuple**: one element per clause domain, recording how strongly that clause is currently proven. ### Per-clause lattice Each clause domain has a small lattice of proof states. For the structural clauses (C1, C4, C6) the lattice is height-2: ``` Proven (the clause is statically discharged here) ⊏ Residual (the clause must be discharged at runtime, or is a hard error) ``` For the bounds clause (C2/C3) the lattice is height-3, because bounds admits an intermediate state where the property is provable over a region rather than at a single point: ``` Proven (offset statically known in range, no code) ⊏ HoistableResidual (provable over a loop/region, one check at region entry) ⊏ DynamicResidual (must check at this specific dereference) ``` `Proven` is the strongest (least) element. The order `⊑` means "no more proven than." `Residual` and `DynamicResidual` are the weakest elements of their domains. ### The product lattice The fact tuple for a pointer is an element of the product lattice ``` L = L_C1 × L_C2C3 × L_C4 × L_C5 × L_C6 ``` ordered componentwise. The product of finite-height lattices is itself a finite-height lattice; the height of `L` is the sum of the component heights, which is a small constant (independent of program size). The transfer functions for each IR operation are **monotone** with respect to `⊑`: an operation never increases the proof strength of a clause it has no evidence for, and joining facts at control-flow merge points takes the weaker (less proven) state. Monotone transfer functions over a finite-height lattice means the Kleene iteration reaches a fixed point in a bounded number of passes (bounded by the lattice height times the number of program points). **The analysis always terminates.** This is the only termination argument AMT needs; there is no widening, no heuristic iteration cap required for soundness. (A pass ceiling exists for *interprocedural* recursion, see [Summaries](#amt-summaries) but it degrades precision, never soundness: hitting it demotes clauses to Residual, which is always safe.) ### Classifying a dereference The four named proof states from AMT's vocabulary `PROVEN_STATIC`, `PROVEN_RANGE`, `REQUIRES_DYNAMIC`, `UNSAFE` are not elements of the lattice. They are a **classification of the residual set** at a dereference site, derived from the fact tuple: | Residual set at `*p` | Classification | Emitted | |-------------------------------------------------------|--------------------|----------------------------------| | empty (all clauses `Proven`) | `PROVEN_STATIC` | nothing | | `{bounds}` only, and bounds is `HoistableResidual` | `PROVEN_RANGE` | one assertion at region entry | | any other non-empty residual | `REQUIRES_DYNAMIC` | per-clause runtime code at `*p` | | `p` is `unsafe *T` | `UNSAFE` | nothing (analysis not run) | The compiler walks every dereference, reads its residual set, classifies it, and emits accordingly. The overwhelming majority of dereferences in well-structured code classify as `PROVEN_STATIC` and generate a bare `mov`. --- ## The Residual Table A residual clause does not have a uniform response. Bounds falls back to a runtime check; a lost provenance is a hard error; a nullable dereference is a hard error in release but a transform in debug. The response is **per-clause**, and the full mapping is the specification of the system: | Residual clause / condition | Response | Tier | |--------------------------------------------------------------|-------------------------------------------------------|------| | **C3 bounds**, range residual | hoisted or local bounds check, **both modes** | R | | **C1 epoch (UAF)**, provenance intact | epoch comparison at deref, **both modes** | R | | **C1 epoch (UAF)**, provenance lost | hard error, both modes | N | | **C4 nullability** | debug: insert null check + panic; release: hard error | P | | **Ownership/escape**, value is heap-promotable | debug: promote to smart pointer; release: hard error | P | | **Stack escape**, value is heap-promotable | debug: rewrite to heap alloc + promote; release: error| P | | **Stack escape**, value not heap-promotable | hard error, both modes | N | | **Scoped-allocator escape** | hard error, both modes | N | | **Opaque-container / untraceable escape** | hard error, both modes | N | | **Iterator invalidation** (even when traceable) | hard error, both modes | N | | **Moved-from use** | hard error, both modes | N | | **C5 race**: 2nd writer to a shared allocation | hard error, both modes | N | | **C5 race**: any access-clause residual on shared *mutable* | hard error (strict mode), both modes | N | | **C5**: residual on shared *read-only* allocation | normal tier-R check applies | R | | **C6 taint**, provenance recoverable | runtime check | R | | **C6 taint**, provenance opaque | hard error, demand `unsafe` / launder | N | The three response classes are: - **Tier R, runtime-check fallback, both debug and release.** The residual is a property that can be established by a cheap, side-effect-free runtime check. The program is unchanged; it pays a comparison. **This is the only tier that emits runtime code in a release build.** - **Tier P, debug transforms, release hard-errors with the fix.** The residual can be established by *changing the program* (inserting a smart pointer, lifting a stack value to the heap, inserting a null guard). Debug performs the transformation and warns. Release refuses, because the transformation would be a hidden allocation or hidden control flow in a release binary, and emits a hard error showing the exact source change the programmer must make. - **Tier N, never allowed, hard error in all modes.** No runtime check and no transformation can establish safety, because the information required to even *form* a check is absent (lost provenance, undeterminable ownership) or the property is genuinely uncheckable (a data race is UB that a single-threaded check cannot rule out). The organizing principle in one sentence: **debug is allowed to rewrite your code to make it work; release makes you write the code that debug would have written; some things are wrong no matter who writes them.** ### Why bounds is Tier R but nullability is Tier P Both are access-safety clauses. They differ in the *nature of the fallback*. A bounds check (`assert(off < count)`) does not change the meaning of the program. `buf[i]` still means `buf[i]`; the access either proceeds or panics on exactly the inputs that would have been UB. Emitting it in release costs one predictable branch and is the price of permitting safe pointer arithmetic with runtime indices. That is the entire point of Kairo's pointer model see [Pointer Arithmetic](#pointer-arithmetic). A null fallback (`if (p == null) panic()`) *introduces a control-flow edge that is not visible in the source*. A release binary that silently panics on a null dereference the programmer never guarded has hidden control flow, which violates the same guarantee that forbids hidden allocation. So nullability is Tier P: debug inserts the guard so you can keep moving, release makes you either prove non-null or handle the null case explicitly. --- ## Pointer Arithmetic Kairo permits pointer arithmetic on safe pointers. This is a deliberate departure from Rust's safe subset, and AMT is what makes it sound. ```kairo var a: *i32 = @create i32(120) *(a + 10) = 19 ``` At this dereference AMT knows the allocation base, element type (`i32`), element size (4 bytes), and element count (120). The offset 10 is a compile-time constant. C3 reduces to `10 < 120`, proven statically. C1, C2, C4, C5, C6 are all proven. Residual set is empty -> `PROVEN_STATIC`: ``` mov [rdi + 40], 19 ``` No check. No metadata. Nothing. ### Runtime offset ```kairo var i = std::input() *(a + i) = 19 ``` Now C3 cannot be proven (the offset is runtime data) and C2 cannot be proven (`i` is signed and may be negative). Every other clause holds. Residual set is `{bounds}`, not hoistable -> `REQUIRES_DYNAMIC`: ```asm assert((0 <= i) && (i < 120)); mov [rdi + i*4], 19 ``` Note both halves of the check. **`i < 120` alone is unsound** for a signed offset: a negative `i` passes it and reads out of bounds backward. The canonical obligation is `0 <= off AND off + sizeof T) <= size`, and AMT emits both comparisons unless it can prove non-negativity separately (e.g. `i` is unsigned, or dominated by a `i >= 0` guard. For an unsigned offset the lower-bound clause C2 is discharged statically and only the upper comparison is emitted. The check is on the **offset**, computed before the pointer is formed never on the resulting pointer address. Checking `result_ptr < end` after forming the pointer is the bypassable version: if `i * sizeof T` overflowed the address width, the formed pointer can wrap below `end` and pass a post-hoc check. AMT checks the offset in a width that cannot wrap for any in-allocation offset (the valid offset range is bounded by `size`, which is known), then forms the pointer. ### Hoisting ```kairo for i in 0..<120 *(a + i) = 0 ``` AMT proves `0 <= i < 120` holds for the entire loop body from the loop's induction structure. The bounds clause is `HoistableResidual` if it is the only residual -> `PROVEN_RANGE`. The per-iteration check is lifted to a single check before the loop: ```asm loop: mov [rdi + i*4], 0 ; no per-iteration check ``` And when the loop bound and the allocation size derive from the same value: ```kairo var n = std::input() var a: *i32 = @create i32(n) for i in 0.. AMT does not present an unprovable bounds access as a *warning*. The programmer did nothing > questionable; the allocation size is simply runtime data. It is reported as an **optimization > remark** when remarks are enabled, naming the fact that blocked the proof: > `[amt] runtime bounds check at : allocation size depends on runtime value 'n'; cost: one > check; to eliminate: bound the index by a compile-time constant or hoist the proof.` --- ## Provenance and Epochs Use-after-free is not a separate debug-only feature. It is clause C1 of the dereference obligation, and it is discharged by the same lattice machinery as bounds. Every allocation carries a **generation** (an epoch) in its provenance fact. Any operation that can free or relocate the allocation increments its epoch: - explicit `@destroy` - scope exit of the owning binding - ownership transfer (move) out of the owning binding - reallocation (e.g. a `Vec` growing its backing buffer) - the close of a scoped allocator that owns the allocation A pointer value derived at epoch *e* records *e* in its provenance. Clause C1 requires `epoch(prov(p)) == live_epoch(alloc(prov(p)))` at the dereference. Statically, most epochs are provably equal there is no intervening freeing operation between derivation and use so C1 is `Proven` and emits nothing. When AMT cannot prove the epochs equal (a free *might* have happened on some path), C1 is residual: ```kairo var buf: *i32 = @create i32(64) var p = buf + 5 realloc(buf) // increments buf's epoch *p = 1 // C1 residual: p was derived at the old epoch ``` If provenance is intact (AMT still knows which allocation and can read the live epoch), C1 residual is **Tier R**: an epoch comparison at the dereference, in both modes. If provenance has been lost entirely (the pointer was laundered through an untracked path and AMT no longer knows the allocation), there is nothing to compare against and it is **Tier N**: a hard error. Folding UAF into the same obligation as bounds is what makes the architecture scale: there is one analysis, one lattice, one residual table, and "use-after-free detection" is not a bolted-on sanitizer but a clause that is either proven away or checked like any other. --- ## Ownership and Promotion Provenance and epochs govern whether an *access* is safe. A separate analysis governs *where a value lives and who frees it*. Its residuals are Tier P (transformable) or Tier N (unfixable), never Tier R there is no runtime check for "who owns this." When AMT determines that a heap pointer needs ownership semantics, it selects a smart pointer type from the pointer's whole-program usage. In **debug** it performs the promotion and warns; in **release** it emits a hard error naming the type to annotate. ### `std::Unique` single owner Exactly one live binding holds the pointer at any point. Ownership may transfer between scopes; the count of live owners is always one. ```kairo fn make_config() -> *Config { var cfg = @create Config(8080) return cfg // debug: promote cfg -> std::Unique // release: error, annotate the return type } ``` ### `std::Shared` multiple owners Multiple live bindings refer to the same allocation and single ownership cannot be proven. The allocation is reference-counted; the object is destroyed when the last `Shared` reference's scope ends. ```kairo fn share_config(a: *Server, b: *Server) { var cfg = @create Config(8080) a.config = cfg b.config = cfg // debug: promote cfg -> std::Shared } ``` A short-lived alias that dies before the owner escapes does **not** force `Shared`. The trigger is not "multiple pointers exist" but "multiple pointers are *live* at a point where shared ownership is required for safety." ```kairo var cfg = @create Config(8080) { var tmp: *Config = cfg validate(tmp) } // tmp is dead here return cfg // single owner at the escape -> Unique ``` ### `std::Weak<*T>` back-reference A pointer that, promoted to `Shared`, would close a reference cycle (A -> B -> A). `Weak` does not contribute to the count and does not keep the target alive; access requires a liveness check. ```kairo class Node { var child: *Node var parent: *Node // points back at the owner -> Weak } ``` ### No promotion A pointer whose lifetime is fully contained, or provably bounded by its referent's lifetime, stays a plain `*T` with no overhead: ```kairo fn process() { var data = @create Buffer(1024) fill(data) consume(data) @destroy data // fully contained -> plain *T } ``` ### Field access and interior pointers `foo.bar.baz` is `PROVEN_STATIC` when `foo` is non-null and the field offsets are static: the access inherits `foo`'s provenance with a constant offset, and its obligation is exactly `foo`'s obligation. Taking an interior pointer `var p = &foo.bar` creates a new pointer whose provenance is derived from `foo`'s allocation at `foo`'s current epoch. Dropping or moving `foo` increments that epoch, so a later `*p` fails C1 by the same mechanism as any other use-after-free. Interior pointers are not special-cased; they reuse the epoch machinery. --- ## Stack Pointers A stack value has no heap allocation behind it, so it cannot be promoted to a smart pointer. But the (B) model splits stack escape into two cases by whether the value is **heap-promotable** that is, whether AMT can mechanically rewrite the stack allocation into a `std::create` call. **Heap-promotable escape Tier P.** A plain local whose address escapes via return or store, where the value's type can be heap-allocated: ```kairo fn make() -> *i32 { var x = 42 return &x // debug: rewrite `var x = 42` into `var x = @create i32(); *x = 42`, // then promote the escaping pointer (Unique) // release: hard error, heap-allocate explicitly or restructure } ``` In debug, AMT lifts `x` to the heap and the escape becomes a legal ownership transfer. In release this is a hard error, because silently turning a stack allocation into a heap allocation in a release binary is exactly the hidden allocation the language forbids. The diagnostic shows the fix: ``` error[AMT]: address of stack local 'x' escapes its scope --> src/m.k:3:12 3 | return &x | ^^ 'x' is stack-allocated and destroyed at end of make() note: in debug, AMT would heap-promote: var x = @create i32() help: allocate on the heap explicitly, or restructure to avoid the escape ``` **Non-promotable escape Tier N.** A borrow with no value to lift (a pointer into a structure AMT cannot heap-allocate, an escape into an opaque sink where AMT cannot even establish the heap-allocation pattern) is a hard error in both modes. There is nothing to transform. Dangling within a function is likewise a hard error in both modes the value is gone, there is no allocation to lift, and the access is statically known to be invalid: ```kairo fn also_bad() { var p: *i32 { var x = 42 p = &x } // x destroyed here (epoch incremented) *p = 10 // hard error: p's provenance epoch is stale } ``` --- ## Threading Kairo owns its threading runtime. All concurrency flows through `thread` and `spawn`, and **only functions marked `async` may be threaded or spawned**. `await` operates on the result of a `thread` or `spawn` call and is the join point. This is what makes static race-freedom tractable. Every concurrent edge in the program is a syntactic `thread`/`spawn` call site there are no hidden threads, no callbacks invoked from another thread, no library spawning work behind your back. AMT knows exactly where every thread boundary is, which is the precondition for proving the absence of races rather than checking for them at runtime. > [!NOTE] > The precise distinction between `thread` and `spawn`, and the synchronization primitives that > establish happens-before, depend on Kairo's concurrency runtime, which is not yet finalized. The > **analysis shape below is fixed**; the runtime seam is marked where it occurs. A synchronization > primitive, when it lands, is anything that injects a happens-before edge AMT can observe; crossing > one re-permits an otherwise-forbidden access. ### Strict mode Between a `thread`/`spawn` and its corresponding `await` (the task's **extent**), AMT enters a stricter analysis mode. Any allocation reachable by a pointer captured into the task is a **shared allocation** for that extent, and every access to it is analyzed under the rules below instead of the single-threaded rules. The defining rule of strict mode: **Tier R collapses into Tier N for shared mutable allocations.** A runtime check reads metadata the allocation's size, its epoch tag and in a threaded extent that metadata can be mutated by another thread, so the check *itself* races. You cannot check your way to safety across threads. A clause that would fall back to a runtime check in single-threaded code must instead be proven statically for a shared mutable allocation, or it is a hard error. The one exception: if a shared allocation is provably **read-only** for the entire extent (no task holds a mutating pointer to it), its metadata is stable, no thread writes it, and ordinary Tier-R checks on it are sound again. Tier R survives only on read-only shared state. ### The rule set 1. **Single writer per shared allocation.** Within an extent, at most one task may hold a *mutating* (`*T`, non-`const`) pointer to a given allocation. The second live writer main+task or task+task with no synchronization between them is a hard error. This is write-exclusivity, not Rust's full write-xor-read: you may have one writer and nothing else, or many readers. 2. **Many readers OR one writer, across the boundary.** This is the single point where Kairo adopts exclusivity and it applies **only inside a threaded extent**. Single-threaded code retains arbitrary mutable aliasing. A shared allocation in an extent is either all-readers (every capture `*const T`, no check, free shared reads) or exactly one writer and no other accessor. A reader and a writer to the same allocation with no synchronization is a read/write race a hard error. The asymmetry with single-threaded aliasing is deliberate: a data race is UB that no runtime check can rule out, so the rule tightens precisely at the boundary where optimism becomes unsound. 3. **No Tier-R fallback for shared mutable state.** Bounds, epoch/UAF, and nullability must be proven statically for any shared mutable allocation, or the access hard-errors. On a read-only shared allocation, normal Tier-R checks apply (rule 2 makes the metadata stable). 4. **Captured-stack escape from a task Tier N always.** A pointer to the spawning frame's stack, captured into a task that can outlive the frame (detached, or awaited after the frame returns), is a hard error. Heap-lifting the value does not fix a cross-thread lifetime, so this is never promotable. The fix is capture-by-transfer (the task owns its copy) or an explicit heap allocation moved into the task. 5. **What may cross the boundary.** A value captured by transfer has its ownership *fully* transferred the parent cannot touch it after the spawn. A value captured by address makes its allocation shared, subject to rules 1-3. A pointer into a **scoped allocator** cannot be captured into a task that outlives the allocator scope (Tier N the arena frees it, and the free also races the task). A `NON_TRANSFER` type cannot be transfer-captured into a task at all (nothing to transfer); it may only be shared read-only by address. 6. **`await` re-establishes facts.** `await` is a happens-before edge. After it, the joined task's writes are visible and ordered, its captured pointers are released, and the allocations it touched return to single-threaded analysis. An allocation that was demoted to "shared, read-only" inside the extent may be a normal mutable `*T` again after the `await` that joins its only other accessor. Strict mode is scoped to the extent, not sticky for the rest of the program. 7. **Detached tasks (never awaited).** With no join there is no happens-before edge, so the task's captured allocations are shared for the rest of the program (or until the task provably ends, which AMT usually cannot prove). Any allocation a detached task captures by address and that anyone writes after the spawn is a hard error. The clean pattern detached tasks capture by transfer only, owning everything they touch is recommended explicitly. ### Sharing syntax For **closures and lambdas**, read-only sharing across a thread boundary is expressed by capturing with `const &` and nothing else. A `const &` capture is a `*const T` into the captured allocation; it cannot mutate the target, which is exactly what rule 2 requires for free shared reads. A plain `&` (mutating) capture into a task makes the allocation a shared *mutable* allocation and triggers the single-writer analysis. For **functions marked `async`**, the read-only guarantee is carried in the parameter type. The pointer parameter is pointer-to-const, so the threaded body cannot mutate the caller's value through it: ```kairo fn foo(a: *const i32) async -> i32 { // reads through `a`; *a = ... would not compile return *a + 1 } var fut = thread foo(&x) // begins executing immediately var res = await fut // join point, happens-before edge; x is read-only over the extent ``` Here `x`'s allocation is shared read-only for the extent of `fut` (because `foo`'s parameter is `*const i32`), so the parent reading `x` during the extent is sound. Had `foo` taken `a: *i32` and written through it, `x` would be a shared *mutable* allocation and the parent touching `x` before the `await` would be a hard error under rule 2. The borrow is live from the `thread`/`spawn` call to the matching `await`. Parent access to a shared-*written* allocation inside that window is a hard error this is the core threading rule, and the single-writer and reader/writer rules are its consequences. --- ## Allocators ### Global allocator The default for `@create T()` and all standard-library heap operations. Overridable with `@mem::set_allocator(MyAllocator)` at the top of a file. **Intentional friction**: the override must appear at the top of *every file in the dependency graph* it affects. A library that sets a global allocator forces the annotation onto every transitive dependent. This keeps global-allocator changes visible and pushes library authors toward scoped allocators. ### Scoped allocator An allocator with RAII semantics it frees everything it allocated when it leaves scope. Set with `@mem::set_scoped_allocator(MyAllocator)` on a function or block. ```kairo @mem::set_scoped_allocator(ArenaAllocator) fn process_frame() { var a = @create Mesh(vertices) var b = @create Texture(pixels) // freed wholesale when process_frame returns } ``` A pointer allocated through a scoped allocator that escapes the allocator's scope is **Tier N** a hard error in both modes. The arena frees the memory at scope end; no smart pointer and no runtime check rescues a pointer to freed arena memory (and across threads, the free races the access). The escape increments the allocation's epoch at the scope close, so the escaping pointer fails C1 by the same machinery as any other dangling pointer. The only fix is restructuring: allocate through the global allocator, or move the scoped boundary outward. ### Freestanding allocator A scoped-allocator variant for embedded and bare-metal targets, conforming through `static` functions only no indirection, no vtable. The compiler replaces `@create T()` with a direct static call plus placement construction. AMT treats freestanding allocators identically to scoped allocators for lifetime tracking; the difference is a codegen concern. --- ## Destruction Timing Where AMT places a value's destruction depends on whether the destructor is observable. **Trivial destructors** (no observable side effect just memory to reclaim) run at **last use**. AMT places the `@destroy` as early as the final use permits, since reclaiming memory after the last use is indistinguishable from reclaiming it at the scope brace: ```kairo var foo = @create SomeClass() // trivial destructor use(foo) // @destroy foo inserted here std::println(10) ``` **Non-trivial destructors** (a user-defined `op delete`, or any member with one file close, lock release, flush, disconnect) run at the **end of the owning binding's lexical scope**, in reverse declaration order. AMT does not move these to last use: ```kairo fn example() { var cfg = @create Config(8080) // side-effecting destructor use(cfg) std::println(10) } // cfg's destructor runs here, at the brace not after use(cfg) ``` This is the "predictable from source" guarantee: any observable destructor side effect fires at the scope brace exactly as written. `finally` blocks, lock guards, and `NON_TRANSFER` scope guards depend on it. **Shared objects** separate two events. The **refcount decrement** happens at each `Shared` binding's own scope boundary, in reverse declaration order. The **object destructor and deallocation** happen once, when the last live `Shared` reference's scope ends and the count reaches zero which is not necessarily the last-declared binding in any single scope. Reason about shared destruction as "when the final owner goes away," not "reverse declaration order of the binding in front of me." `Weak` scope exit does not touch the count; a `Weak` simply becomes invalid once the target is freed. ## Delete Operation `delete` in kairo does not free memory. It is a **destruction operation** that runs the destructor and any side effects, but does not reclaim the allocation. Meaning if the class can be re-constructed with a separate function call, the memory is still valid and can be reused. The `delete` operation is used to explicitly clean up resources without deallocating the memory, which is useful in certain scenarios where you want to manage memory manually or reuse it. ```kairo class MyClass { var data: *i32? = null fn MyClass(self) = default fn init(self) { data = std::alloc(10) // 10 i32's allocated } fn op delete(self) { if (data != null) { @destroy data data = null } } } var obj = MyClass() // stack allocated the object itself obj.init() // allocate memory for data // do something with obj.data delete obj // runs the destructor for obj and frees data, but does not free obj itself obj.init() // can re-initialize obj and reuse the memory ``` `delete` is supposed to be designed in a idempotent way, meaning that it can be called multiple times on the same object without causing undefined behavior. The first call to `delete` will run the destructor and free the resources, while subsequent calls will have no effect since the object is already destroyed. This allows for safe cleanup of resources without worrying about double deletion or dangling pointers. --- ## Copy Elision For COPY types, AMT may emit a move instead of a copy when it proves the source is unused after the transfer **and** the elision is unobservable. Elision means the source's destructor runs at the transfer point instead of at its own scope exit; AMT performs it only when the destructor's observable effects do not depend on that timing trivial destructors, or effects (a `free`, a refcount decrement) that produce identical observable behavior either way. If the destructor has timing-sensitive side effects a flush, a lock release, a log line AMT does not elide. The copy is preserved and the source is destroyed at its lexical scope exit as written. Elision is silent precisely because it is only applied where it cannot be observed. The programmer neither opts in nor controls it, and it is never applied if the move constructor is deleted. --- ## C/C++ Interop The FFI boundary is where AMT's facts run out, and the cost model has an honest asterisk: pointers crossing the boundary carry no provenance AMT can trust, so accessing them in safe code is not free. ### Inbound pointers are tainted A pointer that enters Kairo from C++ has **no provenance** AMT does not know its allocation, size, or epoch. Such a pointer is *tainted* (clause C6). Dereferencing a tainted pointer in safe code cannot be `PROVEN_STATIC`. If AMT can recover enough provenance to form a check, the dereference is Tier R; if the provenance is fully opaque, it is Tier N a hard error demanding an `unsafe` block or an explicit `assume`/launder that transfers responsibility to the programmer. A pointer that acquires its provenance *inside* an `unsafe` block carries the same taint when it flows back into safe code: unsafe is allowed to *narrow* where checks vanish, but it does not let an untracked pointer leak into a safe dereference as if it were proven. ### Outbound pointers freeze their epoch A Kairo pointer handed to C++ has its allocation's epoch frozen pessimistically: AMT must assume C++ may free, relocate, or retain it. On return from the FFI call, all optimistic facts about that allocation are erased unless an annotation says otherwise. This is unavoidable AMT genuinely lacks the proof and it is where the "as fast as unchecked C++" property does not hold: at the interop boundary you pay, because the proof is on the other side of a wall AMT cannot see through. ### Smart-pointer and reference parameters C++ functions using smart-pointer or reference types map automatically and require no `unsafe`: | C++ type | Kairo AMT type | Requires `unsafe` | |----------------------|---------------------------|-------------------| | `std::unique_ptr` | `std::Unique` | No | | `std::shared_ptr` | `std::Shared` | No | | `std::weak_ptr` | `std::Weak<*T>` | No | | `T&`, `const T&` | `*T`, `*const T` | No | | `T&&` (move ref) | `*T` (ownership transfer) | No | A mismatch between AMT's promotion and the C++ signature is a compile error. ### Raw-pointer parameters and shallow analysis C++ functions taking raw pointers (`T*`, `void*`) require an `unsafe` block; AMT does not track across the boundary. For raw FFI calls where the C++ source is visible via the imported header, AMT performs a **one-level** heuristic: if the function only dereferences the pointer without aliasing, storing, or forwarding it, the call may be classified safe without `unsafe`. The analysis does not recurse. If the body is not visible (forward-declared, compiled library), all raw-pointer parameters are opaque and `unsafe` is required. --- ## Generic Code AMT analyzes each monomorphization with full concrete type information generics are not an opaque boundary: ```kairo fn take_ownership(x: *T) { // x is the sole pointer to the allocation after this call } fn caller() { var cfg = @create Config(8080) take_ownership(cfg) // ownership transferred; using cfg after this is a compile error } ``` If `T` has a `@move` transfer constructor, AMT tracks the transfer through the move; if `T` is `@copy`, AMT knows both source and destination are live after the copy. Each instantiation is analyzed as if it were a hand-written concrete function. --- ## .amt Summaries Whole-program analysis cannot re-walk every callee at every call site. Each translation unit produces a `.amt` summary alongside its object file, and the link pass consumes all summaries to resolve cross-TU pointer flows. The summary is what lets a caller discharge a callee's obligations without re-analyzing its body. ### Summary contents A function's summary records four sections per pointer parameter and for the function's effect on reachable state: - **Requires** preconditions the caller must discharge before the call (e.g. *`ptr` in allocation, access ≤ 16 bytes*). - **Guarantees** postconditions the caller may assume after the call (e.g. *`ptr` unchanged*). - **Preserves** arguments and state the function provably does not mutate. Hoisting reads this: the `loop_bound == allocation_count` proof requires that an intervening call **Preserves** the size variable. - **Escapes** what the function retains beyond its own frame (stores, returns, captures). Lifetime analysis reads this. `Escapes` defaults to *everything* and `Preserves` to *nothing* when analysis is incomplete the summary fails **closed**. An incomplete summary costs precision (more residual, more checks or errors), never soundness. The caller-discharges-obligation model: when a pointer flows into a function, AMT consumes the summary rather than the body. If the caller has already proven the `Requires`, the checks are skipped; if it cannot, AMT emits one assertion before the call to establish the precondition. AMT does not need to look inside the callee. ### Summaries are proof certificates, not caches A summary is only sound if it is **correct and current**. A stale summary one whose source changed but whose summary was not regenerated silently disables checks at every call site, which is the single largest soundness surface in the system. Summaries are therefore **content-hashed** against the function body's IR. A call site consuming a summary records the hash; a mismatch at link time is a **hard error**, not a remark and not a silently-trusted cache. There is no "trust me" mode in release. Treat the summary as a certificate that must validate, not a file that is probably fine. (`.amt` files are regenerated by the build system and need not be committed to version control.) > [!IMPORTANT] > The `.amt` file format and the link-time resolution pass are still being finalized. The four-section > schema and the content-hash invariant above are the fixed parts; wire-format specifics may change > before 1.0. --- ## Soundness and Testing AMT makes a strong claim `PROVEN_STATIC` means an access is safe with no runtime evidence and a proof engine cannot ship on inspection alone. Two properties pin it down. ### The soundness property > If a dereference classifies as `PROVEN_STATIC`, then no clause of its obligation can fail at > runtime for any execution. This is what licenses emitting a bare `mov`. The full proof belongs in the AMT paper, not here; the property to hold against is that the static classification is an under-approximation of safety the analysis only proves a clause when it is genuinely discharged, and every uncertainty is resolved toward residual (the monotone "take the weaker state at a merge" rule). Failing closed at every boundary interop, threads, summary gaps, non-convergent recursion is what keeps the under-approximation honest: optimism is what would make the property false, and AMT is never optimistic at a boundary. ### Paranoid mode the empirical falsifier The contrapositive of the soundness property is directly testable. **Paranoid mode** emits the runtime check for *every* dereference regardless of its classification including the ones marked `PROVEN_STATIC`. Then: > If a check fires in paranoid mode at a site AMT classified `PROVEN_STATIC`, the static proof was > wrong. That is a soundness bug. Paranoid mode is the fuzzing oracle. It is cheap to build (the check-emission machinery already exists for Tier R; paranoid mode just disables the suppression) and it turns "is the lattice sound" from a question answered by inspection into one answered by running the test suite under instrumentation. **It is the first thing to build,** before trusting any `PROVEN_STATIC` in anger. ### Optimization-level independence AMT's proof results are **independent of the LLVM optimization level**. The lattice is computed before, and separately from, the backend optimization passes. A higher opt level may *lower* a check more cheaply, but it may never change a dereference's proof *state*. The set of dereferences that carry a runtime check is **identical at every opt level**. This is a hard invariant, and it is the kind that belongs in CI: a `FileCheck` assertion that a given dereference emits (or does not emit) a check, pinned and run at every opt level. Without it, safety-relevant codegen the presence or absence of a check could drift with optimization settings, and a check that exists at `-O0` but is "optimized away" at `-O2` would be indistinguishable from a miscompile. The invariant makes that class of bug impossible by construction and testable by assertion. --- ## What AMT Does Not Do - **Garbage collection.** No tracing, no mark-and-sweep, no runtime collector. Reference counting for `Shared` is the only runtime ownership cost, and it exists only where multiple ownership is required. - **Runtime borrow checking.** Aliasing is resolved at compile time. AMT does not insert runtime aliasing checks; it inserts bounds, epoch, and (for tainted/nullable residuals) access checks those are clause discharges, not borrow checks. - **Lifetime annotations.** No `'a`-style parameters, no lifetime polymorphism. Whole-program analysis infers concrete lifetimes from usage. - **Reference counting everywhere.** Single-owner pointers promote to `Unique` (no refcount); contained lifetimes are not promoted at all. Refcounting appears only under proven multiple ownership. - **Cross-language analysis.** C++ is opaque beyond the one-level heuristic on a visible body. Across the FFI boundary, AMT fails closed (taint inbound, epoch-freeze outbound) rather than guessing. --- ## Summary The whole system, in one table maps each clause's residual to its response: | Residual | Response | Tier | |--------------------------------------------------|----------------------------------------------|------| | bounds (range) | hoisted/local check, both modes | R | | UAF (provenance intact) | epoch compare, both modes | R | | UAF (provenance lost) | hard error | N | | nullability | debug check / release error | P | | ownership escape (heap-promotable) | debug promote / release error | P | | stack escape (heap-promotable) | debug heap-lift / release error | P | | stack escape (non-promotable) | hard error | N | | scoped-allocator escape | hard error | N | | opaque-container escape | hard error | N | | iterator invalidation | hard error | N | | moved-from use | hard error | N | | second writer to shared allocation | hard error | N | | access residual on shared *mutable* allocation | hard error (strict mode) | N | | access residual on shared *read-only* allocation | normal Tier-R check | R | | taint (provenance recoverable) | runtime check | R | | taint (provenance opaque) | hard error (demand `unsafe`) | N | | Type | Tracked? | Promoted? | Escape | |------------------|----------|---------------------------|-------------------------------------------------------| | Plain *T (stack) | Yes | Heap-lift in debug only | Promotable: debug-lift / release-err; else hard error | | Plain *T (heap) | Yes | Debug: yes / Release: no | Release: hard error (annotate) | | unsafe *T | No | Never | Programmer's problem | | Scoped-alloc ptr | Yes | Never | Always hard error | | Shared (thread) | Yes | n/a | One writer, or many `const` readers | | FFI smart ptr | Yes | Mapped 1:1 | Type mismatch = error | | FFI raw ptr | No | Never | Tainted; requires unsafe/launder | AMT proves every dereference safe statically when it can, and emits nothing. When only a checkable property remains, it emits the minimal check Tier R, the only runtime cost in a release build. When safety needs a program change it transforms in debug and errors in release (Tier P), and when nothing can establish safety it is a hard error in every mode (Tier N). --- ## Unsafe URL: https://www.kairolang.org/docs/language/unsafe/ # Unsafe The `unsafe` keyword appears in three distinct contexts in Kairo, each serving a different purpose: | Context | Meaning | |---|---| | `unsafe { ... }` | Block that suspends AMT tracking | | `unsafe *T` | Raw pointer type with no compiler tracking | | `fn foo() unsafe -> T` | Separate function overload namespace | These are independent mechanisms that share a keyword. An `unsafe` block does not make all pointers raw, and a raw pointer does not require an `unsafe` block to use. --- ## Unsafe Blocks An `unsafe { ... }` block suspends [AMT](/docs/language/amt) for all operations within its scope. Inside an unsafe block: - AMT does not track pointer lifetimes or provenance - AMT does not insert automatic destructors or smart pointer promotions - `forget!()` is available to permanently drop pointers from AMT tracking - `unsafe &x` can create raw pointers from safe bindings ```kairo var x = @create i32(42) // AMT-tracked safe pointer unsafe { forget!(x) // drop x from AMT tracking c_function(x as unsafe *i32) // pass to C++ which will free it } // x is no longer tracked AMT will not auto-free it ``` ### When unsafe blocks are required An `unsafe` block is required when calling a C/C++ function that takes or returns raw pointers: ```kairo ffi "c++" import "native.hh"; fn main() { var data = @create i32(100) unsafe { forget!(data) native_take_ownership(data as unsafe *i32) } } ``` ### When unsafe blocks are NOT required Calling C/C++ functions that use safe parameter types does not require an `unsafe` block: | C++ parameter type | Requires `unsafe` block | |---|---| | Value types (`int`, `float`, structs by value) | No | | References (`T&`, `const T&`) | No | | Move references (`T&&`) | No | | `std::unique_ptr` | No | | `std::shared_ptr` | No | | `std::weak_ptr` | No | | Raw pointers (`T*`, `void*`) | Yes | The FFI layer reads C++ declaration signatures and maps C++ smart pointers to Kairo's AMT-tracked equivalents automatically. `std::unique_ptr` maps to `std::Unique`, `std::shared_ptr` maps to `std::Shared`, `std::weak_ptr` maps to `std::Weak<*T>`. A mismatch between AMT's promotion and the C++ function's expected smart pointer type is a compile error. See [C/C++ Interop](/docs/language/c-c++) for the full FFI model. --- ## Forget `forget!()` is a compiler intrinsic that permanently removes a pointer from AMT tracking. It is only valid inside an `unsafe` block: ```kairo var ptr = @create Config(8080) unsafe { forget!(ptr) // AMT stops tracking ptr // ptr is now the caller's responsibility } // AMT will not auto-free ptr if nothing else frees it, this is a memory leak ``` The primary use case is transferring ownership to C++ code that will manage the pointer's lifetime: ```kairo ffi "c++" import "engine.hh"; fn init_engine() { var cfg = @create EngineConfig(defaults()) unsafe { forget!(cfg) engine_init(cfg as unsafe *EngineConfig) // C++ engine now owns the allocation and will free it on shutdown } } ``` > [!CAUTION] > `forget!()` does not free memory it tells AMT to stop tracking the pointer. If the pointer is > not freed by other means (C++ code, manual `std::free()`, etc.), the memory leaks. Use `forget!()` > only when transferring ownership across the FFI boundary. --- ## Raw Pointers (`unsafe *T`) `unsafe *T` declares a pointer with no AMT tracking, no null checks, and no bounds checks. It is the Kairo equivalent of a raw C/C++ pointer: ```kairo var raw: unsafe *i32 = unsafe &some_value *raw = 42 // no null check UB if null ``` Raw pointers do not require an `unsafe` block to dereference or use. The `unsafe` is part of the type itself by declaring the pointer as `unsafe *T`, the programmer has already opted out of safety for that pointer. ```kairo var buf: unsafe *u8 = unsafe std::alloc(1024) buf[0] = 0xFF // no bounds check buf[100] = 0x00 // no bounds check UB if out of allocation unsafe std::free(buf) ``` See [Pointers](/docs/language/pointers) for the full pointer model. --- ## Unsafe Function Overloads The `unsafe` modifier on a function creates a separate overload in its own namespace. The caller explicitly selects the unsafe variant with the `unsafe` keyword: ```kairo fn sort(data: [i32]) -> [i32] { // safe: bounds-checked, stable sort return stable_sort(data) } fn sort(data: [i32]) unsafe -> [i32] { // unsafe: unstable sort, may reorder equal elements return quick_sort(data) } var a = sort(my_data) // calls the safe version var b = unsafe sort(my_data) // calls the unsafe version ``` ### What `unsafe` means on a function `unsafe` on a function does **not** mean "unsafe memory." AMT still guarantees memory safety in both safe and unsafe overloads. The `unsafe` qualifier signals that the function may not uphold semantic invariants that the safe version does stability, ordering, precision, idempotency, or any other contract beyond memory safety. The caller writing `unsafe sort(...)` is explicitly acknowledging: "I know this version has weaker guarantees and I accept the trade-off." ### Overload resolution Safe and unsafe overloads live in separate namespaces. They can have identical parameter types because the dispatch is determined by the presence or absence of the `unsafe` keyword at the call site: ```kairo fn process(x: i32) -> i32 { ... } // safe fn process(x: i32) unsafe -> i32 { ... } // unsafe different namespace process(42) // calls safe version unsafe process(42) // calls unsafe version ``` ### Modifier restrictions `unsafe` cannot be combined with other function modifiers: | Combination | Valid | |---|---| | `unsafe` + `const` | Yes | | `unsafe` + `eval` | No | | `unsafe` + `async` | Yes | | `unsafe` + `panic` | Yes | | `unsafe` + `inline` | Yes | | `unsafe` + `volatile` | Yes | `unsafe` stands alone as an overload qualifier. See [Functions](/docs/language/functions#modifier-compatibility) for the full modifier compatibility table. --- ## The Safety Boundary Kairo's safety model has a clear boundary: **Inside normal code:** AMT tracks all pointer lifetimes, inserts null checks on safe pointer dereference, auto-promotes to smart pointers, and emits compile errors when safety cannot be guaranteed. Memory safety is the compiler's responsibility. **Inside `unsafe { }` blocks:** AMT is suspended. The programmer is responsible for pointer lifetimes, null safety, and deallocation. The compiler trusts the programmer. **With `unsafe *T` pointers:** No tracking regardless of whether the code is inside an `unsafe` block. The pointer is permanently untracked by its type. **With `unsafe` function overloads:** Memory safety is still guaranteed by AMT. Only semantic invariants beyond memory safety are relaxed. ``` Memory safe? AMT tracked? Who manages lifetime? Normal code Yes Yes Compiler (AMT) unsafe { } Programmer No Programmer unsafe *T Programmer No Programmer fn foo() unsafe Yes Yes Compiler (AMT) ``` --- ## Common Patterns ### FFI ownership transfer ```kairo ffi "c++" import "lib.hh"; fn send_to_native(data: Config) { var ptr = @create Config(data) unsafe { forget!(ptr) native_take_ownership(ptr as unsafe *i32) } } ``` ### Custom allocator ```kairo fn allocate_aligned(size: usize, align: usize) -> unsafe *void { var raw: unsafe *void = unsafe std::aligned_alloc(align, size) return raw } ``` ### Interfacing with hardware registers ```kairo fn write_register(addr: usize, value: u32) { var reg = addr as unsafe *u32 *reg = value } ``` ### Unsafe overload for performance ```kairo fn bounds_check(arr: [i32], index: i32) -> i32 { assert index >= 0 && index < arr.length(), "out of bounds" return arr[index] } fn bounds_check(arr: [i32], index: i32) unsafe -> i32 { // caller guarantees index is valid skip the check return arr[index] } ``` --- ## Summary ```kairo // Unsafe block suspends AMT var ptr = @create i32(42) unsafe { forget!(ptr) c_function(ptr as unsafe *i32) } // Raw pointer no tracking by type var raw: unsafe *i32 = unsafe &some_value *raw = 100 // no null check // Unsafe overload separate dispatch namespace fn compute(x: f64) -> f64 { /* precise */ } fn compute(x: f64) unsafe -> f64 { /* fast approximation */ } var precise = compute(3.14) var fast = unsafe compute(3.14) // forget!() drop pointer from AMT var data = @create Buffer(1024) unsafe { forget!(data) // data is no longer tracked manual management required } ``` --- ## Panic URL: https://www.kairolang.org/docs/language/panic/ # Panic `panic` is Kairo's error signaling mechanism. A function marked with the `panic` specifier can produce an error instead of its declared return type. Callers must handle the error via `try`/`catch` or propagate it by marking themselves `panic`. The compiler statically verifies that all error types are accounted for unhandled error types are a compile error. Unlike C++ exceptions, panics use no unwinding tables and no runtime. The codegen is zero-cost every panic site becomes a tagged return value checked with a branch. --- ## The `panic` Specifier Add `panic` after the parameter list to indicate a function may produce an error: ```kairo fn parse_port(input: string) panic -> i32 { if input.length() == 0 { panic std::Error::Runtime("empty input") } var port = std::parse(input) if port < 0 || port > 65535 { panic std::Error::Runtime("port out of range") } return port } ``` The `panic` keyword inside the function body raises an error. The error value can be any type there is no base error class requirement. --- ## Handling Panics with `try`/`catch` Callers handle panics with `try`/`catch`. The compiler tracks every error type that can propagate from the `try` body and verifies that all types are handled: ```kairo fn load_config(path: string) -> Config { var port: i32 try { port = parse_port(read_file(path)) } catch e: std::Error::Runtime { std::println(f"bad config: {e}") port = 8080 } catch e: std::Error::IO { std::println(f"cannot read file: {e}") port = 8080 } return Config(port) } ``` ### Exhaustiveness The compiler enforces that every error type reachable from the `try` body is handled. If any type is missing, it is a compile error unless the function is itself marked `panic`: ```kairo fn partial_handler() panic -> i32 { // This function only handles Runtime errors. // IO errors propagate to the caller. try { return parse_and_validate() } catch e: std::Error::Runtime { return -1 } // std::Error::IO is not caught propagates because this function is marked panic } ``` A bare `catch` (no type) acts as a catch-all and satisfies exhaustiveness for all remaining types: ```kairo fn safe_handler() -> i32 { try { return parse_and_validate() } catch { // handles any error type return -1 } } ``` ### Named and unnamed catch The error value can be bound to a variable for inspection, or the catch block can omit the binding: ```kairo try { risky_operation() } catch e: std::Error::IO { // e is bound can inspect the error log_error(e) } catch { // no binding catch-all, error value is discarded } ``` --- ## Propagation If a function calls a `panic` function without fully handling all error types, it must be marked `panic` itself. The unhandled errors propagate to the caller: ```kairo fn read_config() panic -> Config { var content = read_file("config.txt") // may panic with IO error var port = parse_port(content) // may panic with Runtime error return Config(port) // Both IO and Runtime errors propagate caller must handle them } ``` Calling a `panic` function without `try`/`catch` and without the `panic` specifier is a compile error: ```kairo fn bad() -> i32 { return parse_port("8080") // compile error: parse_port may panic, but bad() is not marked panic } ``` --- ## Multiple Error Types A function does not declare which error types it can produce the compiler infers this from the function body. A single function can panic with any number of different error types: ```kairo fn process(path: string) panic -> Data { if !file_exists(path) { panic std::Error::IO("file not found") } var content = read_file(path) if content.length() == 0 { panic std::Error::Runtime("empty file") } if !validate(content) { panic std::Error::Validation("invalid format") } return parse_data(content) } ``` Callers of `process` must handle `std::Error::IO`, `std::Error::Runtime`, and `std::Error::Validation` (plus any errors from `read_file` and `parse_data`), or propagate them. --- ## Try/Catch as an Expression `try`/`catch` can produce a value. Each branch must return the same type: ```kairo var port = try parse_port(input) catch e: std::Error::Runtime { 8080 } catch { 3000 } ``` `finally` is not permitted in expression form. See [Control Flow](/docs/language/control-flow#trycatch-as-an-expression) for expression-form rules. --- ## Finally `finally` defines cleanup code that runs regardless of whether the `try` body succeeds or panics: ```kairo try { acquire_lock() do_work() } catch e: std::Error::Runtime { handle_error(e) } finally { // identical to standalone `finally` release_lock() // always runs } ``` ### Standalone `finally` (scope exit) `finally` can appear without a preceding `try`. In this form it runs when the enclosing function exits, regardless of how normal return, panic, or early return: ```kairo fn process_file(path: string) panic { var fd = open(path) finally { close(fd) } var data = read(fd) if !validate(data) { return // finally still runs } transform(data) // finally runs here too on normal exit } ``` Multiple `finally` blocks in the same function execute in reverse declaration order (LIFO). See [Control Flow](/docs/language/control-flow#finally) for full `finally` semantics. --- ## Panic and No-Return (`!`) A function with return type `!` can never return normally it always panics, loops forever, or calls another no-return function. The `panic` specifier and `!` cannot coexist because `panic` implies an alternative return path (the error), while `!` guarantees no return at all: ```kairo fn fatal(msg: string) panic -> ! { // compile error: panic and ! are contradictory panic std::Error::Runtime("fatal error") // invalid, panic implies an error return, but ! means no return at all } fn fatal(msg: string) -> ! { loop { crash!(msg) // valid } // ok: never returns } ``` See [Functions](/docs/language/functions#no-return) and [Type System](/docs/language/type-system#the-never-type-) for `!` semantics. --- ## Codegen Panics compile to zero-cost tagged return values. There are no unwinding tables, no runtime exception handler, and no stack unwinding. A function marked `panic` returns a tagged union containing either the success value or an error with source location metadata. At the call site, `try`/`catch` compiles to a branch on the tag. If the tag indicates an error, the catch block executes. If it indicates success, the value is extracted and execution continues. This means: - No runtime overhead on the success path beyond a single branch (which the branch predictor handles efficiently) - No stack unwinding errors propagate via normal return values - No unwinding tables in the binary smaller executables - All functions in Kairo are trivially `noexcept` at the ABI level > [!NOTE] > The `panic` statement inside a function body (`panic SomeError(...)`) does not halt the program. > It produces the error as the function's return value. The term "panic" refers to the signaling > mechanism, not to a crash. The program only terminates if an error reaches `main()` or the top > level and is re-panicked with `panic e` in that context. --- ## Error Types Kairo does not prescribe a specific error hierarchy. Any type can be used as a panic value. The standard library provides common error types under `std::Error`: ```kairo panic std::Error::Runtime("message") panic std::Error::IO("message") panic std::Error::Validation("message") ``` > [!IMPORTANT] > The `std::Error` hierarchy is still being finalized. Detailed documentation for standard error > types will be added in a future update. User-defined error types work the same way: ```kairo class ParseError { pub var message: string pub var position: i32 fn ParseError(self, msg: string, pos: i32) { self.message = msg self.position = pos } } fn parse(input: string) panic -> Ast { if input.length() == 0 { panic ParseError("unexpected end of input", 0) } // ... } try { parse("") } catch e: ParseError { std::println(f"parse error at {e.position}: {e.message}") } ``` --- ## Summary ```kairo // Function that may panic fn divide(a: i32, b: i32) panic -> i32 { if b == 0 { panic std::Error::Runtime("division by zero") } return a / b } // Full handling no panic propagation fn safe_divide(a: i32, b: i32) -> i32 { try { return divide(a, b) } catch e: std::Error::Runtime { std::println(f"error: {e}") return 0 } } // Partial handling propagates unhandled types fn partial(a: i32, b: i32) panic -> i32 { return try complex_operation(a, b) catch e: std::Error::Runtime { -1 } // other error types propagate } // Expression form var result = try { divide(10, 0) } catch { 0 } // Scope exit fn with_cleanup() panic { var resource = acquire() finally { release(resource) } do_work(resource) } ``` --- ## Compile-Time Eval URL: https://www.kairolang.org/docs/language/eval/ # Compile-Time Eval The `eval` keyword forces compile-time evaluation. An `eval` variable, function, or control flow construct must be fully resolvable at compile time if it depends on runtime values, the compiler emits an error. There is no "maybe compile-time, maybe runtime" mode. `eval` means compile-time, always. --- ## Eval Variables `eval` declares a binding whose value is computed at compile time. The result is baked into the binary as a constant: ```kairo eval PI = 3.14159265358979 eval MAX_BUFFER = 1024 * 1024 eval HEADER_SIZE = sizeof u32 + sizeof u16 + sizeof u8 ``` `eval` bindings are implicitly `const` they cannot be reassigned. The initializer must be a compile-time evaluable expression. ### Type annotation Type annotations are optional. The compiler infers the type from the initializer: ```kairo eval N = 10 // i32 eval NAME = "Kairo" // string eval N: u64 = 10 // explicitly u64 ``` ### Usage as generic arguments `eval` variables can be used wherever a compile-time constant is required, including array sizes and generic arguments: ```kairo eval BUFFER_SIZE = 256 var buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE] eval TABLE_WIDTH = 16 eval TABLE_HEIGHT = 16 var grid: [[f64; TABLE_WIDTH]; TABLE_HEIGHT] ``` --- ## Eval Functions An `eval` function must be fully evaluable at compile time. The compiler executes it during compilation and replaces the call site with the result: ```kairo eval fn factorial(n: i32) -> i32 { if n <= 1 { return 1 } return n * factorial(n - 1) } eval FACT_10 = factorial(10) // computed at compile time: 3628800 ``` ### Calling other eval functions Eval functions can call other eval functions: ```kairo eval fn square(x: i32) -> i32 = x * x eval fn sum_of_squares(n: i32) -> i32 { var total = 0 for i in 1..=n { total += square(i) } return total } eval RESULT = sum_of_squares(10) // 385 ``` ### Recursion Eval functions can be recursive. The compiler evaluates the recursion at compile time: ```kairo eval fn fib(n: i32) -> i32 { if n <= 1 { return n } return fib(n - 1) + fib(n - 2) } eval FIB_20 = fib(20) // 6765 ``` > [!WARNING] > Deeply recursive eval functions can significantly increase compile times. The compiler may impose > a recursion depth limit to prevent unbounded compilation. --- ## Restrictions Eval bodies must be deterministic and free of side effects. The following are not permitted inside `eval` functions or `eval` variable initializers: | Not allowed | Reason | |---|---| | Heap allocation (`std::create`, `[T]` growth) | No runtime allocator at compile time | | IO operations (file, network, console) | Side effects | | Pointer operations (`*T`, `unsafe *T`) | No addressable memory at compile time | | `async` / `await` / `spawn` | No runtime scheduler at compile time | | `panic` | No runtime error handler at compile time | | Mutable global / static variables | Side effects across evaluations | | Calls to non-`eval` functions | Cannot guarantee compile-time evaluation | ### What is allowed | Allowed | Examples | |---|---| | Arithmetic and logic | `+`, `-`, `*`, `/`, `%`, `&&`, `\|\|`, `!` | | Comparisons | `==`, `!=`, `<`, `>`, `<=`, `>=` | | Control flow | `if`/`else`, `for`, `while`, `loop`, `match` | | Local variables | `var`, `const` within the eval body | | Calling other `eval` functions | `eval fn` calls | | `sizeof`, `alignof` | Type size queries | | `typeof` (type position) | Compile-time type resolution | | Struct/enum construction (trivial) | Literal aggregate initialization | | String literals and operations | Compile-time string manipulation | | Fixed-size arrays (`[T; N]`) | Stack-like allocation in the evaluator | --- ## Eval If `eval if` selects a branch at compile time. The condition must be a compile-time constant. Only the selected branch is compiled the others are discarded entirely (no codegen, no type checking): ```kairo eval if platform == "linux" { fn init_platform() { /* linux-specific */ } } else if platform == "windows" { fn init_platform() { /* windows-specific */ } } else { fn init_platform() { /* fallback */ } } ``` `eval if` can appear at the top level (selecting between declarations) or inside function bodies (selecting between code paths): ```kairo fn process(x: T) -> T { eval if sizeof T <= 8 { return fast_path(x) } else { return slow_path(x) } } ``` ### Type-based branching `eval if` combined with `typeof` enables type-specialized code paths in generic functions: ```kairo fn serialize(value: T) -> [byte] { eval if typeof T == i32 { return int_to_bytes(value) } else if typeof T == string { return value.to_bytes() } else { return generic_serialize(value) } } ``` The unselected branches are not type-checked, so they can contain code that would be invalid for the current `T`. This is the mechanism for writing type-specialized generic code without separate overloads. See [Control Flow](/docs/language/control-flow#compile-time-branching) for `eval if` in the context of control flow. --- ## Eval For `eval for` unrolls a loop at compile time when all loop bounds and operations are compile-time evaluable: ```kairo eval fn build_lookup_table() -> [i32; 16] { var table: [i32; 16] eval for i in 0..16 { table[i] = i * i } return table } eval SQUARES = build_lookup_table() ``` If the loop body or bounds depend on runtime values, the compiler emits an error. An `eval for` in a `eval` function is not allowed. > [!NOTE] > `eval for` fully computes the loop at compile time and embeds the result. Or if the loop is too large, the compiler will try to unroll the loop without evaluation, and emit a optimization remark. If and only if neither is possible, the compiler will emit an error. --- ## Eval and Types `eval` works with any type that can be constructed and manipulated at compile time: ### Primitives All integer, float, bool, char, and string types are eval-compatible: ```kairo eval X = 42 eval PI = 3.14159 eval FLAG = true eval INITIAL = 'K' eval NAME = "Kairo" // one thing on strings specifically, // if a string is used in an eval context, it must be // a compile-time constant or it will cause a compile error ``` ### Fixed-size arrays ```kairo eval PRIMES = [2, 3, 5, 7, 11, 13, 17, 19] eval IDENTITY: [f64; 4] = [1.0, 0.0, 0.0, 1.0] ``` ### Structs (trivially constructible) ```kairo struct Point { var x: f64 var y: f64 } eval ORIGIN = Point { x: 0.0, y: 0.0 } eval UNIT_X = Point { x: 1.0, y: 0.0 } ``` ### Enums (plain and ADT) ```kairo enum Mode { Debug, Release, Test } eval BUILD_MODE = Mode::Release ``` ### Types that are NOT eval-compatible Classes with constructors that are not marked `eval`, classes and function who leak allocations across the function boundary, can not be used in `eval` context. --- ## Eval vs Const | | `eval` | `const` | |---|---|---| | Evaluation time | Compile time only | Runtime (at initialization) | | Initializer | Must be compile-time evaluable | Any expression | | Reassignment | No | No | | Can use in array sizes | Yes | No | | Can use as generic argument | Yes | No | | Heap allocation in initializer | No | Yes | | IO in initializer | No | Yes | `const` is an immutable binding. `eval` is a compile-time computed value. Use `const` for values that are fixed after initialization but may depend on runtime computation. Use `eval` for values that must be known at compile time. ```kairo const config = load_config("config.toml") // runtime: reads a file eval MAX_CONNECTIONS = 1024 // compile time: baked into binary ``` --- ## Eval and Requires Clauses `eval` expressions are valid in `requires` clauses. When a `requires` clause contains only `eval`-compatible expressions, it is checked at compile time: ```kairo fn stack_alloc() -> T requires sizeof T <= 4096 { // guaranteed at compile time: T fits on the stack } ``` > [!WARNING] > Using `eval` and `where` will cause a compile error. Only `requires` based bounding is allowed in a `eval` context. See [Requires Clauses](/docs/language/requires) for the full constraint system. --- ## Eval in Classes Eval can be used within class definitions to create compile-time computed values, adding eval to class fields, methods, and constructors are valid. If the class contains a non-trivial destructor, it cannot be used in an `eval` context. Classes can mix eval functions with regular functions. ```kairo class HashState { eval seed: u64 = FNV_OFFSET_BASIS; eval data: string = ""; eval fn HashState(self, input: string) { self.data = input; } eval fn hash(self) -> u64 { // Simple hash function var hash = self.seed; for c in self.data { hash = (hash * 16777619) ^ c as u64; } return hash; } } eval state = HashState("hello") eval hash = state.hash() std::println(hash); // Prints the hash of "hello" ``` ## Summary ```kairo // Eval variable eval MAX_SIZE = 1024 * 1024 eval TABLE_SIZE = 256 // Eval function eval fn power(base: i32, exp: i32) -> i32 { if exp == 0 { return 1 } return base * power(base, exp - 1) } eval TWO_TO_16 = power(2, 16) // 65536 // Eval as array size var buffer: [u8; MAX_SIZE] // Eval if (platform selection) eval if platform == "linux" { eval CACHE_LINE = 64 } else { eval CACHE_LINE = 128 } // Eval if (type specialization) fn zero() -> T { eval if typeof T == i32 { return 0 } else if typeof T == f64 { return 0.0 } else if typeof T == string { return "" } else if typeof T == bool { return false } } // Eval for (compile-time loop) eval fn sum_range(n: i32) -> i32 { var total = 0 eval for i in 1..=n { total += i } return total } eval SUM_100 = sum_range(100) // 5050 ``` --- ## Modules URL: https://www.kairolang.org/docs/language/modules/ # Modules Kairo's module system maps source files and directories to namespaces. Every `.k` file is a module. A directory containing a `module.k` entry file is a library. Use the `module` keyword to declare namespaces within a file, similar to C++ `namespace`. See [Imports](/docs/language/imports) for how to bring names from modules into scope. ## File-to-Module Mapping Each `.k` file is automatically a module named after the file (without the `.k` extension): ``` project/ main.k <- module "main" math.k <- module "math" utils/ module.k <- library entry for "utils" strings.k <- module "utils::strings" io.k <- module "utils::io" internal/ helpers.k <- module "utils::internal::helpers" ``` Nested files create nested module names via `::`. A file at `utils/io.k` is accessible as the module `utils::io`. ## Library Entry Files A directory with a `module.k` file is a **library**. The entry file controls what the library exports publicly: ```kairo // utils/module.k pub import strings // re-export utils/strings.k pub import io // re-export utils/io.k priv import internal // NOT re-exported (implementation detail) ``` Imports inside `module.k` are **relative to the directory**. `pub import strings` resolves to `utils/strings.k`. Consumers of the library see only the public API: ```kairo utils::strings::trim(" hello ") // from utils/strings.k utils::io::read_file("data.txt") // from utils/io.k // utils::internal::... // compile error: not exported ``` The library entry file is the public face of the library. Use it to re-export submodules, hide internal organization, and present a clean API boundary. ## Module Namespaces The `module` keyword declares a namespace within a file. It is equivalent to C++ `namespace`: ```kairo module serialization { pub fn to_json(data: Config) -> string { ... } pub fn from_json(input: string) panic -> Config { ... } priv fn escape_string(s: string) -> string { ... } } serialization::to_json(my_config) ``` ### Anonymous Modules A module without a name creates a scope for grouping declarations without introducing a named namespace: ```kairo module { var internal_state: i32 = 0 // not accessible from outside this block } ``` ### Nested Modules Modules can be nested: ```kairo module api { pub module v1 { pub fn handle_request(req: Request) -> Response { ... } } pub module v2 { pub fn handle_request(req: Request) -> Response { ... } } } api::v1::handle_request(req) api::v2::handle_request(req) ``` ## Module Visibility Modules themselves have visibility. The default is `pub`: ```kairo pub module api { // accessible to consumers fn handle_request(req: Request) -> Response { ... } } priv module cache { // only accessible within this file var store: {string: Data} = {} } prot module platform { // accessible within this file and sibling files in the library fn detect_os() -> string { ... } } ``` ### Visibility Scopes | Modifier | Scope | |---|---| | `pub` (default) | Any module that imports this module | | `priv` | Current file only | | `prot` | Current file and sibling files in the same library subtree | `prot` (protected) is scoped to the **library subtree**, not the immediate directory. Any file anywhere under a library's directory tree can access `prot` declarations from any other file in that same library. This is analogous to "package-private" in other languages. ## Module Reopening A module can be reopened across multiple files to extend its contents: ```kairo // encoding.k pub module codec { pub fn encode_base64(data: [byte]) -> string { ... } } ``` ```kairo // compression.k pub module codec { pub fn compress(data: [byte]) -> [byte] { ... } } ``` ```kairo // main.k encoding::codec::encode_base64(data) // defined in encoding.k compression::codec::compress(data) // defined in compression.k ``` ### Reopening Rules - Reopening a module with different visibility than the original is a compile error. - Reopening may only **add** new top-level declarations (functions, types, constants). It cannot modify existing declarations or add members to types defined in the original. - Both declarations must be at file scope (not nested inside other modules). ## Scope Resolution (`::` The `::` operator resolves names uniformly across all scoping contexts: | Context | Example | |---|---| | Module access | `std::println(...)` | | Library submodule | `math::vector::cross_product(...)` | | Class static members | `Counter::count` | | Enum variants | `Direction::North` | | Nested types | `Packet::Header` | | Base class methods | `Base::method(self)` | There is no separate syntax for module access vs type member access, all use `::`. ## The Standard Library (`std`) `std` is not automatically imported. It must be imported explicitly: ```kairo std::println("hello") std::create::(42) ``` Individual items can be imported selectively: ```kairo println("hello") ``` **Core language primitives** (`i32`, `string`, `bool`, `[T]`, etc.) and **built-in syntax** (`if`, `for`, `match`, etc.) are always available without any import. Only **standard library functions and types** (`std::println`, `std::Error`, `std::Shared`, etc.) require an explicit import. See [Imports](/docs/language/imports) for import syntax and the `pub import` pattern for re-exporting. ## Circular Dependencies Circular imports are a compile error: ```kairo // a.k // b.k ``` **Break circular dependencies** by extracting shared declarations into a third module that both A and B import: ```kairo // shared.k pub fn common_utility() { ... } // a.k // no import b // b.k // no import a ``` Circular dependencies at the module level reflect circular dependencies in the design, restructuring to break the cycle usually improves the architecture. ## Visibility on Top-Level Declarations All top-level declarations (functions, classes, modules, constants, etc.) default to `pub`. Use `priv` to restrict to the current file and `prot` for library-internal visibility: ```kairo pub fn public_api() { ... } priv fn internal_helper() { ... } prot fn library_internal() { ... } pub class Server { ... } priv class ConnectionPool { ... } pub eval MAX_CONNECTIONS = 1024 priv static var request_count: i32 = 0 ``` See [Imports](/docs/language/imports#visibility-on-imports) for how visibility affects what is re-exported via `pub import`. --- ## Extends URL: https://www.kairolang.org/docs/language/extends/ # Extends `extend` blocks add methods, operators, and static functions to types declared elsewhere. They are the primary way to attach behavior to [structs](/docs/language/structures) and [enums](/docs/language/enums), which cannot contain methods in their body. Classes can also be extended. --- ## Basic Syntax ```kairo struct Point { var x: f64 var y: f64 } extend Point { fn length(self) const -> f64 { return std::sqrt(self.x * self.x + self.y * self.y) } fn op +(self, other: Point) -> Point { return Point { x: self.x + other.x, y: self.y + other.y } } static fn origin() -> Point { return Point { x: 0.0, y: 0.0 } } } var p = Point { x: 3.0, y: 4.0 } p.length() // 5.0 Point::origin() // Point { x: 0.0, y: 0.0 } ``` Methods added via `extend` are called the same way as methods defined in a class body there is no syntactic distinction at the call site. --- ## What Can Be Extended | Type | Supports `extend` | |---|---| | Structs | Yes the only way to add methods | | Enums | Yes the only way to add methods | | Classes | Yes | | Unions | No | | Interfaces | No | --- ## What Extends Can Add The rules differ by type: ### Structs | Allowed | Not allowed | |---|---| | Methods | Constructors | | Static functions | Destructors (`fn op delete`) | | Arithmetic / comparison operators | Copy / move assignment (`fn op =`) | | `fn op as` (type conversion) | | | `fn op in` (iteration) | | Structs are trivially copyable. Extending lifecycle operations (destructors, copy/move) would break that guarantee. See [Structures](/docs/language/structures#copy-semantics). ### Enums | Allowed | Not allowed | |---|---| | Methods | Constructors | | Static functions | Destructors | | Comparison / arithmetic operators | Copy / move assignment | | `fn op as` (type conversion) | | ### Classes | Allowed | Not allowed | |---|---| | Methods | Constructors | | Static functions | Destructors | | All operators | Copy / move assignment | Classes already support methods and operators in their body. `extend` on a class is useful for separating interface conformance or adding functionality in a different section of the codebase (within the same file). > [!NOTE] > Constructors, destructors, and copy/move assignment cannot be added via `extend` on any type. > If you need construction logic on a struct, extend a static factory function instead. If you need > a destructor, use a [class](/docs/language/classes). --- ## Interface Conformance `extend ... impl` declares that a type satisfies an interface and provides the required methods: ```kairo interface Drawable { fn draw(self) const -> string } extend Point impl Drawable { fn draw(self) const -> string { return f"({self.x}, {self.y})" } } ``` The compiler verifies at the `extend` declaration that all interface requirements are satisfied. Missing methods are a compile error. A type can conform to multiple interfaces through separate `extend` blocks: ```kairo extend Point impl Drawable { fn draw(self) const -> string { ... } } extend Point impl Serializable { fn serialize(self) const -> [byte] { ... } fn byte_size(self) const -> i32 { ... } } ``` See [Interfaces](/docs/language/interfaces) for interface declarations and structural conformance. --- ## Generic Extends When extending a generic type, redeclare the type parameters: ```kairo struct Pair { var first: T var second: T } extend Pair { fn swap(self) -> Pair { return Pair { first: self.second, second: self.first } } } ``` The type parameters in the `extend` block must match the original declaration. Constraints can be added via `impl`, `derives`, or `where` clauses: ```kairo extend Pair { fn max(self) const -> T { return if self.first > self.second { self.first } else { self.second } } } ``` This `max` method is only available on `Pair` when `T` satisfies `Comparable`. Calling `Pair.max()` is a compile error. ### Generic interface conformance ```kairo interface Container { fn size(self) const -> i32 fn get(self, index: i32) const -> T } extend Pair impl Container { fn size(self) const -> i32 { return 2 } fn get(self, index: i32) const -> T { return if index == 0 { self.first } else { self.second } } } ``` See [Bounds](/docs/language/bounds) for the full constraint system. --- ## Visibility Extended methods can have `pub`, `prot`, or `priv` visibility: ```kairo extend Point { pub fn distance(self, other: Point) const -> f64 { return (self - other).length() } priv fn validate(self) const -> bool { return self.x >= 0.0 && self.y >= 0.0 } } ``` The default visibility for extended methods matches the type's convention `pub` for struct and enum extensions, `pub` for class method extensions. --- ## `Self` in Extend Blocks `Self` is available in `extend` blocks and resolves to the extended type: ```kairo extend Pair { fn duplicate(self) const -> Self { // Self resolves to Pair return Pair { first: self.first, second: self.second } } } ``` --- ## Same-File Restriction A plain `extend` block must be in the same file as the type definition: ```kairo // point.k struct Point { var x: f64; var y: f64 } extend Point { fn length(self) const -> f64 { ... } // ok: same file as Point } ``` ```kairo // other.k // extend Point { ... } // compile error: Point is defined in a different file ``` For `extend ... impl` blocks (interface conformance), the rule is looser: either the type or the interface must be defined in the same file as the `extend` block: ```kairo // drawable.k interface Drawable { fn draw(self) const -> string } // This is legal even though Point is defined in point.k, // because Drawable is defined here: extend Point impl Drawable { fn draw(self) const -> string { return f"({self.x}, {self.y})" } } ``` This prevents conflicts between extensions in different files while still allowing interface conformance to be declared where the interface is defined. --- ## Multiple Extend Blocks A type can have multiple `extend` blocks in the same file. Each block can target different interfaces or group related functionality: ```kairo struct Color { var r: u8 var g: u8 var b: u8 } // Arithmetic operations extend Color { fn op +(self, other: Color) -> Color { return Color { r: (self.r as i32 + other.r as i32).min(255) as u8, g: (self.g as i32 + other.g as i32).min(255) as u8, b: (self.b as i32 + other.b as i32).min(255) as u8, } } } // Serialization extend Color impl Serializable { fn serialize(self) const -> [byte] { ... } fn byte_size(self) const -> i32 { return 3 } } // Display extend Color impl Drawable { fn draw(self) const -> string { return f"rgb({self.r}, {self.g}, {self.b})" } } ``` --- ## Extend vs Class Methods For classes, there is no semantic difference between a method in the class body and a method in an `extend` block both produce the same compiled output. The choice is organizational: ```kairo class Server { var port: i32 fn Server(self, port: i32) { self.port = port } // Core functionality in the class body fn start(self) { ... } fn stop(self) { ... } } // Interface conformance separated extend Server impl Loggable { fn to_log_string(self) const -> string { return f"Server(:{self.port})" } } ``` --- ## Summary ```kairo // Extend a struct with methods struct Vec2 { var x: f64; var y: f64 } extend Vec2 { fn length(self) const -> f64 = std::sqrt(self.x * self.x + self.y * self.y) fn op +(self, other: Vec2) -> Vec2 = Vec2 { x: self.x + other.x, y: self.y + other.y } static fn zero() -> Vec2 = Vec2 { x: 0.0, y: 0.0 } } // Extend an enum with methods extend Direction { fn opposite(self) -> Direction { match self { case .North { return .South } case .South { return .North } case .East { return .West } case .West { return .East } } } } // Interface conformance extend Vec2 impl Drawable { fn draw(self) const -> string = f"({self.x}, {self.y})" } // Generic extend with constraints extend Pair { fn max(self) const -> T = if self.first > self.second { self.first } else { self.second } } ``` --- ## Attributes URL: https://www.kairolang.org/docs/language/attributes/ # Attributes Attributes are compile-time AST transformations. They modify the structure of declarations renaming fields, injecting code, adding members with full access to the parsed syntax tree and type information. Unlike [macros](/docs/language/macros), which operate on raw tokens before parsing, attributes run after parsing and can inspect and modify typed AST nodes. | Property | Guarantee | |---|---| | Type safety | Yes | | Hygiene | Yes | | Scope | Yes | | Expansion time | Compile time | | Expansion order | Inner to outer | | Expansion context | The node the attribute is attached to | --- ## Defining Attributes Attributes are defined with the `macro` keyword followed by `@name` and a parameter list. The first parameter is always a pointer to the AST node being transformed: ```kairo macro @add_logging(node: *AST::FunctionDecl) { node->body.prepend( AST::parse!(std::println(f"entering {stringify!(node->name)}")) ) node->body.append( AST::parse!(std::println(f"exiting {stringify!(node->name)}")) ) } ``` ```kairo @add_logging fn process_data(x: i32) -> i32 { return x * 2 } ``` After expansion: ```kairo fn process_data(x: i32) -> i32 { std::println("entering process_data") var __result = x * 2 std::println("exiting process_data") return __result } ``` ### Node preservation rule Attributes must preserve the node type. An attribute attached to a function declaration receives a `*AST::FunctionDecl` and must leave it as a function declaration it cannot replace it with a class or a variable. This ensures the expansion process is deterministic and the AST remains structurally consistent. To add new nodes, create them with `@create AST::NodeType()` and attach them to the existing node (e.g., appending statements to a function body, adding members to a class). --- ## Attribute Arguments Attributes can take additional arguments beyond the implicit node parameter: ```kairo macro @repeat(block: *AST::Block, times: i32) { var original = block->clone() var expanded = @create AST::Block() for i in 0..times { expanded->body.append(original.clone()) } *block = *expanded } @repeat(3) { std::println("hello") } ``` After expansion: ```kairo { { std::println("hello") } { std::println("hello") } { std::println("hello") } } ``` Arguments are passed in parentheses after the attribute name at the use site. The node parameter is implicit it is always the declaration or block the attribute is attached to. --- ## Overloading Attribute definitions can be overloaded by node type or argument types. The compiler selects the correct overload based on what the attribute is attached to: ```kairo macro @serialize(node: *AST::ClassDecl) { // generate serialization for a class } macro @serialize(node: *AST::StructDecl) { // generate serialization for a struct } ``` ```kairo @serialize class Config { ... } // calls the ClassDecl overload @serialize struct Point { ... } // calls the StructDecl overload ``` --- ## Expansion Order When multiple attributes are stacked on a single declaration, they expand inner to outer the attribute closest to the declaration runs first: ```kairo @deserialize // runs second, on the result of @serializable @serializable // runs first, on the original class class Config { var host: string var port: i32 } ``` This allows attributes to compose `@serializable` can add serialization methods, and `@deserialize` can then inspect those methods to generate the inverse. --- ## Attaching Attributes Attributes can be attached to any AST node: ```kairo // On a function @inline fn hot_path(x: i32) -> i32 { ... } // On a class @packed class Header { ... } // On a struct @align(16) struct SimdData { ... } // On a block @repeat(3) { std::println("repeated") } // On a variable (if the attribute accepts VariableDecl) @deprecated("use new_config instead") var old_config: Config ``` --- ## Built-in Attributes Kairo provides built-in attributes that are handled directly by the compiler: ### Layout attributes | Attribute | Description | Applies to | |---|---|---| | `@packed` | Remove padding between members | Classes, structs, unions | | `@align(N)` | Set minimum alignment to N bytes | Classes, structs, unions | See [Classes](/docs/language/classes#memory-layout) and [Structures](/docs/language/structures#memory-layout) for layout details. ### Branch hints | Attribute | Description | Applies to | |---|---|---| | `@likely` | Condition is expected to be true | `if` statements | | `@unlikely` | Condition is expected to be false | `if` statements | | `@unreachable` | Branch should never execute (UB if reached) | `match`/`if` branches | See [Control Flow](/docs/language/control-flow#branch-hints) for branch prediction hints. ### Diagnostics | Attribute | Description | Applies to | |---|---|---| | `@no_warn(CODE)` | Suppress a specific compiler warning | Any declaration | | `@deprecated(msg)` | Mark a declaration as deprecated | Any declaration | ### Other | Attribute | Description | Applies to | |---|---|---| | `@core::where_handler` | Custom handler for where-clause failures | Functions | See [Where Clauses](/docs/language/bounds) for the where handler system. --- ## The `std::AST` API Attribute definitions interact with the AST through the `std::AST` module. This module provides types representing each kind of AST node (`FunctionDecl`, `ClassDecl`, `StructDecl`, `Block`, `VariableDecl`, etc.) and methods for inspecting and modifying them. > [!IMPORTANT] > The `std::AST` API is under development. The full set of node types, their fields, and available > methods will be documented once the API is finalized. The examples on this page demonstrate the > intended usage patterns. Key operations available on AST nodes: | Operation | Description | |---|---| | `node->name` | Access the declaration name | | `node->body` | Access the body (for functions, blocks) | | `node->body.append(stmt)` | Add a statement to the end | | `node->body.prepend(stmt)` | Add a statement to the beginning | | `node->clone()` | Deep-copy the node | | `AST::parse!(code)` | Parse a code fragment into an AST node | | `@create AST::T()` | Create a new AST node of type T | --- ## Macros vs Attributes | | Macros | Attributes | |---|---|---| | Operates on | Raw tokens | AST nodes | | Type awareness | No | Yes | | Expansion time | Before parsing | After parsing | | Can modify structure | Token substitution only | Can transform the AST | | Syntax | `name!(args)` | `@name` on declarations | | Must preserve node type | N/A | Yes | Use macros for simple substitutions and conditional compilation. Use attributes for structural transformations that need to inspect or modify declarations. See [Macros](/docs/language/macros) for the token-level macro system. --- ## Summary ```kairo // Define an attribute macro @timer(node: *AST::FunctionDecl) { node->body.prepend( AST::parse!(var __start = std::time::now()) ) node->body.append( AST::parse!(std::println(f"elapsed: {std::time::now() - __start}ms")) ) } // Use an attribute @timer fn expensive_computation() { // ... work ... } // Attribute with arguments macro @version(node: *AST::ClassDecl, ver: string) { // add a static VERSION member to the class } @version("2.1.0") class MyLibrary { ... } // Stacked attributes (inner to outer) @json_output @validate_fields struct ApiResponse { var status: i32 var body: string } // Built-in attributes @packed @align(16) struct CacheLine { var data: [u8; 64] } @likely if hot_path { fast_operation() } ``` --- ## Macros URL: https://www.kairolang.org/docs/language/macros/ # Macros Macros in Kairo are token-level substitutions they operate on raw tokens before parsing, similar to C/C++ `#define` but with scoping and balanced-delimiter requirements. Macros are identified by the `!` suffix on their name. For AST-level transformations with type awareness, see [Attributes](/docs/language/attributes). --- ## Defining Macros A macro is defined with the `macro` keyword, a name ending in `!`, optional parameters, and a body. The body must have balanced delimiters: ```kairo macro double!(x) { x + x } macro greeting! { "hello, world" } ``` At every use site, the preprocessor replaces the macro invocation with the body, substituting parameters: ```kairo var a = double!(5) // replaced with: 5 + 5 var b = greeting! // replaced with: "hello, world" ``` ### Parameters Parameters are typeless they accept any sequence of tokens. Multiple parameters are comma-separated: ```kairo macro clamp!(value, lo, hi) { if value < lo { lo } else if value > hi { hi } else { value } } var x = clamp!(temperature, 0, 100) ``` ### Macros as arguments Macros can be passed to other macros. The inner macro is expanded at the final substitution site: ```kairo macro tag! { "debug" } macro repeat!(m) { m! m! m! } var labels = repeat!(tag) // becomes: "debug" "debug" "debug" ``` --- ## Built-in Macros Kairo provides a set of compiler-intrinsic macros for common tasks. ### Token manipulation | Macro | Description | |---|---| | `concat!(a, b)` | Join tokens into a single token | | `stringify!(a)` | Convert a token to a string literal | | `unstringify!(s)` | Convert a string literal back to tokens | ```kairo var name = concat!(my, _var) // becomes: my_var var s = stringify!(some_ident) // becomes: "some_ident" ``` > [!WARNING] > `unstringify!` converts a string into raw tokens that are injected into the source. This is a > potential injection risk only use with trusted, compile-time-known strings. ### Variadic helpers | Macro | Description | |---|---| | `count!(...args)` | Number of arguments | | `first!(...args)` | First argument | | `last!(...args)` | Last argument | | `rest!(...args)` | All arguments except the first | | `init!(...args)` | All arguments except the last | ```kairo count!(a, b, c) // 3 first!(a, b, c) // a last!(a, b, c) // c rest!(a, b, c) // b, c init!(a, b, c) // a, b ``` ### Source location | Macro | Description | |---|---| | `file!` | Current file path as a string literal | | `line!` | Current line number as an integer literal | | `column!` | Current column number as an integer literal | | `module_path!` | Current module path as a string literal | ```kairo std::println(f"logged from {file!}:{line!}") ``` ### Diagnostics | Macro | Description | |---|---| | `error!(msg)` | Emit a compile error with the given message | | `warning!(msg)` | Emit a compile warning | | `note!(msg)` | Emit a compile note | ```kairo eval if platform == "wasm" { error!("WebAssembly is not supported yet") } ``` An optional error code can be passed as a second argument: ```kairo warning!("deprecated API", "W0042") ``` ### Code generation | Macro | Description | |---|---| | `include!(path)` | Paste file contents as tokens | | `include_str!(path)` | File contents as a string literal | | `embed!(path)` | File contents as a byte array (`[byte]`) | | `unique_id!` | Generate a unique identifier | | `todo!(msg)` | Runtime panic placeholder with optional message | | `unreachable!(msg)` | Assert a code path is unreachable | ```kairo // Embed a shader as a string eval VERTEX_SHADER = include_str!("shaders/vertex.glsl") // Embed a binary resource eval ICON_DATA = embed!("assets/icon.png") // Mark unfinished code fn process() -> Config { todo!("implement config parsing") } ``` `todo!()` panics at runtime with the given message. `unreachable!()` is undefined behavior if reached the optimizer assumes the code path is dead. ### Conditional | Macro | Description | |---|---| | `defined!(name)` | Check if a macro with the given name exists | ```kairo eval if defined!(DEBUG_MODE) { fn log(msg: string) { std::println(f"[DEBUG] {msg}") } } else { fn log(msg: string) { } } ``` ### Hygiene | Macro | Description | |---|---| | `undef!(name)` | Remove a macro definition | ```kairo macro TEMP! { 42 } var x = TEMP! // 42 undef!(TEMP) // var y = TEMP! // compile error: TEMP is not defined ``` --- ## Compiler Intrinsic Macros Some macros are compiler intrinsics that perform operations beyond token substitution: | Macro | Description | Details | |---|---|---| | `label!(name)` | Declare a jump target | [Control Flow](/docs/language/control-flow#jumps) | | `jump!(name)` | Unconditional jump to a label | [Control Flow](/docs/language/control-flow#jumps) | | `forget!(ptr)` | Drop a pointer from AMT tracking | [Unsafe](/docs/language/unsafe#forget) | | `unwrap!(expr)` | Force-unwrap a nullable, panic on null | [Variables](/docs/language/variables#force-unwrap) | | `mref!(T)` | Produce an rvalue reference type (`&&T`) | [Classes](/docs/language/classes#the-rule-of-five) | These use macro syntax (`name!`) to make their usage explicit and searchable in a codebase, but they are not user-definable they are built into the compiler. --- ## Scoping Unlike C/C++ `#define`, Kairo macros respect scope. A macro defined inside a module or block is only visible within that scope: ```kairo module internal { macro BUFFER_SIZE! { 4096 } var buf: [u8; BUFFER_SIZE!] } // BUFFER_SIZE! is not visible here ``` Top-level macros follow the same visibility rules as other declarations: ```kairo pub macro MAX_RETRIES! { 3 } // visible to importers priv macro INTERNAL_FLAG! { true } // file-scoped ``` --- ## Macros vs Attributes | | Macros | Attributes | |---|---|---| | Operates on | Raw tokens | AST nodes | | Type awareness | No | Yes | | Expansion time | Before parsing | After parsing | | Can modify structure | Token substitution only | Can transform the AST | | Syntax | `name!(args)` | `@name` on declarations | | Hygiene | Scoped, balanced delimiters | Full AST hygiene | Use macros for simple substitutions, constants, and conditional compilation. Use attributes for code transformations that need type information or structural awareness. See [Attributes](/docs/language/attributes) for the AST-level transformation system. --- ## Summary ```kairo // Define a macro macro square!(x) { x * x } var s = square!(5) // 25 // No-parameter macro macro VERSION! { "1.0.0" } std::println(f"version: {VERSION!}") // Built-in macros std::println(f"file: {file!}, line: {line!}") eval SHADER = include_str!("shader.glsl") eval ICON = embed!("icon.png") // Variadic helpers var n = count!(a, b, c, d) // 4 // Diagnostics eval if sizeof usize < 8 { error!("64-bit platform required") } // Conditional compilation macro DEBUG! { true } eval if defined!(DEBUG) { fn trace(msg: string) { std::println(f"[TRACE] {msg}") } } // Scoped macros module config { priv macro DEFAULT_PORT! { 8080 } pub eval PORT = DEFAULT_PORT! } ``` --- ## Concurrency URL: https://www.kairolang.org/docs/language/concurrency/ # Concurrency > [!IMPORTANT] > This page is under development. The concurrency model is being designed. Full documentation will > be added once the design is finalized. Kairo's concurrency system provides async/await, coroutines, and thread-level primitives. The following features are planned and referenced across the existing documentation: ### Async/Await The `async` modifier on functions and the `await` keyword for waiting on asynchronous results. See [Functions](/docs/language/functions#function-modifiers) for the `async` modifier. ### Coroutines (`yield`) Functions with a `yield T` return type produce values cooperatively. The `yield` keyword suspends the function and produces a value to the caller. See [Functions](/docs/language/functions#special-return-types) for yield return types. ### `spawn` Launching concurrent work. Syntax and runtime model (green threads, OS threads, or event loop) are being finalized. ### Atomic Types (`atomic T`) Thread-safe wrapper type for lock-free operations. Referenced in [Functions](/docs/language/functions#special-return-types). ### Thread-Local Storage (`thread T`) Per-thread storage modifier. Referenced in [Functions](/docs/language/functions#special-return-types). ### Synchronization Primitives Mutexes, channels, and other coordination mechanisms will be documented here once the standard library concurrency API is finalized. ### Custom Awaitables Classes can define `fn op await(self, obj: std::forward) -> T` to customize the behavior of `await` when called on an instance. See [Operators](/docs/language/operators#special-operators) for the `op await` overload. ```kairo await async_fn() // syntax sugar for a state machine spawn some_async_fn() // syntax sugar for detaching a thread yield some_value // syntax sugar for a coroutine, function must have yield on return type fn get_tokens() -> yield string { yield "token1" yield "token2" } ``` `op await` is overloadable: `fn op await (self) -> T` for custom async types. - `await async_fn()` syntax sugar for state machine - `spawn some_async_fn()` syntax sugar for detaching a thread - `yield some_value` coroutine syntax, function must have `-> yield T` return type - `fn op await (self) -> T` overloadable for custom async types - `atomic T` thread-safe wrapper type - `thread T` thread-local storage type --- ## C & C++ Interoperability URL: https://www.kairolang.org/docs/language/c-c++/ # C & C++ Interoperability Kairo provides zero-overhead, bidirectional interoperability with C and C++. There is no serialization layer, no binding generator, and no runtime bridge Kairo emits ABI-compatible object code and consumes C/C++ headers directly. This page covers the full interop surface: calling C/C++ from Kairo, exposing Kairo to C/C++, inline C++ blocks, pointer and reference passing, templates and concepts across the boundary, exception interop, and the underlying ABI contract. --- ## Coverage Matrix The table below summarizes which C and C++ features Kairo can consume and expose. Rows marked **bidirectional** work in both directions. | Feature | Direction | Notes | |---|---|---| | Functions | Bidirectional | Includes variadic functions | | Structs | Bidirectional | Layout-compatible; see [Structs](/docs/structs) | | Unions | Bidirectional | See [Unions](/docs/unions) | | Enums | Bidirectional | See [Enums](/docs/enums) | | Classes | Bidirectional | Vtable-compatible; see [Classes](/docs/classes) | | Templates | Bidirectional | Instantiation across the boundary; see [below](#templates-and-concepts) | | Concepts | Bidirectional | Kairo's `impl` constraints map to C++20 concepts | | Namespaces | Bidirectional | | | Pointers & References | Bidirectional | Requires `unsafe` on the Kairo side; see [below](#pointers-and-references) | | Operator Overloading | Bidirectional | See [Operators](/docs/operators) | | Lambdas | Bidirectional | | | Exceptions | C++ -> Kairo | Kairo -> C++ is on the roadmap | | Macros | C++ -> Kairo | Preprocessor macros are expanded before Kairo sees them | | Preprocessor Directives | C++ -> Kairo | | | Inline Assembly | Kairo -> C++ | Via `inline "asm"` blocks | | Coroutines | Bidirectional | | | Named Modules (`import std;`) | Not yet | See [Modules note](#a-note-on-c-modules) | --- ## Calling C/C++ from Kairo Import a C or C++ header with the `ffi` directive. The compiler parses the header, extracts declarations, and makes them available as native Kairo symbols no wrapper code required. ```kairo // main.k ffi "c++" import "my_code.hh"; fn main() { var obj = MyClass("Kairo") std::println(f"name = {obj.get_name()}") my_function(42) } ``` Given this C++ header: ```cpp // my_code.hh #include #include class MyClass { public: MyClass(std::string name) : name(name) {} std::string get_name() const { return name; } private: std::string name; }; void my_function(int x) { std::cout << "Hello from C++! x = " << x << std::endl; } ``` Build and run: ```sh kairo main.k ./main ``` ``` name = Kairo Hello from C++! x = 42 ``` > [!NOTE] `ffi "c++"` invokes Clang's frontend internally to parse the header. All exported > declarations functions, classes, enums, templates become available in Kairo's scope with their original > names and signatures. No code generation or binding step is visible to the user. --- ## Exposing Kairo to C++ Going the other direction requires the **`kcc`** driver, a libclang-based compiler wrapper that makes `#include "file.k"` work transparently in C++ translation units. ```kairo // my_code.k fn my_kairo_function(x: i32) { std::println(f"Hello from Kairo! x = {x}") } class MyKairoClass { pub var name: string fn MyKairoClass(self, name: string) { self.name = name } fn get_name(self) -> string { return self.name } } ``` ```cpp // main.cpp #include "my_code.k" #include int main() { MyKairoClass obj("C++"); std::cout << "name = " << obj.get_name() << std::endl; my_kairo_function(42); return 0; } ``` ```sh kcc main.cpp -o main ./main ``` ``` name = C++ Hello from Kairo! x = 42 ``` ### How `kcc` works `kcc` is a Clang driver with a single addition: a preprocessor hook that intercepts `#include` directives. When the included file has a `.k` extension, `kcc`: 1. Invokes the Kairo compiler **in-process as a library** to produce a C++-compatible header containing forward declarations and wrapper signatures. 2. Compiles the `.k` file into an object file. 3. Links the Kairo object into the final binary at the end of the pipeline. Auto-linking can be disabled with **`-fno-kairo-link`** if you need manual control over the link step. ### Manual workflow (without `kcc`) If you prefer a standard C++ build process, compile the Kairo source to a static library and a generated header, then link normally: ```sh kairo my_code.k -c -o my_code -xc++ -header my_code.hh g++ main.cpp my_code.o -o main ``` > [!TIP] > `kcc` is the simplest path for mixed codebases. The manual workflow is better when Kairo > is a dependency consumed by an existing CMake/Meson/Bazel project that manages its own link step. --- ## The `ffi` Keyword `ffi` controls linkage and name mangling. It can be applied to individual declarations or to blocks. ```kairo // C++ linkage name mangling, overloading, classes, templates all permitted ffi "c++" { fn compute(x: i32) -> i32 { return x + 1; } } // C linkage no name mangling, same restrictions as extern "C" in C++ ffi "c" fn add(x: i32, y: i32) -> i32 { return x + y; } ``` `ffi "c"` follows the same rules as `extern "C"` in C++: no classes, no overloading, no templates. `ffi "c++"` follows the same rules as `extern "C++"`: full C++ feature set, Itanium or MSVC mangling depending on the target. --- ## Inline ASM For cases where you need to embed hardware-specific instructions, use `inline "asm"` blocks. Kairo uses the extended assembly syntax (outputs, inputs, and clobbers) to allow safe interaction between assembly and Kairo variables. ```kairo fn get_timestamp() -> u64 { var low: u32 var high: u32 // The syntax follows: "instruction" : outputs : inputs : clobbers unsafe { inline "asm" { "rdtsc" : "=a"(low), "=d"(high) : : } return (high as u64 << 32) | (low as u64) } fn syscall_example(fd: i32, buf: *u8, len: usize) -> isize { var ret: isize unsafe { inline "asm" volatile { "syscall" : "=a"(ret) : "a"(1), "D"(fd), "S"(buf), "d"(len) : "rcx", "r11", "memory" } } return ret } ``` ### Constraints and Safety - **Volatile**: Use `inline "asm" volatile` if the assembly has side effects that the optimizer might otherwise remove (like a syscall or hardware port I/O). - **Clobbers**: Always list registers modified by the assembly (like `memory` or specific registers) to prevent the AMT analysis and LLVM from making incorrect assumptions about the state of the machine. - **Unsafe**: Assembly code is inherently unsafe and should be wrapped in an `unsafe` block to indicate that the programmer is responsible for ensuring the correctness of the assembly. --- ## Pointers and References Kairo's [pointer model](/docs/pointers) distinguishes safe pointers (`*T`, non-nullable, bounds-tracked) from raw pointers (`unsafe *T`, no tracking). Passing any pointer or reference across the FFI boundary requires explicit `unsafe` context because the compiler cannot enforce safety guarantees on the C/C++ side. ### Safe variable, unsafe pass ```kairo ffi "c++" import "my_code.hh"; fn main() { var x = 41 // compile error: cannot pass reference to C function without unsafe block // add_one(&x) unsafe { add_one(unsafe &x) // strips bounds checking; caller owns the memory contract } // for calling c++ with pointers and back, you must use unsafe blocks, // short hand syntax is not allowed `unsafe add_one(unsafe &x)` is a compile error std::println(f"x = {x}") // x = 42 } ``` `unsafe &` creates a raw pointer from a safe binding. The compiler relinquishes tracking for that pointer the caller is responsible for lifetime and aliasing correctness. ### Raw pointer from the start If the value will be passed to C/C++ repeatedly, allocate it as a raw pointer upfront: ```kairo ffi "c++" import "my_code.hh"; fn main() { var x: unsafe *i32 = @create i32(41) add_one(x) // already unsafe no block needed std::println(f"x = {*x}") // x = 42 } ``` > [!WARNING] > `std::create` uses Kairo's global allocator, which is compatible with > C++'s `new`/`delete` by default. If you supply a custom allocator, C++ code **must not** free the resulting pointer with `delete` doing so is undefined behavior. See [AMT](/docs/amt) for allocator details. > [!WARNING] > `unsafe *T` pointers can be null. Dereferencing a null `unsafe *T` is undefined > behavior the compiler will not insert a null check. --- ## Templates and Concepts Kairo generics and C++ templates are interchangeable across the boundary. A C++ concept can constrain a Kairo generic parameter, and a Kairo generic type can satisfy a C++ concept. ```cpp // my_code.hh #include template concept Addable = requires(T a, T b) { { a + b } -> std::same_as; }; ``` ```kairo // MyInt.k ffi "c++" import "my_code.hh"; class MyInt { pub var value: T fn MyInt(self, value: T) { self.value = value } fn op + (self, other: MyInt) -> MyInt { return MyInt(self.value + other.value) } } fn add(a: T, b: T) -> T { return a + b } ``` ```cpp // main.cpp #include #include "my_code.hh" #include "MyInt.k" int main() { MyInt a(5), b(10); MyInt c = add(a, b); std::cout << "c.value = " << c.value << std::endl; // 15 int x = add(3, 4); std::cout << "x = " << x << std::endl; // 7 } ``` `T impl Addable` in Kairo maps directly to `Addable T` in the generated C++ the constraint is preserved across the boundary, not erased. --- ## Exceptions Kairo can catch C++ exceptions using its standard `try`/`catch` syntax. ```kairo ffi "c++" import "my_code.hh"; fn main() { try { might_throw(true); } catch e: std::exception { std::println(f"Caught: {e.what()}") } } ``` > [!NOTE] > Unlike Kairo's [panic system](/docs/panic), the compiler cannot statically > determine every exception type a C++ function might throw. A `catch` block that doesn't handle a thrown type > will propagate the exception up the stack. If nothing catches it, the runtime calls `std::terminate`. Throwing Kairo exceptions into C++ is not yet supported. Use error codes or return types (e.g., `Panickable`) for error signaling in the Kairo -> C++ direction. --- ## ABI Compatibility Kairo emits object code conforming to the platform's native C++ ABI: - **Unix-like systems:** Itanium C++ ABI - **Windows:** Microsoft C++ ABI This means Kairo `.o`/`.obj` files can be linked with object files from any ABI-compliant C++ compiler (GCC, Clang, MSVC) without shims or translation layers. Name mangling, vtable layout, RTTI, and exception unwinding tables all follow the platform convention. `ffi "c++"` declarations use C++ mangling. `ffi "c"` declarations use C mangling (no decoration). This matches the behavior of `extern "C++"` and `extern "C"` in C++. --- ## A Note on C++ Modules C++20 named modules (`import std;`, `import my_module;`) are **not currently supported**. The interop layer relies on header-based inclusion via Clang's preprocessor, and module interface deserialization (consuming pre-compiled BMIs) is a future roadmap item. Exporting Kairo code as a C++ module interface unit (`.cppm`) is also planned but not yet implemented. **For now:** use header-based interop (`ffi "c++"` + `kcc`) for all cross-language boundaries. --- ## Imports URL: https://www.kairolang.org/docs/language/imports/ # Imports The `import` statement brings names from other modules and external libraries into the current scope. Imports are how you access code defined in other files and C/C++ headers. See [Modules](/docs/language/modules) for how files organize into module namespaces and how the module system resolves import paths. ## Import Forms ```kairo ``` ## Resolution Semantics When you write `import Foo`, the compiler resolves it in this order: 1. **File first:** look for `Foo.k` as a sibling file. 2. **Library fallback:** if no `Foo.k` exists, look for `Foo/module.k` (library entry). 3. **Ambiguity warning:** if both `Foo.k` and `Foo/module.k` exist, the import resolves to `Foo.k` and the compiler emits a warning: > ambiguous import 'Foo' resolves to Foo.k, but Foo/module.k also exists; rename the file or use `import module Foo` to import the library The `module` modifier overrides this: `import module Foo` skips step 1 and resolves **only** against `Foo/module.k`. If no library entry exists, the compiler errors. ### Resolution with `::` `import Foo::bar` applies the same file-first rule to `Foo`: 1. Resolve `Foo` -> `Foo.k` (or `Foo/module.k` if no `Foo.k`). 2. Look for symbol `bar` inside the resolved module. The `::` does not change the resolution strategy. If you want `bar` to resolve as a **library submodule** (`Foo/bar/module.k`), use `import module Foo::bar`. ```kairo // Given: // math.k contains `fn add(...)` // math/module.k library entry // math/vector/module.k submodule ``` ## Visibility on Imports Imports default to **`priv`** (private). A private import brings names into the current file but does not re-export them to consumers of this module: ```kairo priv import std::collections // default, only visible in this file pub import std::collections // re-exported to consumers prot import std::platform // visible to sibling files in same library ``` ### Re-export Pattern Use `pub import` explicitly in library entry files to curate the public API: ```kairo // network/module.k pub import tcp // consumers of "network" can access tcp pub import udp // consumers of "network" can access udp priv import dns // implementation detail, not re-exported ``` Consumers see only the public surface: ```kairo network::tcp::connect("example.com", 80) // ok network::udp::send_packet(data) // ok // network::dns::lookup(...) // compile error: not exported ``` ### Why Private by Default Default-private prevents transitive dependency leakage. If `network.k` imports an internal utility module, consumers of `network` don't accidentally see that utility in their namespace. Public re-export is an explicit, intentional choice by the module author. ## Selective Imports Selective imports bring specific items from a module: ```kairo ``` Each item can have its own alias: ```kairo ``` Items can reference nested paths relative to the base: ```kairo ``` Here `HashMap` resolves as `std::collections::HashMap` and `LinkedList` resolves as `std::collections::list::LinkedList`. A wildcard inside a selective list is valid but unusual: ```kairo ``` ## Wildcard Imports ```kairo ``` Brings all public symbols from `math` into the current scope. Use sparingly, it obscures where names come from and can cause collisions. **Prefer selective imports.** ## Aliased Imports ```kairo m::Vector3(1.0, 2.0, 3.0) ``` The alias replaces the module name in the current scope. The original name (`math`) is **not** available, only `m`. ## Library-Only Imports ```kairo ``` Skips the file-first lookup and resolves **only** against the library entry (`math/module.k`). Use this when both `math.k` and `math/module.k` exist and you specifically want the library version. If no `math/module.k` exists, this is a compile error, it does not fall back to `math.k`. ## FFI Header Imports C and C++ headers are imported via the `ffi` keyword: ```kairo ffi "c++" import "graphics.hh" ffi "c" import "legacy_api.h" ``` All declarations from the header are imported into the current namespace. Use `as` to namespace the import: ```kairo ffi "c++" import "graphics.hh" as gfx gfx::create_window(800, 600) gfx::RenderContext() ``` ### C++ `std` Namespace If a C++ header defines names in the `std` namespace, those collide with Kairo's `std` module. The C++ standard library is accessible through `libcxx`: ```kairo std::println("Kairo") cout << "C++" << endl ``` FFI imports of headers that define names in `std` are automatically redirected to `libcxx`: ```kairo ffi "c++" import "iostream" // imported into libcxx, not std ``` See [C/C++ Interop](/docs/language/c-c++) for the full FFI model. ## FFI Linkage Declarations The `ffi` keyword also applies foreign linkage to individual declarations or blocks: ```kairo // Single declaration ffi "c++" fn create_window(i32, i32) -> *Window // Block of declarations ffi "c++" { fn gl_init() fn gl_clear(u32) class Renderer { fn draw(self, *Mesh) } } ``` Declarations inside an `ffi` block are parsed normally but stamped with C/C++ linkage. See [C/C++ Interop](/docs/language/c-c++) for details on FFI constraints and capabilities. ## Import Reference | Form | Resolves to | Notes | |------|-------------|-------| | `import Foo` | `Foo.k`, fallback `Foo/module.k` | File-first resolution | | `import Foo::bar` | Symbol `bar` in `Foo` | `::` does not change resolution | | `import Foo::{a, b}` | Symbols `a`, `b` in `Foo` | Per-item aliases supported | | `import Foo::*` | All public symbols from `Foo` | Avoid; prefer selective imports | | `import Foo as m` | `Foo.k` or `Foo/module.k`, aliased to `m` | Original name unavailable | | `import module Foo` | `Foo/module.k` only | Explicit library import; errors if no entry | | `ffi "c++" import "h.hh"` | C++ header | Optional `as` namespace | | `ffi "c++" fn foo(...)` | Single decl with C++ linkage | | | `ffi "c++" { ... }` | Block of decls with C++ linkage | | --- ======================================================================== SECTION: TOOLCHAIN ======================================================================== ## kals URL: https://www.kairolang.org/docs/toolchain/kals/ --- ## kbld URL: https://www.kairolang.org/docs/toolchain/kbld/ --- ## kfmt URL: https://www.kairolang.org/docs/toolchain/kfmt/ --- ## kld URL: https://www.kairolang.org/docs/toolchain/kld/ --- ## kpkg URL: https://www.kairolang.org/docs/toolchain/kpkg/ --- ======================================================================== SECTION: EXAMPLES ======================================================================== ## Example: HTTP Server URL: https://www.kairolang.org/docs/examples/http-server/ # Example: HTTP Server This example builds a minimal HTTP server from scratch, demonstrating how Kairo's features compose in a real application. The server parses requests, routes them to handlers, and sends responses -- all with type-safe error handling and automatic memory management via AMT. --- ## Project Structure ``` http-server/ http-server.k <- library entry request.k response.k router.k server.k main.k ``` --- ## Request Parsing `request.k` defines the HTTP request model and a parser that converts raw bytes into a typed request object. ```kairo // request.k pub enum Method { Get, Post, Put, Delete, Patch, Head, Options, } extend Method { static fn from_string(s: string) panic -> Method { match s { case "GET" { return .Get } case "POST" { return .Post } case "PUT" { return .Put } case "DELETE" { return .Delete } case "PATCH" { return .Patch } case "HEAD" { return .Head } case "OPTIONS" { return .Options } default { panic ParseError("unknown HTTP method", 0) } } } } pub struct Header { var name: string var value: string } pub class Request { pub var method: Method pub var path: string pub var headers: [Header] pub var body: string priv var query_params: {string: string} fn Request(self, method: Method, path: string) { self.method = method self.path = path self.headers = [] self.body = "" self.query_params = {} } pub fn get_header(const self, name: string) -> string? { for h in self.headers { if h.name == name { return h.value } } return null } pub fn content_length(const self) -> i32 { var cl? = self.get_header("Content-Length") return cl as i32 ?? 0 } } pub class ParseError { pub var message: string pub var position: i32 fn ParseError(self, msg: string, pos: i32) { self.message = msg self.position = pos } } pub fn parse_request(raw: string) panic -> Request { var lines = raw.split("\r\n") if lines.length() == 0 { panic ParseError("empty request", 0) } // Parse request line: "GET /path HTTP/1.1" var parts = lines[0].split(" ") if parts.length() < 3 { panic ParseError("malformed request line", 0) } var method = Method::from_string(parts[0]) var path = parts[1] var req = Request(method, path) // Parse headers var i = 1 while i < lines.length() && lines[i].length() > 0 { var colon = lines[i].index_of(":") if colon < 0 { panic ParseError("malformed header", i) } req.headers.push(Header { name: lines[i].substring(0, colon).trim(), value: lines[i].substring(colon + 1).trim(), }) i += 1 } // Remaining lines are the body if i + 1 < lines.length() { req.body = lines[i + 1] } return req } ``` Key patterns: - `Method` is a plain enum with behavior added via `extend` - `Header` is a struct plain data, no constructor needed - `Request` is a class with private state (`query_params`) and public accessors - `parse_request` uses `panic` for error signaling callers must handle `ParseError` - `get_header` returns `string?` for safe nullable access --- ## Response Building `response.k` defines the response model with a builder-style API. ```kairo // response.k pub enum StatusCode derives u16 { Ok = 200, Created = 201, NoContent = 204, BadRequest = 400, NotFound = 404, MethodNotAllowed = 405, InternalError = 500, } extend StatusCode { fn reason(self) const -> string { match self { case .Ok { return "OK" } case .Created { return "Created" } case .NoContent { return "No Content" } case .BadRequest { return "Bad Request" } case .NotFound { return "Not Found" } case .MethodNotAllowed { return "Method Not Allowed" } case .InternalError { return "Internal Server Error" } } } } pub class Response { pub var status: StatusCode priv var headers: {string: string} priv var body: string fn Response(self, status: StatusCode) { self.status = status self.headers = {} self.body = "" } pub fn header(self, name: string, value: string) -> Self { self.headers[name] = value return self } pub fn content_type(self, mime: string) -> Self { return self.header("Content-Type", mime) } pub fn text(self, content: string) -> Self { self.body = content return self.content_type("text/plain") } pub fn json(self, content: string) -> Self { self.body = content return self.content_type("application/json") } pub fn serialize(const self) -> string { var line = f"HTTP/1.1 {self.status as u16} {self.status.reason()}\r\n" for (name, value) in self.headers { line = line + f"{name}: {value}\r\n" } line = line + f"Content-Length: {self.body.length()}\r\n" line = line + "\r\n" line = line + self.body return line } pub static fn ok() -> Response { return Response(StatusCode::Ok) } pub static fn not_found() -> Response { return Response(StatusCode::NotFound) .text("404 Not Found") } pub static fn bad_request(msg: string) -> Response { return Response(StatusCode::BadRequest) .text(msg) } pub static fn internal_error() -> Response { return Response(StatusCode::InternalError) .text("Internal Server Error") } } ``` Key patterns: - `StatusCode` is an enum with an explicit `u16` underlying type - `Response` uses method chaining (each builder method returns `Self`) - Static factory methods (`Response::ok()`, `Response::not_found()`) for common responses - `serialize` is a `const` method it does not modify the response --- ## Router `router.k` maps method + path combinations to handler functions. ```kairo // router.k pub type Handler = fn(*Request) panic -> Response struct Route { var method: Method var path: string var handler: Handler } pub class Router { priv var routes: [Route] priv var not_found_handler: Handler fn Router(self) { self.routes = [] self.not_found_handler = fn (req: *Request) panic -> Response { return Response::not_found() } } pub fn get(self, path: string, handler: Handler) -> Self { self.routes.push(Route { method: .Get, path: path, handler: handler }) return self } pub fn post(self, path: string, handler: Handler) -> Self { self.routes.push(Route { method: .Post, path: path, handler: handler }) return self } pub fn put(self, path: string, handler: Handler) -> Self { self.routes.push(Route { method: .Put, path: path, handler: handler }) return self } pub fn delete(self, path: string, handler: Handler) -> Self { self.routes.push(Route { method: .Delete, path: path, handler: handler }) return self } pub fn fallback(self, handler: Handler) -> Self { self.not_found_handler = handler return self } pub fn resolve(const self, req: *Request) -> Handler { for route in self.routes { if route.method == req->method && route.path == req->path { return route.handler } } return self.not_found_handler } } ``` Key patterns: - `Handler` is a type alias for a function pointer that takes a request and may panic - `Route` is a struct plain data with aggregate init - `Router` uses method chaining for registration - `resolve` is `const` routing does not mutate the router --- ## Server `server.k` ties everything together with a TCP accept loop. It uses C++ interop for socket operations. ```kairo // server.k ffi "c++" import "socket_api.hh" as net pub class Server { priv var router: Router priv var port: i32 priv var running: bool fn Server(self, port: i32) { self.router = Router() self.port = port self.running = false } pub fn routes(self) -> *Router { return &self.router } pub fn start(self) panic { var listener = net::tcp_listen(self.port) if listener < 0 { panic std::Error::IO(f"failed to bind to port {self.port}") } finally { net::tcp_close(listener) } std::println(f"listening on :{self.port}") self.running = true while self.running { var client = net::tcp_accept(listener) if client < 0 { continue } self.handle_client(client) } } pub fn stop(self) { self.running = false } priv fn handle_client(self, fd: i32) { finally { net::tcp_close(fd) } var raw = net::tcp_read(fd, 8192) var response = try { var req = parse_request(raw) var handler = self.router.resolve(&req) try { handler(&req) } catch { Response::internal_error() } } catch e: ParseError { Response::bad_request(e.message) } catch { Response::internal_error() } net::tcp_write(fd, response.serialize()) } } ``` Key patterns: - `ffi "c++" import` brings in socket operations no unsafe block needed because `tcp_listen`, `tcp_accept`, etc. take and return value types (`i32`, `string`), not raw pointers - `finally` blocks ensure sockets are closed on all exit paths (normal, panic, early return) - `try`/`catch` is used in expression form to produce a `Response` on both success and failure - Nested `try`/`catch` the outer one handles parse errors, the inner one handles handler panics - `handle_client` is `priv` internal implementation detail --- ## Putting It Together `main.k` wires up routes and starts the server. ```kairo // main.k fn handle_index(req: *Request) panic -> Response { return Response::ok() .json("{\"status\": \"running\"}") } fn handle_health(req: *Request) panic -> Response { return Response::ok() .text("ok") } fn handle_echo(req: *Request) panic -> Response { if req->method != .Post { return Response(StatusCode::MethodNotAllowed) .text("POST only") } return Response::ok() .content_type("text/plain") .text(req->body) } fn main() { var srv = Server(8080) srv.routes() ->get("/", handle_index) ->get("/health", handle_health) ->post("/echo", handle_echo) ->fallback(fn (req: *Request) panic -> Response { return Response::not_found() }) try { srv.start() } catch e: std::Error::IO { std::println(f"server error: {e}") } catch { std::println("unexpected error") } } ``` Key patterns: - Handlers are plain functions passed as function pointers - The fallback handler is an inline closure - `main` handles all panics from `srv.start()` no unhandled errors escape --- ## Building and Running ```sh kairo main.k -o server ./server ``` ``` listening on :8080 ``` ```sh curl http://localhost:8080/ # {"status": "running"} curl http://localhost:8080/health # ok curl -X POST -d "hello" http://localhost:8080/echo # hello curl http://localhost:8080/nonexistent # 404 Not Found ``` --- ## Features Used | Feature | Where | |---|---| | [Classes](/docs/language/classes) | `Request`, `Response`, `Server`, `Router` | | [Structs](/docs/language/structures) | `Header`, `Route` | | [Enums](/docs/language/enums) | `Method`, `StatusCode` | | [Extends](/docs/language/extends) | `Method::from_string`, `StatusCode::reason` | | [Interfaces](/docs/language/interfaces) | `Handler` type alias for function pointers | | [Panic](/docs/language/panic) | Error handling throughout | | [Try/Catch](/docs/language/control-flow#match) | Request parsing, handler execution | | [Finally](/docs/language/control-flow#finally) | Socket cleanup | | [Closures](/docs/language/closures) | Fallback handler | | [Nullable types](/docs/language/variables#nullable-types) | `get_header` returns `string?` | | [Null coalescing](/docs/language/variables#null-coalescing-) | `content_length` uses `??` | | [Type aliases](/docs/language/type-system#type-aliases) | `Handler` type | | [C++ interop](/docs/language/c-c++) | Socket API via `ffi "c++"` | | [Match](/docs/language/control-flow#match) | Method parsing, status code reasons | | [Modules](/docs/language/modules) | File-per-module structure | | [Const methods](/docs/language/classes#const-methods) | `serialize`, `resolve`, `get_header` | | [Method chaining](/docs/language/classes#self-and-self) | Response builder, router registration | --- ======================================================================== SECTION: LIBRARY ======================================================================== ## reference URL: https://www.kairolang.org/docs/library/Core/functions/move_refrence/ # `mref!(T)` Converts a specified type to a move reference type. ## Signature ```kairo mref!(T) -> &&T ``` --- ## reference URL: https://www.kairolang.org/docs/library/Core/functions/refrence/ # `ref!(T)` Converts a specified type to a reference type. ## Signature ```kairo ref!(T) -> &T ``` --- ## libcxx Namespace URL: https://www.kairolang.org/docs/library/Core/libcxx/ # `libcxx` The `libcxx` namespace encompasses the C and C++ standard library headers for use in the current version of Kairo. ## C Standard Library Headers ### Cross-Platform C Headers - [``](https://en.cppreference.com/w/c/io) - File control options - [``](https://en.cppreference.com/w/c/memory/malloc) - Memory allocation (non-standard) - [``](https://en.cppreference.com/w/c/string/byte) - Memory operations (non-standard) - [``](https://en.cppreference.com/w/c/io) - File status - [``](https://en.cppreference.com/w/c/chrono) - Time utilities - [``](https://en.cppreference.com/w/c/types) - System types - [``](https://en.cppreference.com/w/c/chrono) - File time modification ### Windows-Specific C Headers - `` - Console I/O (Microsoft-specific) - `` - Core runtime (Microsoft-specific) - `` - Core runtime I/O (Microsoft-specific) - `` - Core runtime memory allocation (Microsoft-specific) - `` - Core runtime math (Microsoft-specific) - `` - Core runtime math defines (Microsoft-specific) - `` - Core runtime secure memory copy (Microsoft-specific) - `` - Core runtime memory (Microsoft-specific) - `` - Core runtime search (Microsoft-specific) - `` - Core runtime sharing (Microsoft-specific) - `` - Core runtime startup (Microsoft-specific) - `` - Core runtime stdio config (Microsoft-specific) - `` - Core runtime termination (Microsoft-specific) - `` - Core runtime wide console I/O (Microsoft-specific) - `` - Core runtime wide character types (Microsoft-specific) - `` - Core runtime wide directory (Microsoft-specific) - `` - Core runtime wide I/O (Microsoft-specific) - `` - Core runtime wide process (Microsoft-specific) - `` - Core runtime wide stdio (Microsoft-specific) - `` - Core runtime wide stdlib (Microsoft-specific) - `` - Core runtime wide string (Microsoft-specific) - `` - Core runtime wide time (Microsoft-specific) - `` - Debug heap (Microsoft-specific) - `` - Directory control (Microsoft-specific) - `` - DOS interface (Microsoft-specific) - `` - IEEE floating point (Microsoft-specific) - `` - Low-level I/O (Microsoft-specific) - `` - Multibyte character types (Microsoft-specific) - `` - Multibyte string (Microsoft-specific) - `` - Min/max macros (Microsoft-specific) - `` - New operator (Microsoft-specific) - `` - Process control (Microsoft-specific) - `` - Safe integer operations (Microsoft-specific) - `` - Safe integer internals (Microsoft-specific) - `` - File sharing (Microsoft-specific) - `` - File locking (Microsoft-specific) - `` - Generic text mappings (Microsoft-specific) ### Unix-Specific C Headers - [``](https://en.cppreference.com/w/c/thread) - POSIX threads - [``](https://en.cppreference.com/w/c/io) - Memory management (POSIX) - [``](https://en.cppreference.com/w/c/program) - Resource usage (POSIX) - [``](https://en.cppreference.com/w/c/io) - POSIX operating system API ## C++ Standard Library Headers ### C++ Wrapper Headers for C Standard Library - [``](https://en.cppreference.com/w/cpp/header/cassert) - Conditionally compiled macro for assertion checking - [``](https://en.cppreference.com/w/cpp/header/cctype) - Functions to determine character types - [``](https://en.cppreference.com/w/cpp/header/cerrno) - Macro containing the last error number - [``](https://en.cppreference.com/w/cpp/header/cfenv) - Floating-point environment - [``](https://en.cppreference.com/w/cpp/header/cfloat) - Limits of float types - [``](https://en.cppreference.com/w/cpp/header/cinttypes) - Formatting macros, intmax_t and uintmax_t math - [``](https://en.cppreference.com/w/cpp/header/clocale) - Localization utilities - [``](https://en.cppreference.com/w/cpp/header/cmath) - Mathematical functions - [``](https://en.cppreference.com/w/cpp/header/csetjmp) - Macro for non-local jumps - [``](https://en.cppreference.com/w/cpp/header/csignal) - Functions and macro constants for signal management - [``](https://en.cppreference.com/w/cpp/header/cstdarg) - Handling of variable length argument lists - [``](https://en.cppreference.com/w/cpp/header/cstddef) - Standard type definitions - [``](https://en.cppreference.com/w/cpp/header/cstdio) - C-style input/output functions - [``](https://en.cppreference.com/w/cpp/header/cstdlib) - General purpose utilities - [``](https://en.cppreference.com/w/cpp/header/cstring) - Null-terminated byte string utilities - [``](https://en.cppreference.com/w/cpp/header/ctime) - C-style time utilities - [``](https://en.cppreference.com/w/cpp/header/cuchar) - Unicode character conversion facilities - [``](https://en.cppreference.com/w/cpp/header/cwchar) - Wide string utilities - [``](https://en.cppreference.com/w/cpp/header/cwctype) - Functions for determining wide character types ### Mathematical C++ Headers - [``](https://en.cppreference.com/w/cpp/header/ccomplex) - Complex number type - [``](https://en.cppreference.com/w/cpp/header/complex) - Complex number type - [``](https://en.cppreference.com/w/cpp/header/ctgmath) - Type-generic math functions - [``](https://en.cppreference.com/w/cpp/header/numeric) - Numeric operations on values in ranges - [``](https://en.cppreference.com/w/cpp/header/random) - Random number generation facilities - [``](https://en.cppreference.com/w/cpp/header/valarray) - Class for representing and manipulating arrays ### Algorithms and Utilities - [``](https://en.cppreference.com/w/cpp/header/algorithm) - Algorithms for ranges of elements - [``](https://en.cppreference.com/w/cpp/header/functional) - Function objects, function invocations, bind operations - [``](https://en.cppreference.com/w/cpp/header/iterator) - Iterator utilities - [``](https://en.cppreference.com/w/cpp/header/ranges) - Range access, primitives, requirements, utilities and adaptors - [``](https://en.cppreference.com/w/cpp/header/utility) - Utility components ### Containers - [``](https://en.cppreference.com/w/cpp/header/array) - Fixed-size sequence container - [``](https://en.cppreference.com/w/cpp/header/bitset) - Bit array - [``](https://en.cppreference.com/w/cpp/header/deque) - Double-ended queue - [``](https://en.cppreference.com/w/cpp/header/list) - Doubly-linked list - [``](https://en.cppreference.com/w/cpp/header/map) - Associative containers - [``](https://en.cppreference.com/w/cpp/header/queue) - Queue container adaptors - [``](https://en.cppreference.com/w/cpp/header/set) - Associative containers - [``](https://en.cppreference.com/w/cpp/header/stack) - Stack container adaptor - [``](https://en.cppreference.com/w/cpp/header/unordered_map) - Unordered associative containers - [``](https://en.cppreference.com/w/cpp/header/unordered_set) - Unordered associative containers - [``](https://en.cppreference.com/w/cpp/header/vector) - Dynamic array ### Strings and Text Processing - [``](https://en.cppreference.com/w/cpp/header/codecvt) - Unicode conversion facilities - [``](https://en.cppreference.com/w/cpp/header/locale) - Localization utilities - [``](https://en.cppreference.com/w/cpp/header/regex) - Regular expressions - [``](https://en.cppreference.com/w/cpp/header/string) - String types - [``](https://en.cppreference.com/w/cpp/header/string_view) - String view ### Input/Output - [``](https://en.cppreference.com/w/cpp/header/fstream) - File stream classes - [``](https://en.cppreference.com/w/cpp/header/iomanip) - Input/output manipulators - [``](https://en.cppreference.com/w/cpp/header/ios) - Input/output stream base classes - [``](https://en.cppreference.com/w/cpp/header/iosfwd) - Forward declarations of input/output streams - [``](https://en.cppreference.com/w/cpp/header/iostream) - Standard input/output streams - [``](https://en.cppreference.com/w/cpp/header/istream) - Input stream classes - [``](https://en.cppreference.com/w/cpp/header/ostream) - Output stream classes - [``](https://en.cppreference.com/w/cpp/header/sstream) - String stream classes - [``](https://en.cppreference.com/w/cpp/header/streambuf) - Stream buffer classes ### Memory Management - [``](https://en.cppreference.com/w/cpp/header/memory) - Higher level memory management utilities - [``](https://en.cppreference.com/w/cpp/header/scoped_allocator) - Nested allocator class ### Threading and Concurrency - [``](https://en.cppreference.com/w/cpp/header/atomic) - Atomic operations library - [``](https://en.cppreference.com/w/cpp/header/condition_variable) - Thread waiting conditions - [``](https://en.cppreference.com/w/cpp/header/future) - Asynchronous computations - [``](https://en.cppreference.com/w/cpp/header/mutex) - Mutual exclusion primitives - [``](https://en.cppreference.com/w/cpp/header/shared_mutex) - Shared mutual exclusion primitives - [``](https://en.cppreference.com/w/cpp/header/thread) - Thread support library ### Type Support and Metaprogramming - [``](https://en.cppreference.com/w/cpp/header/exception) - Exception handling utilities - [``](https://en.cppreference.com/w/cpp/header/limits) - Standardized way to query properties of fundamental types - [``](https://en.cppreference.com/w/cpp/header/stdexcept) - Standard exception objects - [``](https://en.cppreference.com/w/cpp/header/system_error) - System error support - [``](https://en.cppreference.com/w/cpp/header/tuple) - Tuple types - [``](https://en.cppreference.com/w/cpp/header/type_traits) - Compile-time type information - [``](https://en.cppreference.com/w/cpp/header/typeindex) - Wrapper around type_info objects - [``](https://en.cppreference.com/w/cpp/header/typeinfo) - Runtime type information utilities - [``](https://en.cppreference.com/w/cpp/header/variant) - Type-safe union ### C++20 and Later Features - [``](https://en.cppreference.com/w/cpp/header/chrono) - Time utilities - [``](https://en.cppreference.com/w/cpp/header/coroutine) - Coroutine support library - [``](https://en.cppreference.com/w/cpp/header/optional) - Optional objects - [``](https://en.cppreference.com/w/cpp/header/span) - Object that can refer to a contiguous sequence of objects ## Platform-Specific Extensions ### Unix/Linux Extensions - `` - C++ ABI support (GCC-specific) ### Windows Extensions - `` - Process and thread API (Windows-specific) - `` - Tool help functions (Windows-specific) - `` - Windows API (Windows-specific) --- ## std::abi::demangle URL: https://www.kairolang.org/docs/library/Core/std-abi/demangle/ # demangle Demangles a previously mangled name according to the specified object type ## Signature ```kairo demangle(input: const &string, ty: ObjectType) -> string? ``` --- ## std::abi::demangle_partial URL: https://www.kairolang.org/docs/library/Core/std-abi/demangle_partial/ # demangle_partial Demangles all mangled segments within a string, leaving non-mangled parts unchanged ## Signature ```kairo demangle_partial(input: const &string) -> string ``` --- ## std::abi::is_mangled URL: https://www.kairolang.org/docs/library/Core/std-abi/is_mangled/ # is_mangled Checks if a string is a mangled name and returns the detected object type ## Signature ```kairo is_mangled(input: const &string) -> ObjectType ``` --- ## std::abi::mangle URL: https://www.kairolang.org/docs/library/Core/std-abi/mangle/ # mangle Mangles a name according to the specified object type ## Signature ```kairo mangle(input: const &string, ty: ObjectType) -> string? ``` --- ## std::Error::BaseError URL: https://www.kairolang.org/docs/library/Core/std-error/baseerror/ # BaseError Base class for all Kairo errors --- ## std::Error::NullValueError URL: https://www.kairolang.org/docs/library/Core/std-error/nullvalueerror/ # NullValueError Error thrown when a null value is encountered where a valid value is expected --- ## std::Error::RuntimeError URL: https://www.kairolang.org/docs/library/Core/std-error/runtimeerror/ # RuntimeError Error thrown for general runtime errors --- ## std::Error::StateMismatchError URL: https://www.kairolang.org/docs/library/Core/std-error/statemismatcherror/ # StateMismatchError Error thrown when an object is in an incorrect state for the requested operation --- ## std::Error::TypeMismatchError URL: https://www.kairolang.org/docs/library/Core/std-error/typemismatcherror/ # TypeMismatchError Error thrown when a type mismatch occurs --- ## std::Legacy::new URL: https://www.kairolang.org/docs/library/Core/std-legacy/new/ # `new` Allocates memory for a type and calls its constructor with the specified arguments (equivalent to 'new T(args...)') ## Template Parameters - `T` ## Signature ```kairo fn fn new(...args) -> *T; ``` --- ## std::Memory::address_type URL: https://www.kairolang.org/docs/library/Core/std-memory/address_type/ # address_type Returns the type of memory address for a pointer ## Signature ```kairo address_type(ptr: *void) -> AddressType ``` --- ## std::Memory::as_pointer URL: https://www.kairolang.org/docs/library/Core/std-memory/as_pointer/ # `as_pointer` Converts a reference to a pointer type ## Template Parameters - `T` ## Signature ```kairo fn fn as_pointer(ref: &T) -> *T; ``` --- ## std::Memory::as_reference URL: https://www.kairolang.org/docs/library/Core/std-memory/as_reference/ # `as_reference` Converts a pointer to an lvalue reference type ## Template Parameters - `T` ## Signature ```kairo fn fn as_reference(ptr: *T) -> &T; ``` --- ## std::Memory::compare URL: https://www.kairolang.org/docs/library/Core/std-memory/compare/ # compare Compares two blocks of memory for equality ## Signature ```kairo compare(ptr1: *const void, ptr2: *const void, size: usize) -> i32 ``` --- ## std::Memory::copy URL: https://www.kairolang.org/docs/library/Core/std-memory/copy/ # copy Copies memory from source to destination ## Signature ```kairo copy(dest: *void, src: *const void, size: usize) -> *void ``` --- ## std::Memory::delete_aligned URL: https://www.kairolang.org/docs/library/Core/std-memory/delete_aligned/ # `delete_aligned` Deallocates memory for a type allocated with new_aligned ## Template Parameters - `T` ## Signature ```kairo fn fn delete_aligned(ptr: *T) -> void; ``` --- ## std::Memory::exchange URL: https://www.kairolang.org/docs/library/Core/std-memory/exchange/ # `exchange` Atomically exchanges the value at the pointer with the specified value, returns the old value ## Template Parameters - `T` ## Signature ```kairo fn fn exchange(ptr: *T, value: T) -> &T; ``` --- ## std::Memory::find URL: https://www.kairolang.org/docs/library/Core/std-memory/find/ # `find` Finds the first occurrence of a value in a block of memory ## Template Parameters - `T` ## Signature ```kairo fn fn find(ptr: *const void, value: i32, size: usize) -> *T?; ``` --- ## std::Memory::forward URL: https://www.kairolang.org/docs/library/Core/std-memory/forward/ # `forward` Forwards a value as an rvalue reference, used for perfect forwarding in function templates ## Template Parameters - `T` ## Signature ```kairo fn fn forward(value: T) -> T; ``` --- ## std::Memory::heap_start URL: https://www.kairolang.org/docs/library/Core/std-memory/heap_start/ # heap_start Returns the start address of the heap memory region ## Signature ```kairo heap_start() -> *void ``` --- ## std::Memory::in_rotdata URL: https://www.kairolang.org/docs/library/Core/std-memory/in_rotdata/ # in_rotdata Checks if a pointer is in the ROTData memory region ## Signature ```kairo in_rotdata(ptr: *const void) -> bool ``` --- ## std::Memory::in_stack URL: https://www.kairolang.org/docs/library/Core/std-memory/in_stack/ # in_stack Checks if a pointer is in the stack memory region ## Signature ```kairo in_stack(ptr: *const void) -> bool ``` --- ## std::Memory::move URL: https://www.kairolang.org/docs/library/Core/std-memory/move/ # move Moves memory from source to destination ## Signature ```kairo move(dest: *void, src: *const void, size: usize) -> *void ``` --- ## std::Memory::new_aligned URL: https://www.kairolang.org/docs/library/Core/std-memory/new_aligned/ # `new_aligned` Allocates memory for a type with specified alignment (alignment of T) and arguments ## Template Parameters - `T` ## Signature ```kairo fn fn new_aligned(...args) -> *T; ``` --- ## std::Memory::set URL: https://www.kairolang.org/docs/library/Core/std-memory/set/ # set Sets a block of memory to a specified value ## Signature ```kairo set(dest: *void, value: i32, size: usize) -> *void ``` --- ## std::Memory::stack_bounds URL: https://www.kairolang.org/docs/library/Core/std-memory/stack_bounds/ # stack_bounds Returns the bounds of the stack memory region as a pair of pointers ## Signature ```kairo stack_bounds() -> libcxx::pair<*void, *void> ``` --- ## std::Memory::stack_size URL: https://www.kairolang.org/docs/library/Core/std-memory/stack_size/ # stack_size Returns the size of the stack memory region ## Signature ```kairo stack_size() -> usize ``` --- ## std::Memory::stack_start URL: https://www.kairolang.org/docs/library/Core/std-memory/stack_start/ # stack_start Returns the start address of the stack memory region ## Signature ```kairo stack_start() -> *void ``` --- ## std::Meta::all_extents_removed URL: https://www.kairolang.org/docs/library/Core/std-meta/all_extents_removed/ # `all_extents_removed` Removes all extents from an array type ## Signature ```kairo all_extents_removed -> type ``` ## Specifiers - `constexpr` --- ## std::Meta::as_const URL: https://www.kairolang.org/docs/library/Core/std-meta/as_const/ # `as_const` Converts a type to a const type ## Signature ```kairo as_const -> const T ``` ## Specifiers - `constexpr` --- ## std::Meta::as_const_volatile URL: https://www.kairolang.org/docs/library/Core/std-meta/as_const_volatile/ # `as_const_volatile` Converts a type to a const volatile type ## Signature ```kairo as_const_volatile -> const volatile T ``` ## Specifiers - `constexpr` --- ## std::Meta::as_cvref URL: https://www.kairolang.org/docs/library/Core/std-meta/as_cvref/ # `as_cvref` Converts a type to a const volatile reference type ## Signature ```kairo as_cvref -> const volatile &T ``` ## Specifiers - `constexpr` --- ## std::Meta::as_lvalue_reference URL: https://www.kairolang.org/docs/library/Core/std-meta/as_lvalue_reference/ # `as_lvalue_reference` Converts a type to an lvalue reference type ## Signature ```kairo as_lvalue_reference -> &T ``` ## Specifiers - `constexpr` --- ## std::Meta::as_rvalue_reference URL: https://www.kairolang.org/docs/library/Core/std-meta/as_rvalue_reference/ # `as_rvalue_reference` Converts a type to an rvalue reference type ## Signature ```kairo as_rvalue_reference -> &&T ``` ## Specifiers - `constexpr` --- ## std::Meta::const_removed URL: https://www.kairolang.org/docs/library/Core/std-meta/const_removed/ # `const_removed` Removes const qualifier from a type ## Signature ```kairo const_removed -> type ``` ## Specifiers - `constexpr` --- ## std::Meta::const_volatile_removed URL: https://www.kairolang.org/docs/library/Core/std-meta/const_volatile_removed/ # `const_volatile_removed` Removes both const and volatile qualifiers from a type ## Signature ```kairo const_volatile_removed -> type ``` ## Specifiers - `constexpr` --- ## std::Meta::cvref_removed URL: https://www.kairolang.org/docs/library/Core/std-meta/cvref_removed/ # `cvref_removed` Removes const, volatile, and reference qualifiers from a type ## Signature ```kairo cvref_removed -> type ``` ## Specifiers - `constexpr` --- ## std::Meta::declval URL: https://www.kairolang.org/docs/library/Core/std-meta/declval/ # `declval` Provides a type that can be used to simulate an rvalue of the specified type ## Signature ```kairo declval -> &&T ``` ## Specifiers - `constexpr` --- ## std::Meta::enable_if URL: https://www.kairolang.org/docs/library/Core/std-meta/enable_if/ # `enable_if` Enables a type if the condition is true ## Signature ```kairo enable_if -> T | void ``` ## Specifiers - `constexpr` --- ## std::Meta::ref_as_ptr URL: https://www.kairolang.org/docs/library/Core/std-meta/ref_as_ptr/ # `ref_as_ptr` Converts a reference type to a pointer type ## Signature ```kairo ref_as_ptr<&T> -> *T ``` ## Specifiers - `constexpr` --- ## std::Meta::reference_removed URL: https://www.kairolang.org/docs/library/Core/std-meta/reference_removed/ # `reference_removed` Removes reference from a type ## Signature ```kairo reference_removed -> type ``` ## Specifiers - `constexpr` --- ## std::Panic::Frame::Frame (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-panic/frame/constructors/frame-1/ # `Frame` (Constructor) Creates a panic frame with object, filename, and line number ## Signature ```kairo fn Frame(obj: T, filename: const char*, lineno: usize) requires ; ``` ## See Also [Back to Frame](../frame) --- ## std::Panic::Frame::Frame (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-panic/frame/constructors/frame-2/ # `Frame` (Constructor) Creates a panic frame with object, filename as string, and line number ## Signature ```kairo fn Frame(obj: T, filename: string, lineno: usize) requires ; ``` ## See Also [Back to Frame](../frame) --- ## std::Panic::Frame::Frame (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-panic/frame/constructors/frame-3/ # `Frame` (Constructor) Creates a panic frame from another frame (noreturn) ## Signature ```kairo Frame(Frame& obj, const string& filename, usize lineno) ``` ## Specifiers - `noreturn` ## See Also [Back to Frame](../frame) --- ## std::Panic::Frame::Frame (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-panic/frame/constructors/frame-4/ # `Frame` (Constructor) Creates a panic frame from another frame (move, noreturn) ## Signature ```kairo Frame(Frame&& obj, const string& filename, usize lineno) ``` ## Specifiers - `noreturn` ## See Also [Back to Frame](../frame) --- ## std::Panic::Frame URL: https://www.kairolang.org/docs/library/Core/std-panic/frame/ # Frame Represents a panic frame with error context and location information ## Constructors ### [Frame](./frame/constructors/frame-1) Creates a panic frame with object, filename, and line number ```kairo fn Frame(obj: T, filename: const char*, lineno: usize) requires ; ``` ### [Frame](./frame/constructors/frame-2) Creates a panic frame with object, filename as string, and line number ```kairo fn Frame(obj: T, filename: string, lineno: usize) requires ; ``` ### [Frame](./frame/constructors/frame-3) Creates a panic frame from another frame (noreturn) ```kairo Frame(Frame& obj, const string& filename, usize lineno) ``` ### [Frame](./frame/constructors/frame-4) Creates a panic frame from another frame (move, noreturn) ```kairo Frame(Frame&& obj, const string& filename, usize lineno) ``` ## Operators ### [operator panic](./frame/operators/operator-panic-1) Panic operator used in kairo code as 'panic ...' ```kairo op panic(self) -> void ``` ## Methods ### [file](./frame/methods/file-1) Returns the filename where the panic occurred ```kairo file(self) -> string ``` ### [line](./frame/methods/line-2) Returns the line number where the panic occurred ```kairo line(self) -> usize ``` ### [reason](./frame/methods/reason-3) Returns the reason for the panic ```kairo reason(self) -> string ``` ### [get_context](./frame/methods/get_context-4) Returns the panic context ```kairo get_context(self) -> *FrameContext ``` --- ## std::Panic::Frame::file URL: https://www.kairolang.org/docs/library/Core/std-panic/frame/methods/file-1/ # `file` Returns the filename where the panic occurred ## Signature ```kairo file(self) -> string ``` ## Specifiers - `const` ## Attributes - `nodiscard` ## See Also [Back to `Frame`](../frame) --- ## std::Panic::Frame::get_context URL: https://www.kairolang.org/docs/library/Core/std-panic/frame/methods/get_context-4/ # `get_context` Returns the panic context ## Signature ```kairo get_context(self) -> *FrameContext ``` ## Specifiers - `const` ## Attributes - `nodiscard` ## See Also [Back to `Frame`](../frame) --- ## std::Panic::Frame::line URL: https://www.kairolang.org/docs/library/Core/std-panic/frame/methods/line-2/ # `line` Returns the line number where the panic occurred ## Signature ```kairo line(self) -> usize ``` ## Specifiers - `const` ## Attributes - `nodiscard` ## See Also [Back to `Frame`](../frame) --- ## std::Panic::Frame::reason URL: https://www.kairolang.org/docs/library/Core/std-panic/frame/methods/reason-3/ # `reason` Returns the reason for the panic ## Signature ```kairo reason(self) -> string ``` ## Specifiers - `const` ## Attributes - `nodiscard` ## See Also [Back to `Frame`](../frame) --- ## std::Panic::Frame::operator panic URL: https://www.kairolang.org/docs/library/Core/std-panic/frame/operators/operator-panic-1/ # `operator panic` Panic operator used in kairo code as 'panic ...' ## Signature ```kairo op panic(self) -> void ``` ## Specifiers - `noreturn` ## See Also [Back to Frame](../frame) --- ## std::Panic::FrameContext URL: https://www.kairolang.org/docs/library/Core/std-panic/framecontext/ # FrameContext Provides context for a panic frame ## Methods ### [crash](./framecontext/methods/crash-1) Terminates the program with the panic context ```kairo crash(self) -> void ``` ### [object](./framecontext/methods/object-2) Returns a pointer to the object that caused the panic ```kairo object(self) -> *void ``` ### [type_name](./framecontext/methods/type_name-3) Returns the type name of the object that caused the panic ```kairo type_name(self) -> string ``` ### [operator==](./framecontext/methods/operator-4) Equality comparison with type_info ```kairo op ==(self, const libcxx::type_info& other) -> bool ``` --- ## std::Panic::FrameContext::crash URL: https://www.kairolang.org/docs/library/Core/std-panic/framecontext/methods/crash-1/ # `crash` Terminates the program with the panic context ## Signature ```kairo crash(self) -> void ``` ## Specifiers - `noreturn` ## See Also [Back to `FrameContext`](../framecontext) --- ## std::Panic::FrameContext::object URL: https://www.kairolang.org/docs/library/Core/std-panic/framecontext/methods/object-2/ # `object` Returns a pointer to the object that caused the panic ## Signature ```kairo object(self) -> *void ``` ## See Also [Back to `FrameContext`](../framecontext) --- ## std::Panic::FrameContext::operator== URL: https://www.kairolang.org/docs/library/Core/std-panic/framecontext/methods/operator-4/ # `operator==` Equality comparison with type_info ## Signature ```kairo op ==(self, const libcxx::type_info& other) -> bool ``` ## See Also [Back to `FrameContext`](../framecontext) --- ## std::Panic::FrameContext::type_name URL: https://www.kairolang.org/docs/library/Core/std-panic/framecontext/methods/type_name-3/ # `type_name` Returns the type name of the object that caused the panic ## Signature ```kairo type_name(self) -> string ``` ## See Also [Back to `FrameContext`](../framecontext) --- ## std::String::basic::basic (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/basic/constructors/basic-1/ # `basic` (Constructor) Default constructor ## Signature ```kairo basic() noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::basic (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/basic/constructors/basic-2/ # `basic` (Constructor) Copy constructor ## Signature ```kairo basic(const basic& other) noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::basic (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/basic/constructors/basic-3/ # `basic` (Constructor) Move constructor ## Signature ```kairo basic(basic&& other) noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::basic (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/basic/constructors/basic-4/ # `basic` (Constructor) Constructor from C++ string ## Signature ```kairo basic(const libcxx::basic_string& str) noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::basic (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/basic/constructors/basic-5/ # `basic` (Constructor) Constructor from C-style string ## Signature ```kairo basic(const CharT* str) noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::basic (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/basic/constructors/basic-6/ # `basic` (Constructor) Constructor with character repeated count times ## Signature ```kairo basic(const CharT chr, size_t count) noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::basic (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/basic/constructors/basic-7/ # `basic` (Constructor) Constructor from C-style string with length ## Signature ```kairo basic(const CharT* str, size_t len) noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::basic (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/basic/constructors/basic-8/ # `basic` (Constructor) Constructor from string slice ## Signature ```kairo basic(const slice_t& s) noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `basic`](../basic) --- ## std::String::basic URL: https://www.kairolang.org/docs/library/Core/std-string/basic/ # `basic` Represents a basic string type with specified character type and traits, similar to std::basic_string in C++ ## Template Parameters - `Char` - `Traits` ## Type Aliases - `char_traits = Traits` - `char_t = CharT` - `size_t = usize` - `string_t = libcxx::basic_string` - `slice_t = slice` - `slice_vec = vec` - `char_vec = vec` ## Constructors ### [`basic`](./basic/constructors/basic-1) Default constructor ```kairo basic() noexcept ``` ### [`basic`](./basic/constructors/basic-2) Copy constructor ```kairo basic(const basic& other) noexcept ``` ### [`basic`](./basic/constructors/basic-3) Move constructor ```kairo basic(basic&& other) noexcept ``` ### [`basic`](./basic/constructors/basic-4) Constructor from C++ string ```kairo basic(const libcxx::basic_string& str) noexcept ``` ### [`basic`](./basic/constructors/basic-5) Constructor from C-style string ```kairo basic(const CharT* str) noexcept ``` ### [`basic`](./basic/constructors/basic-6) Constructor with character repeated count times ```kairo basic(const CharT chr, size_t count) noexcept ``` ### [`basic`](./basic/constructors/basic-7) Constructor from C-style string with length ```kairo basic(const CharT* str, size_t len) noexcept ``` ### [`basic`](./basic/constructors/basic-8) Constructor from string slice ```kairo basic(const slice_t& s) noexcept ``` ## Operators ### [`operator=`](./basic/operators/operator-1) Copy assignment operator ```kairo op =(self, const basic& other) -> &basic ``` ### [`operator=`](./basic/operators/operator-2) Move assignment operator ```kairo op =(self, basic&& other) -> &basic ``` ### [`operator=`](./basic/operators/operator-3) Assignment from C-style string ```kairo op =(self, const CharT* str) -> &basic ``` ### [`operator[]`](./basic/operators/operator-4) Access character at index (mutable) ```kairo op [``](self, index: size_t) -> &CharT ``` ### [`operator[]`](./basic/operators/operator-5) Access character at index (const) ```kairo op [``](self, index: size_t) -> const &CharT ``` ### [`operator+=`](./basic/operators/operator-6) Append another string ```kairo op +=(self, const basic& other) -> &basic ``` ### [`operator+=`](./basic/operators/operator-7) Append C-style string ```kairo op +=(self, const CharT* str) -> &basic ``` ### [`operator+=`](./basic/operators/operator-8) Append character ```kairo op +=(self, const U chr) -> &basic ``` ### [`operator+`](./basic/operators/operator-9) Concatenate with another string ```kairo op +(self, const basic& other) -> basic ``` ### [`operator+`](./basic/operators/operator-10) Concatenate with C-style string ```kairo op +(self, const CharT* str) -> basic ``` ### [`operator+`](./basic/operators/operator-11) Concatenate with character ```kairo op +(self, const U chr) -> basic ``` ### [`operator==`](./basic/operators/operator-12) Equality comparison ```kairo op ==(self, const basic& other) -> bool ``` ### [`operator!=`](./basic/operators/operator-13) Inequality comparison ```kairo op !=(self, const basic& other) -> bool ``` ### [`operator<`](./basic/operators/operator-14) Less than comparison ```kairo op <(self, const basic& other) -> bool ``` ### [`operator>`](./basic/operators/operator-15) Greater than comparison ```kairo op >(self, const basic& other) -> bool ``` ### [`operator<=`](./basic/operators/operator-16) Less than or equal comparison ```kairo op <=(self, const basic& other) -> bool ``` ### [`operator>=`](./basic/operators/operator-17) Greater than or equal comparison ```kairo op >=(self, const basic& other) -> bool ``` ### [`operator slice_t`](./basic/operators/operator-slice_t-18) Conversion to slice ```kairo op slice_t(self) -> slice_t ``` ### [`operator as`](./basic/operators/operator-as-19) Cast to slice ```kairo op as(self, const slice_t*) -> slice_t ``` ### [`operator as`](./basic/operators/operator-as-20) Cast to C-style string ```kairo op as(self, const char_t*) -> const char_t* ``` ### [`operator in`](./basic/operators/operator-in-21) Contains operator for slice ```kairo op in(self, needle: slice) -> bool ``` ### [`operator in`](./basic/operators/operator-in-22) Contains operator for character ```kairo op in(self, chr: CharT) -> bool ``` ## Methods ### [`push_back`](./basic/methods/push_back-1) Appends a character to the end of the string ```kairo push_back(self, c: CharT) -> void ``` ### [`append`](./basic/methods/append-2) Appends another string ```kairo append(self, const basic& other) -> void ``` ### [`append`](./basic/methods/append-3) Appends C-style string with length ```kairo append(self, const CharT* str, len: size_t) -> void ``` ### [`append`](./basic/methods/append-4) Appends string slice ```kairo append(self, const slice_t& s) -> void ``` ### [`clear`](./basic/methods/clear-5) Clears the string content ```kairo clear(self) -> void ``` ### [`replace`](./basic/methods/replace-6) Replaces portion of string with another slice ```kairo replace(self, pos: size_t, len: size_t, const slice_t& other) -> void ``` ### [`reserve`](./basic/methods/reserve-7) Reserves capacity for the string ```kairo reserve(self, new_cap: size_t) -> void ``` ### [`resize`](./basic/methods/resize-8) Resizes the string to new size ```kairo resize(self, new_size: size_t, c: CharT = CharT()) -> void ``` ### [`empty`](./basic/methods/empty-9) Checks if string is empty ```kairo empty(self) -> bool ``` ### [`raw`](./basic/methods/raw-10) Returns raw C-style string pointer ```kairo raw(self) -> const CharT* ``` ### [`size`](./basic/methods/size-11) Returns the size of the string ```kairo size(self) -> size_t ``` ### [`length`](./basic/methods/length-12) Returns the length of the string ```kairo length(self) -> size_t ``` ### [`is_empty`](./basic/methods/is_empty-13) Checks if string is empty ```kairo is_empty(self) -> bool ``` ### [`raw_string`](./basic/methods/raw_string-14) Returns reference to underlying C++ string ```kairo raw_string(self) -> &string_t ``` ### [`subslice`](./basic/methods/subslice-15) Returns a substring ```kairo subslice(self, pos: size_t, len: size_t) -> basic ``` ### [`l_strip`](./basic/methods/l_strip-16) Strips whitespace from the left ```kairo l_strip(self, const char_vec& delim = {' ', '\t', '\n', '\r'}) -> basic ``` ### [`r_strip`](./basic/methods/r_strip-17) Strips whitespace from the right ```kairo r_strip(self, const char_vec& delim = {' ', '\t', '\n', '\r'}) -> basic ``` ### [`strip`](./basic/methods/strip-18) Strips whitespace from both ends ```kairo strip(self, const char_vec& delim = {' ', '\t', '\n', '\r'}) -> basic ``` ### [`split`](./basic/methods/split-19) Splits string by delimiter ```kairo split(self, const basic& delim, op: slice_t::Operation = slice_t::Operation::Remove) -> vec ``` ### [`split_lines`](./basic/methods/split_lines-20) Splits string by newlines ```kairo split_lines(self) -> vec ``` ### [`starts_with`](./basic/methods/starts_with-21) Checks if string starts with needle ```kairo starts_with(self, const basic& needle) -> bool ``` ### [`starts_with`](./basic/methods/starts_with-22) Checks if string starts with character ```kairo starts_with(self, c: CharT) -> bool ``` ### [`starts_with`](./basic/methods/starts_with-23) Checks if string starts with slice ```kairo starts_with(self, needle: slice) -> bool ``` ### [`ends_with`](./basic/methods/ends_with-24) Checks if string ends with needle ```kairo ends_with(self, const basic& needle) -> bool ``` ### [`ends_with`](./basic/methods/ends_with-25) Checks if string ends with character ```kairo ends_with(self, c: CharT) -> bool ``` ### [`ends_with`](./basic/methods/ends_with-26) Checks if string ends with slice ```kairo ends_with(self, needle: slice) -> bool ``` ### [`contains`](./basic/methods/contains-27) Checks if string contains needle ```kairo contains(self, const basic& needle) -> bool ``` ### [`contains`](./basic/methods/contains-28) Checks if string contains character ```kairo contains(self, c: CharT) -> bool ``` ### [`lfind`](./basic/methods/lfind-29) Finds first occurrence of needle ```kairo lfind(self, needle: slice) -> usize? ``` ### [`rfind`](./basic/methods/rfind-30) Finds last occurrence of needle ```kairo rfind(self, needle: slice) -> usize? ``` ### [`find_first_of`](./basic/methods/find_first_of-31) Finds first occurrence of any character in needle ```kairo find_first_of(self, needle: slice) -> usize? ``` ### [`find_last_of`](./basic/methods/find_last_of-32) Finds last occurrence of any character in needle ```kairo find_last_of(self, needle: slice) -> usize? ``` ### [`find_first_not_of`](./basic/methods/find_first_not_of-33) Finds first character not in needle ```kairo find_first_not_of(self, needle: slice) -> usize? ``` ### [`find_last_not_of`](./basic/methods/find_last_not_of-34) Finds last character not in needle ```kairo find_last_not_of(self, needle: slice) -> usize? ``` --- ## std::String::basic::append URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/append-2/ # `append` Appends another string ## Signature ```kairo append(self, const basic& other) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::append URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/append-3/ # `append` Appends C-style string with length ## Signature ```kairo append(self, const CharT* str, len: size_t) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::append URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/append-4/ # `append` Appends string slice ## Signature ```kairo append(self, const slice_t& s) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::clear URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/clear-5/ # `clear` Clears the string content ## Signature ```kairo clear(self) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::contains URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/contains-27/ # `contains` Checks if string contains needle ## Signature ```kairo contains(self, const basic& needle) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::contains URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/contains-28/ # `contains` Checks if string contains character ## Signature ```kairo contains(self, c: CharT) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::empty URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/empty-9/ # `empty` Checks if string is empty ## Signature ```kairo empty(self) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::ends_with URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/ends_with-24/ # `ends_with` Checks if string ends with needle ## Signature ```kairo ends_with(self, const basic& needle) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::ends_with URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/ends_with-25/ # `ends_with` Checks if string ends with character ## Signature ```kairo ends_with(self, c: CharT) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::ends_with URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/ends_with-26/ # `ends_with` Checks if string ends with slice ## Signature ```kairo ends_with(self, needle: slice) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::find_first_not_of URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/find_first_not_of-33/ # `find_first_not_of` Finds first character not in needle ## Signature ```kairo find_first_not_of(self, needle: slice) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::find_first_of URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/find_first_of-31/ # `find_first_of` Finds first occurrence of any character in needle ## Signature ```kairo find_first_of(self, needle: slice) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::find_last_not_of URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/find_last_not_of-34/ # `find_last_not_of` Finds last character not in needle ## Signature ```kairo find_last_not_of(self, needle: slice) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::find_last_of URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/find_last_of-32/ # `find_last_of` Finds last occurrence of any character in needle ## Signature ```kairo find_last_of(self, needle: slice) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::is_empty URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/is_empty-13/ # `is_empty` Checks if string is empty ## Signature ```kairo is_empty(self) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::l_strip URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/l_strip-16/ # `l_strip` Strips whitespace from the left ## Signature ```kairo l_strip(self, const char_vec& delim = {' ', '\t', '\n', '\r'}) -> basic ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::length URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/length-12/ # `length` Returns the length of the string ## Signature ```kairo length(self) -> size_t ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::lfind URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/lfind-29/ # `lfind` Finds first occurrence of needle ## Signature ```kairo lfind(self, needle: slice) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::push_back URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/push_back-1/ # `push_back` Appends a character to the end of the string ## Signature ```kairo push_back(self, c: CharT) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::r_strip URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/r_strip-17/ # `r_strip` Strips whitespace from the right ## Signature ```kairo r_strip(self, const char_vec& delim = {' ', '\t', '\n', '\r'}) -> basic ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::raw URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/raw-10/ # `raw` Returns raw C-style string pointer ## Signature ```kairo raw(self) -> const CharT* ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::raw_string URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/raw_string-14/ # `raw_string` Returns reference to underlying C++ string ## Signature ```kairo raw_string(self) -> &string_t ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::replace URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/replace-6/ # `replace` Replaces portion of string with another slice ## Signature ```kairo replace(self, pos: size_t, len: size_t, const slice_t& other) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::reserve URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/reserve-7/ # `reserve` Reserves capacity for the string ## Signature ```kairo reserve(self, new_cap: size_t) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::resize URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/resize-8/ # `resize` Resizes the string to new size ## Signature ```kairo resize(self, new_size: size_t, c: CharT = CharT()) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::rfind URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/rfind-30/ # `rfind` Finds last occurrence of needle ## Signature ```kairo rfind(self, needle: slice) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::size URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/size-11/ # `size` Returns the size of the string ## Signature ```kairo size(self) -> size_t ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::split URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/split-19/ # `split` Splits string by delimiter ## Signature ```kairo split(self, const basic& delim, op: slice_t::Operation = slice_t::Operation::Remove) -> vec ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::split_lines URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/split_lines-20/ # `split_lines` Splits string by newlines ## Signature ```kairo split_lines(self) -> vec ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::starts_with URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/starts_with-21/ # `starts_with` Checks if string starts with needle ## Signature ```kairo starts_with(self, const basic& needle) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::starts_with URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/starts_with-22/ # `starts_with` Checks if string starts with character ## Signature ```kairo starts_with(self, c: CharT) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::starts_with URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/starts_with-23/ # `starts_with` Checks if string starts with slice ## Signature ```kairo starts_with(self, needle: slice) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::strip URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/strip-18/ # `strip` Strips whitespace from both ends ## Signature ```kairo strip(self, const char_vec& delim = {' ', '\t', '\n', '\r'}) -> basic ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::subslice URL: https://www.kairolang.org/docs/library/Core/std-string/basic/methods/subslice-15/ # `subslice` Returns a substring ## Signature ```kairo subslice(self, pos: size_t, len: size_t) -> basic ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator= URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-1/ # `operator=` Copy assignment operator ## Signature ```kairo op =(self, const basic& other) -> &basic ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator+ URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-10/ # `operator+` Concatenate with C-style string ## Signature ```kairo op +(self, const CharT* str) -> basic ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator+ URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-11/ # `operator+` Concatenate with character ## Signature ```kairo op +(self, const U chr) -> basic ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator== URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-12/ # `operator==` Equality comparison ## Signature ```kairo op ==(self, const basic& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator!= URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-13/ # `operator!=` Inequality comparison ## Signature ```kairo op !=(self, const basic& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator< URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-14/ # `operator<` Less than comparison ## Signature ```kairo op <(self, const basic& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator> URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-15/ # `operator>` Greater than comparison ## Signature ```kairo op >(self, const basic& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator<= URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-16/ # `operator<=` Less than or equal comparison ## Signature ```kairo op <=(self, const basic& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator>= URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-17/ # `operator>=` Greater than or equal comparison ## Signature ```kairo op >=(self, const basic& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator= URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-2/ # `operator=` Move assignment operator ## Signature ```kairo op =(self, basic&& other) -> &basic ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator= URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-3/ # `operator=` Assignment from C-style string ## Signature ```kairo op =(self, const CharT* str) -> &basic ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator[] URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-4/ # `operator[]` Access character at index (mutable) ## Signature ```kairo op [](self, index: size_t) -> &CharT ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator[] URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-5/ # `operator[]` Access character at index (const) ## Signature ```kairo op [](self, index: size_t) -> const &CharT ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator+= URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-6/ # `operator+=` Append another string ## Signature ```kairo op +=(self, const basic& other) -> &basic ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator+= URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-7/ # `operator+=` Append C-style string ## Signature ```kairo op +=(self, const CharT* str) -> &basic ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator+= URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-8/ # `operator+=` Append character ## Signature ```kairo op +=(self, const U chr) -> &basic ``` ## Specifiers - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator+ URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-9/ # `operator+` Concatenate with another string ## Signature ```kairo op +(self, const basic& other) -> basic ``` ## Specifiers - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator as URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-as-19/ # `operator as` Cast to slice ## Signature ```kairo op as(self, const slice_t*) -> slice_t ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator as URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-as-20/ # `operator as` Cast to C-style string ## Signature ```kairo op as(self, const char_t*) -> const char_t* ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator in URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-in-21/ # `operator in` Contains operator for slice ## Signature ```kairo op in(self, needle: slice) -> bool ``` ## Specifiers - `constexpr` - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator in URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-in-22/ # `operator in` Contains operator for character ## Signature ```kairo op in(self, chr: CharT) -> bool ``` ## Specifiers - `constexpr` - `const` ## See Also [Back to `basic`](../basic) --- ## std::String::basic::operator slice_t URL: https://www.kairolang.org/docs/library/Core/std-string/basic/operators/operator-slice_t-18/ # `operator slice_t` Conversion to slice ## Signature ```kairo op slice_t(self) -> slice_t ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `basic`](../basic) --- ## std::String::compare URL: https://www.kairolang.org/docs/library/Core/std-string/compare/ # `compare` Compares two C-style strings lexicographically ## Template Parameters - `T` ## Signature ```kairo fn fn compare(a: const *T, b: const *T) -> i32; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::compare_locale URL: https://www.kairolang.org/docs/library/Core/std-string/compare_locale/ # `compare_locale` Compares two C-style strings according to the current locale ## Template Parameters - `T` ## Signature ```kairo fn fn compare_locale(a: const *T, b: const *T) -> i32; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::compare_n URL: https://www.kairolang.org/docs/library/Core/std-string/compare_n/ # `compare_n` Compares two C-style strings lexicographically up to n characters ## Template Parameters - `T` ## Signature ```kairo fn fn compare_n(a: const *T, b: const *T, n: usize) -> i32; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::concat URL: https://www.kairolang.org/docs/library/Core/std-string/concat/ # `concat` Concatenates two C-style strings ## Template Parameters - `T` ## Signature ```kairo fn fn concat(dest: *T, src: const *T) -> *T; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::concat_n URL: https://www.kairolang.org/docs/library/Core/std-string/concat_n/ # `concat_n` Concatenates two C-style strings with maximum count ## Template Parameters - `T` ## Signature ```kairo fn fn concat_n(dest: *T, src: const *T, n: usize) -> *T; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::copy URL: https://www.kairolang.org/docs/library/Core/std-string/copy/ # `copy` Copies a C-style string ## Template Parameters - `T` ## Signature ```kairo fn fn copy(dest: *T, src: const *T) -> *T; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::copy_n URL: https://www.kairolang.org/docs/library/Core/std-string/copy_n/ # `copy_n` Copies a C-style string with maximum count ## Template Parameters - `T` ## Signature ```kairo fn fn copy_n(dest: *T, src: const *T, n: usize) -> *T; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::error URL: https://www.kairolang.org/docs/library/Core/std-string/error/ # error Returns a string describing the error number ## Signature ```kairo error(errnum: i32) -> const char* ``` ## Specifiers - `inline` --- ## std::String::find URL: https://www.kairolang.org/docs/library/Core/std-string/find/ # `find` Finds the first occurrence of a character in a C-style string ## Template Parameters - `T` ## Signature ```kairo fn fn find(str: const *T, c: i32) -> *T?; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::find_any URL: https://www.kairolang.org/docs/library/Core/std-string/find_any/ # `find_any` Finds the first occurrence of any character from accept in str ## Template Parameters - `T` ## Signature ```kairo fn fn find_any(str: const *T, accept: const *T) -> *T?; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::find_last URL: https://www.kairolang.org/docs/library/Core/std-string/find_last/ # `find_last` Finds the last occurrence of a character in a C-style string ## Template Parameters - `T` ## Signature ```kairo fn fn find_last(str: const *T, c: i32) -> *T?; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::find_sub URL: https://www.kairolang.org/docs/library/Core/std-string/find_sub/ # `find_sub` Finds the first occurrence of needle in haystack ## Template Parameters - `T` ## Signature ```kairo fn fn find_sub(haystack: const *T, needle: const *T) -> *T?; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::length URL: https://www.kairolang.org/docs/library/Core/std-string/length/ # `length` Returns the length of a C-style string ## Template Parameters - `T` ## Signature ```kairo fn fn length(str: const *T) -> usize; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::prefix_length URL: https://www.kairolang.org/docs/library/Core/std-string/prefix_length/ # `prefix_length` Returns the length of the initial segment of str consisting of characters in chars ## Template Parameters - `T` ## Signature ```kairo fn fn prefix_length(str: const *T, chars: const *T, exclude: bool = false) -> usize; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::slice::slice (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/slice/constructors/slice-1/ # `slice` (Constructor) Default constructor ## Signature ```kairo slice() noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::slice (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/slice/constructors/slice-2/ # `slice` (Constructor) Copy constructor ## Signature ```kairo slice(const slice& other) noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::slice (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/slice/constructors/slice-3/ # `slice` (Constructor) Move constructor ## Signature ```kairo slice(slice&& other) noexcept ``` ## Specifiers - `constexpr` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::slice (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/slice/constructors/slice-4/ # `slice` (Constructor) Constructor from C-style string ## Signature ```kairo slice(const CharT* str) noexcept ``` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::slice (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/slice/constructors/slice-5/ # `slice` (Constructor) Constructor from C-style string with size ## Signature ```kairo slice(const CharT* str, size: size_t) noexcept ``` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::slice (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/slice/constructors/slice-6/ # `slice` (Constructor) Constructor from string view ## Signature ```kairo slice(view: view_t) noexcept ``` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::slice (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/slice/constructors/slice-7/ # `slice` (Constructor) Constructor from character vector ## Signature ```kairo slice(vec: char_vec&) noexcept ``` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::slice (Constructor) URL: https://www.kairolang.org/docs/library/Core/std-string/slice/constructors/slice-8/ # `slice` (Constructor) Constructor from character vector (move) ## Signature ```kairo slice(vec: char_vec&&) noexcept ``` ## See Also [Back to `slice`](../slice) --- ## std::String::slice URL: https://www.kairolang.org/docs/library/Core/std-string/slice/ # `slice` Represents a slice of a string with specified character type and traits, similar to std::string_view in C++ ## Template Parameters - `Char` - `Traits` ## Type Aliases - `view_t = libcxx::basic_string_view` - `char_traits = Traits` - `char_t = CharT` - `slice_t = slice` - `size_t = usize` - `slice_vec = vec` - `char_vec = vec` ## Enums ### Operation Operation type for string operations #### Values - **Keep**: Keep the delimiter in the result - **Remove**: Remove the delimiter from the result ## Private Members ### len - **Type**: `usize` - **Description**: Length of the slice ### data - **Type**: `view_t` - **Description**: String view data - **Default**: `{}` ## Constructors ### [`slice`](./slice/constructors/slice-1) Default constructor ```kairo slice() noexcept ``` ### [`slice`](./slice/constructors/slice-2) Copy constructor ```kairo slice(const slice& other) noexcept ``` ### [`slice`](./slice/constructors/slice-3) Move constructor ```kairo slice(slice&& other) noexcept ``` ### [`slice`](./slice/constructors/slice-4) Constructor from C-style string ```kairo slice(const CharT* str) noexcept ``` ### [`slice`](./slice/constructors/slice-5) Constructor from C-style string with size ```kairo slice(const CharT* str, size: size_t) noexcept ``` ### [`slice`](./slice/constructors/slice-6) Constructor from string view ```kairo slice(view: view_t) noexcept ``` ### [`slice`](./slice/constructors/slice-7) Constructor from character vector ```kairo slice(vec: char_vec&) noexcept ``` ### [`slice`](./slice/constructors/slice-8) Constructor from character vector (move) ```kairo slice(vec: char_vec&&) noexcept ``` ## Operators ### [`operator view_t`](./slice/operators/operator-view_t-1) Conversion to string view ```kairo op view_t(self) -> view_t ``` ### [`operator==`](./slice/operators/operator-2) Equality comparison ```kairo op ==(self, const slice& other) -> bool ``` ### [`operator!=`](./slice/operators/operator-3) Inequality comparison ```kairo op !=(self, const slice& other) -> bool ``` ### [`operator<`](./slice/operators/operator-4) Less than comparison ```kairo op <(self, const slice& other) -> bool ``` ### [`operator>`](./slice/operators/operator-5) Greater than comparison ```kairo op >(self, const slice& other) -> bool ``` ### [`operator<=`](./slice/operators/operator-6) Less than or equal comparison ```kairo op <=(self, const slice& other) -> bool ``` ### [`operator>=`](./slice/operators/operator-7) Greater than or equal comparison ```kairo op >=(self, const slice& other) -> bool ``` ### [`operator in`](./slice/operators/operator-in-8) Contains operator for slice ```kairo op in(self, needle: slice&) -> bool ``` ### [`operator in`](./slice/operators/operator-in-9) Contains operator for character ```kairo op in(self, chr: CharT&) -> bool ``` ### [`operator[]`](./slice/operators/operator-10) Access character at index ```kairo op [``](self, index: usize) -> CharT ``` ## Methods ### [`size`](./slice/methods/size-1) Returns the length of the slice's data, essential for bounds checking and iteration ```kairo size(self) -> size_t ``` ### [`exchange`](./slice/methods/exchange-2) Exchanges content with another slice ```kairo exchange(self, other: slice&) -> void ``` ### [`replace`](./slice/methods/replace-3) Replaces content with another slice ```kairo replace(self, other: slice&) -> void ``` ### [`replace`](./slice/methods/replace-4) Replaces content with C-style string ```kairo replace(self, str: CharT*, size: usize) -> void ``` ### [`raw`](./slice/methods/raw-5) Returns a pointer to the slice's underlying data, essential for direct access ```kairo raw(self) -> const CharT* ``` ### [`is_empty`](./slice/methods/is_empty-6) Indicates whether the slice has no data, crucial for control flow ```kairo is_empty(self) -> bool ``` ### [`subslice`](./slice/methods/subslice-7) Creates a view into a portion of the slice, vital for safe substring operations ```kairo subslice(self, pos: usize, len: usize) -> slice ``` ### [`l_strip`](./slice/methods/l_strip-8) Strips whitespace from the left ```kairo l_strip(self, delim: char_vec& = {' ', '\t', '\n', '\r'}) -> slice ``` ### [`r_strip`](./slice/methods/r_strip-9) Strips whitespace from the right ```kairo r_strip(self, delim: char_vec& = {' ', '\t', '\n', '\r'}) -> slice ``` ### [`strip`](./slice/methods/strip-10) Strips whitespace from both ends ```kairo strip(self, delim: char_vec& = {' ', '\t', '\n', '\r'}) -> slice ``` ### [`length`](./slice/methods/length-11) Returns the length of the slice ```kairo length(self) -> usize ``` ### [`starts_with`](./slice/methods/starts_with-12) Checks if slice starts with needle ```kairo starts_with(self, needle: slice&) -> bool ``` ### [`ends_with`](./slice/methods/ends_with-13) Checks if slice ends with needle ```kairo ends_with(self, needle: slice&) -> bool ``` ### [`contains`](./slice/methods/contains-14) Checks if slice contains needle ```kairo contains(self, needle: slice&) -> bool ``` ### [`contains`](./slice/methods/contains-15) Checks if slice contains character ```kairo contains(self, chr: CharT&) -> bool ``` ### [`compare`](./slice/methods/compare-16) Compares two slices lexicographically ```kairo compare(self, other: slice&) -> isize ``` ### [`split_lines`](./slice/methods/split_lines-17) Returns a vector of line views, necessary for line-based processing ```kairo split_lines(self) -> slice_vec ``` ### [`split`](./slice/methods/split-18) Returns a vector of views, necessary for delimiter-based processing ```kairo split(self, delim: slice&, op: Operation = Operation::Remove) -> slice_vec ``` ### [`lfind`](./slice/methods/lfind-19) Finds first occurrence of needle ```kairo lfind(self, needle: slice&) -> usize? ``` ### [`rfind`](./slice/methods/rfind-20) Finds last occurrence of needle ```kairo rfind(self, needle: slice&) -> usize? ``` ### [`find_first_of`](./slice/methods/find_first_of-21) Finds first occurrence of any character in needle ```kairo find_first_of(self, needle: slice&) -> usize? ``` ### [`find_last_of`](./slice/methods/find_last_of-22) Finds last occurrence of any character in needle ```kairo find_last_of(self, needle: slice&) -> usize? ``` ### [`find_first_not_of`](./slice/methods/find_first_not_of-23) Finds first character not in needle ```kairo find_first_not_of(self, needle: slice&) -> usize? ``` ### [`find_last_not_of`](./slice/methods/find_last_not_of-24) Finds last character not in needle ```kairo find_last_not_of(self, needle: slice&) -> usize? ``` --- ## std::String::slice::compare URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/compare-16/ # `compare` Compares two slices lexicographically ## Signature ```kairo compare(self, other: slice&) -> isize ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::contains URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/contains-14/ # `contains` Checks if slice contains needle ## Signature ```kairo contains(self, needle: slice&) -> bool ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::contains URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/contains-15/ # `contains` Checks if slice contains character ## Signature ```kairo contains(self, chr: CharT&) -> bool ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::ends_with URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/ends_with-13/ # `ends_with` Checks if slice ends with needle ## Signature ```kairo ends_with(self, needle: slice&) -> bool ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::exchange URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/exchange-2/ # `exchange` Exchanges content with another slice ## Signature ```kairo exchange(self, other: slice&) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::find_first_not_of URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/find_first_not_of-23/ # `find_first_not_of` Finds first character not in needle ## Signature ```kairo find_first_not_of(self, needle: slice&) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::find_first_of URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/find_first_of-21/ # `find_first_of` Finds first occurrence of any character in needle ## Signature ```kairo find_first_of(self, needle: slice&) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::find_last_not_of URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/find_last_not_of-24/ # `find_last_not_of` Finds last character not in needle ## Signature ```kairo find_last_not_of(self, needle: slice&) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::find_last_of URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/find_last_of-22/ # `find_last_of` Finds last occurrence of any character in needle ## Signature ```kairo find_last_of(self, needle: slice&) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::is_empty URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/is_empty-6/ # `is_empty` Indicates whether the slice has no data, crucial for control flow ## Signature ```kairo is_empty(self) -> bool ``` ## Specifiers - `constexpr` - `const` - `noexcept` ## Attributes - `nodiscard` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::l_strip URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/l_strip-8/ # `l_strip` Strips whitespace from the left ## Signature ```kairo l_strip(self, delim: char_vec& = {' ', '\t', '\n', '\r'}) -> slice ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::length URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/length-11/ # `length` Returns the length of the slice ## Signature ```kairo length(self) -> usize ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::lfind URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/lfind-19/ # `lfind` Finds first occurrence of needle ## Signature ```kairo lfind(self, needle: slice&) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::r_strip URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/r_strip-9/ # `r_strip` Strips whitespace from the right ## Signature ```kairo r_strip(self, delim: char_vec& = {' ', '\t', '\n', '\r'}) -> slice ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::raw URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/raw-5/ # `raw` Returns a pointer to the slice's underlying data, essential for direct access ## Signature ```kairo raw(self) -> const CharT* ``` ## Specifiers - `constexpr` - `const` - `noexcept` ## Attributes - `nodiscard` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::replace URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/replace-3/ # `replace` Replaces content with another slice ## Signature ```kairo replace(self, other: slice&) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::replace URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/replace-4/ # `replace` Replaces content with C-style string ## Signature ```kairo replace(self, str: CharT*, size: usize) -> void ``` ## Specifiers - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::rfind URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/rfind-20/ # `rfind` Finds last occurrence of needle ## Signature ```kairo rfind(self, needle: slice&) -> usize? ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::size URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/size-1/ # `size` Returns the length of the slice's data, essential for bounds checking and iteration ## Signature ```kairo size(self) -> size_t ``` ## Specifiers - `constexpr` - `const` - `noexcept` ## Attributes - `nodiscard` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::split URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/split-18/ # `split` Returns a vector of views, necessary for delimiter-based processing ## Signature ```kairo split(self, delim: slice&, op: Operation = Operation::Remove) -> slice_vec ``` ## Specifiers - `const` ## Attributes - `nodiscard` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::split_lines URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/split_lines-17/ # `split_lines` Returns a vector of line views, necessary for line-based processing ## Signature ```kairo split_lines(self) -> slice_vec ``` ## Specifiers - `const` ## Attributes - `nodiscard` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::starts_with URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/starts_with-12/ # `starts_with` Checks if slice starts with needle ## Signature ```kairo starts_with(self, needle: slice&) -> bool ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::strip URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/strip-10/ # `strip` Strips whitespace from both ends ## Signature ```kairo strip(self, delim: char_vec& = {' ', '\t', '\n', '\r'}) -> slice ``` ## Specifiers - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::subslice URL: https://www.kairolang.org/docs/library/Core/std-string/slice/methods/subslice-7/ # `subslice` Creates a view into a portion of the slice, vital for safe substring operations ## Signature ```kairo subslice(self, pos: usize, len: usize) -> slice ``` ## Specifiers - `const` - `noexcept` ## Attributes - `nodiscard` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::operator[] URL: https://www.kairolang.org/docs/library/Core/std-string/slice/operators/operator-10/ # `operator[]` Access character at index ## Signature ```kairo op [](self, index: usize) -> CharT ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::operator== URL: https://www.kairolang.org/docs/library/Core/std-string/slice/operators/operator-2/ # `operator==` Equality comparison ## Signature ```kairo op ==(self, const slice& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::operator!= URL: https://www.kairolang.org/docs/library/Core/std-string/slice/operators/operator-3/ # `operator!=` Inequality comparison ## Signature ```kairo op !=(self, const slice& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::operator< URL: https://www.kairolang.org/docs/library/Core/std-string/slice/operators/operator-4/ # `operator<` Less than comparison ## Signature ```kairo op <(self, const slice& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::operator> URL: https://www.kairolang.org/docs/library/Core/std-string/slice/operators/operator-5/ # `operator>` Greater than comparison ## Signature ```kairo op >(self, const slice& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::operator<= URL: https://www.kairolang.org/docs/library/Core/std-string/slice/operators/operator-6/ # `operator<=` Less than or equal comparison ## Signature ```kairo op <=(self, const slice& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::operator>= URL: https://www.kairolang.org/docs/library/Core/std-string/slice/operators/operator-7/ # `operator>=` Greater than or equal comparison ## Signature ```kairo op >=(self, const slice& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::operator in URL: https://www.kairolang.org/docs/library/Core/std-string/slice/operators/operator-in-8/ # `operator in` Contains operator for slice ## Signature ```kairo op in(self, needle: slice&) -> bool ``` ## Specifiers - `constexpr` - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::operator in URL: https://www.kairolang.org/docs/library/Core/std-string/slice/operators/operator-in-9/ # `operator in` Contains operator for character ## Signature ```kairo op in(self, chr: CharT&) -> bool ``` ## Specifiers - `constexpr` - `const` ## See Also [Back to `slice`](../slice) --- ## std::String::slice::operator view_t URL: https://www.kairolang.org/docs/library/Core/std-string/slice/operators/operator-view_t-1/ # `operator view_t` Conversion to string view ## Signature ```kairo op view_t(self) -> view_t ``` ## Specifiers - `constexpr` - `const` - `noexcept` ## See Also [Back to `slice`](../slice) --- ## std::String::split URL: https://www.kairolang.org/docs/library/Core/std-string/split/ # `split` Splits a string by delimiter ## Template Parameters - `T` ## Signature ```kairo fn fn split(str: *T, delim: const *T) -> vec<*T>; ``` ## Specifiers - `constexpr` - `inline` --- ## std::String::transform URL: https://www.kairolang.org/docs/library/Core/std-string/transform/ # `transform` Transforms src string and stores result in dest ## Template Parameters - `T` ## Signature ```kairo fn fn transform(dest: *T, src: const *T, n: usize) -> usize; ``` ## Specifiers - `constexpr` - `inline` --- ## std::Function URL: https://www.kairolang.org/docs/library/Core/std/function/ # `Function` Represents a function type with specified return type and argument types --- ## std::as_cast URL: https://www.kairolang.org/docs/library/Core/std/functions/as_cast/ # `as_cast` Performs a safe cast from one type to another ## Signature ```kairo fn as_cast(value: From) -> To; ``` ## Specifiers - `constexpr` --- ## std::as_const URL: https://www.kairolang.org/docs/library/Core/std/functions/as_const/ # `as_const` Converts a type to a const type ## Signature ```kairo fn as_const(value: From) -> To; ``` ## Specifiers - `constexpr` --- ## std::as_unsafe URL: https://www.kairolang.org/docs/library/Core/std/functions/as_unsafe/ # `as_unsafe` Performs an unsafe cast from one type to another ## Signature ```kairo fn as_unsafe(value: From) -> To; ``` ## Specifiers - `constexpr` --- ## std::char_to_cchar URL: https://www.kairolang.org/docs/library/Core/std/functions/char_to_cchar/ # char_to_cchar Converts a wide character to a char (c-char) ## Signature ```kairo char_to_cchar(wc: wchar_t) -> char ``` ## Specifiers - `inline` --- ## std::crash URL: https://www.kairolang.org/docs/library/Core/std/functions/crash/ # `crash` Crashes the program with a specified Panicable object ## Signature ```kairo fn crash(obj: T) -> void; ``` ## Specifiers - `noreturn` --- ## std::nptr_to_string URL: https://www.kairolang.org/docs/library/Core/std/functions/cstrptr_to_string/ # nptr_to_string Converts a C-style string pointer to a string with specified size ## Signature ```kairo nptr_to_string(cstr: const char*, size: size_t) -> string ``` ## Specifiers - `inline` --- ## std::endl URL: https://www.kairolang.org/docs/library/Core/std/functions/endl/ # endl Represents an endline character for output streaming in print function call ## Signature ```kairo endl(line_ending: string | char) -> endl ``` --- ## std::erase_type URL: https://www.kairolang.org/docs/library/Core/std/functions/erase_type/ # `erase_type` Erases the type of an object and returns a TypeErasure object ## Signature ```kairo fn fn erase_type(obj: *T) -> *TypeErasure; ``` --- ## std::next URL: https://www.kairolang.org/docs/library/Core/std/functions/next/ # next Retrieves the next value from a generator ## Signature ```kairo next(gen: Generator) -> T ``` --- ## std::range URL: https://www.kairolang.org/docs/library/Core/std/functions/range/ # range Creates a range from first to last with specified step ## Signature ```kairo range(first: T, last: T, step: isize = 1) -> Range ``` --- ## std::nstring_to_string URL: https://www.kairolang.org/docs/library/Core/std/functions/sstring_to_string/ # nstring_to_string Converts a nstring to a string (nstring being `kairo::String::basic`) ## Signature ```kairo nstring_to_string(cstr: const nstring&) -> string ``` ## Specifiers - `inline` --- ## std::string_to_nstring URL: https://www.kairolang.org/docs/library/Core/std/functions/string_to_sstring/ # string_to_nstring Converts a string to a nstring ## Signature ```kairo string_to_nstring(wstr: const string&) -> nstring ``` ## Specifiers - `inline` --- ## std::stringf URL: https://www.kairolang.org/docs/library/Core/std/functions/stringf/ # stringf Formats a string using the specified format and arguments, similar to printf in C ## Signature ```kairo stringf(format: string, ...args) -> string ``` --- ## std::wptr_to_nptr URL: https://www.kairolang.org/docs/library/Core/std/functions/strptr_to_cstrptr/ # wptr_to_nptr Converts a wide string pointer to a C-style string pointer ## Signature ```kairo wptr_to_nptr(wstr: const wchar_t*, buffer: char*, buffer_size: size_t) -> void ``` ## Specifiers - `inline` --- ## std::to_string URL: https://www.kairolang.org/docs/library/Core/std/functions/to_string/ # to_string Converts various types to string representation ## Signature ```kairo to_string(value: T) -> string ``` --- ## std::Generator URL: https://www.kairolang.org/docs/library/Core/std/generator/ # `Generator` Represents a generator type that yields values of the specified type, C++ coroutines are used in the implementation --- ## std::null_t URL: https://www.kairolang.org/docs/library/Core/std/null_t/ # null_t Represents a null value, used for null pointers or null values in containers --- ## std::Questionable::Questionable (Constructor) URL: https://www.kairolang.org/docs/library/Core/std/questionable/constructors/questionable-1/ # `Questionable` (Constructor) Default constructor (null) ## Signature ```kairo Questionable() noexcept ``` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::Questionable (Constructor) URL: https://www.kairolang.org/docs/library/Core/std/questionable/constructors/questionable-2/ # `Questionable` (Constructor) Null constructor ## Signature ```kairo Questionable(const std::null_t&) noexcept ``` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::Questionable (Constructor) URL: https://www.kairolang.org/docs/library/Core/std/questionable/constructors/questionable-3/ # `Questionable` (Constructor) Value constructor ## Signature ```kairo Questionable(const T& value) ``` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::Questionable (Constructor) URL: https://www.kairolang.org/docs/library/Core/std/questionable/constructors/questionable-4/ # `Questionable` (Constructor) Move value constructor ## Signature ```kairo Questionable(T&& value) ``` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::Questionable (Constructor) URL: https://www.kairolang.org/docs/library/Core/std/questionable/constructors/questionable-5/ # `Questionable` (Constructor) Error constructor ## Signature ```kairo Questionable(const std::Panic::Frame& error) ``` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable URL: https://www.kairolang.org/docs/library/Core/std/questionable/ # `Questionable` Represents a type that can be nullable or panicable ## Constructors ### [Questionable](./questionable/constructors/questionable-1) Default constructor (null) ```kairo Questionable() noexcept ``` ### [Questionable](./questionable/constructors/questionable-2) Null constructor ```kairo Questionable(const std::null_t&) noexcept ``` ### [Questionable](./questionable/constructors/questionable-3) Value constructor ```kairo Questionable(const T& value) ``` ### [Questionable](./questionable/constructors/questionable-4) Move value constructor ```kairo Questionable(T&& value) ``` ### [Questionable](./questionable/constructors/questionable-5) Error constructor ```kairo Questionable(const std::Panic::Frame& error) ``` ## Operators ### [operator==](./questionable/operators/operator-1) Equality comparison with null ```kairo op ==(self, const std::null_t&) -> bool ``` ### [operator!=](./questionable/operators/operator-2) Inequality comparison with null ```kairo op !=(self, const std::null_t&) -> bool ``` ### [operator?](./questionable/operators/operator-3) Collapse operator to get T | null_t | Error ```kairo op ?(self) -> bool ``` ### [operator in](./questionable/operators/operator-in-4) Contains operator ```kairo op in(self, const E& other) -> bool ``` ### [operator as](./questionable/operators/operator-as-5) Cast to error type ```kairo op as(self, E*) -> E ``` ### [operator as](./questionable/operators/operator-as-6) Cast to value type ```kairo op as(self, T*) -> T ``` ### [operator*](./questionable/operators/operator-7) Dereference operator ```kairo op *(self) -> &T ``` ### [operator T](./questionable/operators/operator-t-8) Implicit conversion to T ```kairo op T(self) -> T ``` --- ## std::Questionable::operator== URL: https://www.kairolang.org/docs/library/Core/std/questionable/operators/operator-1/ # `operator==` Equality comparison with null ## Signature ```kairo op ==(self, const std::null_t&) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::operator!= URL: https://www.kairolang.org/docs/library/Core/std/questionable/operators/operator-2/ # `operator!=` Inequality comparison with null ## Signature ```kairo op !=(self, const std::null_t&) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::operator? URL: https://www.kairolang.org/docs/library/Core/std/questionable/operators/operator-3/ # `operator?` Collapse operator to get T | null_t | Error ## Signature ```kairo op ?(self) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::operator* URL: https://www.kairolang.org/docs/library/Core/std/questionable/operators/operator-7/ # `operator*` Dereference operator ## Signature ```kairo op *(self) -> &T ``` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::operator as URL: https://www.kairolang.org/docs/library/Core/std/questionable/operators/operator-as-5/ # `operator as` Cast to error type ## Signature ```kairo op as(self, E*) -> E ``` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::operator as URL: https://www.kairolang.org/docs/library/Core/std/questionable/operators/operator-as-6/ # `operator as` Cast to value type ## Signature ```kairo op as(self, T*) -> T ``` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::operator in URL: https://www.kairolang.org/docs/library/Core/std/questionable/operators/operator-in-4/ # `operator in` Contains operator ## Signature ```kairo op in(self, const E& other) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `Questionable`](../questionable) --- ## std::Questionable::operator T URL: https://www.kairolang.org/docs/library/Core/std/questionable/operators/operator-t-8/ # `operator T` Implicit conversion to T ## Signature ```kairo op T(self) -> T ``` ## See Also [Back to `Questionable`](../questionable) --- ## std::Range::Range (Constructor) URL: https://www.kairolang.org/docs/library/Core/std/range/constructors/range-1/ # `Range` (Constructor) Creates a range from first to last with specified step ## Signature ```kairo Range(self, first: T, last: T, step: isize = 1) ``` ## See Also [Back to `Range`](../range) --- ## std::Range::Range (Constructor) URL: https://www.kairolang.org/docs/library/Core/std/range/constructors/range-2/ # `Range` (Constructor) Creates a range from 0 to last with step 1 ## Signature ```kairo Range(self, last: T) ``` ## See Also [Back to `Range`](../range) --- ## std::Range URL: https://www.kairolang.org/docs/library/Core/std/range/ # `Range` Represents a range of values of type T ## Constructors ### [Range](./range/constructors/range-1) Creates a range from first to last with specified step ```kairo Range(self, first: T, last: T, step: isize = 1) ``` ### [Range](./range/constructors/range-2) Creates a range from 0 to last with step 1 ```kairo Range(self, last: T) ``` ## Operators ### [operator in](./range/operators/operator-in-1) Iterator operator for range-based loops ```kairo fn op in (self)[iter] -> yield T ``` ### [operator in](./range/operators/operator-in-2) Contains operator to check if value is in range ```kairo fn op in (self, value: T)[contains] -> bool ``` ## Methods ### [begin](./range/methods/begin-1) Returns the beginning value of the range ```kairo begin(self) -> T ``` ### [end](./range/methods/end-2) Returns the ending value of the range ```kairo end(self) -> T ``` ### [step](./range/methods/step-3) Returns the step value of the range ```kairo step(self) -> T ``` ### [contains](./range/methods/contains-4) Checks if a value is within the range ```kairo contains(self, value: const T&) -> bool ``` ### [is_empty](./range/methods/is_empty-5) Checks if the range is empty ```kairo is_empty(self) -> bool ``` --- ## std::Range::begin URL: https://www.kairolang.org/docs/library/Core/std/range/methods/begin-1/ # `begin` Returns the beginning value of the range ## Signature ```kairo begin(self) -> T ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `Range`](../range) --- ## std::Range::contains URL: https://www.kairolang.org/docs/library/Core/std/range/methods/contains-4/ # `contains` Checks if a value is within the range ## Signature ```kairo contains(self, value: const T&) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `Range`](../range) --- ## std::Range::end URL: https://www.kairolang.org/docs/library/Core/std/range/methods/end-2/ # `end` Returns the ending value of the range ## Signature ```kairo end(self) -> T ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `Range`](../range) --- ## std::Range::is_empty URL: https://www.kairolang.org/docs/library/Core/std/range/methods/is_empty-5/ # `is_empty` Checks if the range is empty ## Signature ```kairo is_empty(self) -> bool ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `Range`](../range) --- ## std::Range::step URL: https://www.kairolang.org/docs/library/Core/std/range/methods/step-3/ # `step` Returns the step value of the range ## Signature ```kairo step(self) -> T ``` ## Specifiers - `const` - `noexcept` ## See Also [Back to `Range`](../range) --- ## std::Range::operator in URL: https://www.kairolang.org/docs/library/Core/std/range/operators/operator-in-1/ # `operator in` Iterator operator for range-based loops ## Signature ```kairo fn op in (self)[iter] -> yield T ``` ## See Also [Back to `Range`](../range) --- ## std::Range::operator in URL: https://www.kairolang.org/docs/library/Core/std/range/operators/operator-in-2/ # `operator in` Contains operator to check if value is in range ## Signature ```kairo fn op in (self, value: T)[contains] -> bool ``` ## See Also [Back to `Range`](../range) --- ## std::TypeErasure URL: https://www.kairolang.org/docs/library/Core/std/typeerasure/ # TypeErasure A type erasure mechanism that allows for type removal ## Methods ### [destroy](./typeerasure/methods/destroy-1) Destroys the type-erased object ```kairo destroy(self) -> void ``` ### [operator*](./typeerasure/methods/operator-2) Dereference operator to access the erased object ```kairo op *(self) -> *void ``` ### [type_info](./typeerasure/methods/type_info-3) Returns type information about the erased object ```kairo type_info(self) -> libcxx::type_info ``` ### [clone](./typeerasure/methods/clone-4) Creates a copy of the type-erased object ```kairo clone(self) -> *TypeErasure ``` --- ## std::TypeErasure::clone URL: https://www.kairolang.org/docs/library/Core/std/typeerasure/methods/clone-4/ # `clone` Creates a copy of the type-erased object ## Signature ```kairo clone(self) -> *TypeErasure ``` ## See Also [Back to `TypeErasure`](../typeerasure) --- ## std::TypeErasure::destroy URL: https://www.kairolang.org/docs/library/Core/std/typeerasure/methods/destroy-1/ # `destroy` Destroys the type-erased object ## Signature ```kairo destroy(self) -> void ``` ## See Also [Back to `TypeErasure`](../typeerasure) --- ## std::TypeErasure::operator* URL: https://www.kairolang.org/docs/library/Core/std/typeerasure/methods/operator-2/ # `operator*` Dereference operator to access the erased object ## Signature ```kairo op *(self) -> *void ``` ## See Also [Back to `TypeErasure`](../typeerasure) --- ## std::TypeErasure::type_info URL: https://www.kairolang.org/docs/library/Core/std/typeerasure/methods/type_info-3/ # `type_info` Returns type information about the erased object ## Signature ```kairo type_info(self) -> libcxx::type_info ``` ## See Also [Back to `TypeErasure`](../typeerasure) --- ======================================================================== SECTION: BLOG ======================================================================== ## How AI Is Used in Kairo URL: https://www.kairolang.org/blog/how-ai-is-used-in-kairo/ # AI Workflow/Usage in Kairo Kairo is being created by a small core team of university students. Most of the AI usage mentioned here is mine, since I (Dhruvan) undertake most of the compiler and language design work. I want to be upfront about where and how AI is employed in this project. This is a snapshot as of May 2026. I'll write a follow up if usage changes. ## What Not AI-Assisted To be clear: the language design, compiler architecture (Stage 0 and Stage 1), memory model, type system, ABI decisions, codegen pipeline, and build system are all human-written and human-designed. ## Why I’m Writing This I know that the systems programming community has a lot of opinions about AI in development. I'd rather be straightforward about where exactly it's utilized, than have someone find out later and question what else I didn't tell them about it. ## Compiler The compiler is hand written. Every line of code in the lexer, parser, AST, arena allocator, diagnostics engine, preprocessor and codegen pipeline is hand-written. No AI-generated code exists in the compiler, in either Stage 0 or Stage 1. I don’t use Copilot or any kind of inline code completion. Kairo is a novel language, it does not exist in any training data. AI models can’t recommend Kairo code since they have never seen Kairo code. Autocomplete is useless, the compiler is self-hosted. All lines are hand written. ## Debugging When I encounter deep bugs in the compiler, I use Claude as a debugging partner. Kairo compiles via a C++ codegen layer (stage0 compiler), hence all LLDB symbols are mangled C++ names which are hard to read. My methodology is: I walk through the trace in LLDB, try to find and fix the issue myself - if and only if, I'm not able to - I provide the output with relevant source context to Claude, it tells me what commands to perform or what state to look at, I run them, feed the results back, and we figure out what’s really going on. Then I write the repair. Recent example: empty source files crashed compiler . The debugging session found it was a `file.size() - 1` on a zero-length file. The subtraction underflowed a `usize` into `usize(-1)`. This blew out everything downstream, making a buffer null, and then crashing on a SEGFAULT, the crash never traced back to the underflow, the crash was in a completely unrelated file that I thought was isolated. Then it went from crashing to working but slow (a spin-wait bug), again another underflow in the SourceLineTable then to immediate execution after all the fixes. It was a simple problem, but it's buried by mangled symbols and threaded execution. The AI helped me find the problem faster. It did not write the fix. AI is utilized as a second pair of eyes on diagnostic output, not as a code author. ## Doc Comments Within the Compiler Codebase Github copilot is used to write doc comments within the compiler codebase. Code itself is hand written, but the prose comments that explain what the code is doing are AI-assisted. This will be replaced with human-written comments eventually (ideally stage2), but for now it helps me write more comments than I would otherwise, and makes the code easier to understand for me looking back at it after a long time or for new contributors. ## Docs Every page on kairolang.org was a raw technical dump, syntax examples, ABI lowering information, generated C++ output, invariants, edge cases. The kind of notes that make sense to me and nobody else. I handed those dumps to Claude to rework into polished-ish reference documentation. The LLM structures sections, writes prose transitions, formats tables and code blocks, and smooths out phrasing. I do all the technical writing, examine every page for correctness, and change anything that misrepresents the language semantics. The technical substance, the language itself, how it operates, the invariants are mine. AI helps with the prose refinement. If you see something in the docs that’s a technical error, that’s on me. ## Web site I'm not a web developer. I've never had a passion. Astro, CSS, HTML and JS are not languages that I work with. The kairolang.org website was built by: Claude. I explained the theme, layout, what the site should include and what is it for. The AI wrote the Astro components, CSS and front end code. I looked over the output, and corrected errors wherever I found them. The website source is publicly available at [github.com/kairolang/kairo-web](https://github.com/kairolang/kairo-web). ## Git Commit Messages I use GitLens AI to write my commit messages. That’s just pure-laziness. The commits are mine, the messages are autogenerated. ## LSP and VSCode Extension (Stage 0) Stage 0 compiler is written entirely by hand. But the LSP integration and VSCode extension were AI helped. I implemented a pseudo-LSP mode in the compiler, pass it `--lsp-mode` and it returns diagnostics as JSON, then I provided Claude the LSP protocol spec. Claude built the Python (pygls) server that connects the compiler output to VSCode and the extension code to wire it up. It's not great but, it was enough to unblock Stage 1 development, without spending weeks on the LSP protocol and VSCode extension API. The Stage 1 LSP server and extension will be developed from scratch. If you have questions, open an issue on [GitHub](https://github.com/kairolang/kairo/issues). --- ## Pointers Without References: Kairo's Pointer Model URL: https://www.kairolang.org/blog/kairo pointer-model/ # Pointers Without References: Kairo's Pointer Model C++ has pointers and references. Rust has raw pointers, references, mutable references, and smart pointers. Go has pointers but no pointer arithmetic. Every systems language ends up with some combination of indirection types, each with its own rules, its own syntax, and its own mental overhead. Kairo has one: `*T`. One pointer type, 8 bytes, non-null, with full compile-time safety analysis. No references. No borrow syntax. No lifetime annotations. ## The Problem with Splitting Pointers and References C++ has `T*` and `T&`. In theory, references are "safe pointers that can't be null and can't be reseated." In practice, you can absolutely create dangling references, null references through casts, and references to dead stack frames. The compiler doesn't stop you. The "safety" is convention, not enforcement. Rust fixed the enforcement problem. `&T` and `&mut T` are genuinely safe, the borrow checker proves at compile time that references don't dangle and that mutable access is exclusive. But the cost is complexity. You now have `&T`, `&mut T`, `*const T`, `*mut T`, `Box`, `Rc`, `Arc`, `Weak`, and lifetime annotations (`'a`) to connect them all. The programmer has to choose the right indirection type at every point and annotate lifetimes when the compiler can't infer them. This is a real barrier. Not a skill issue, a complexity issue. Lifetime annotations are the number one complaint from developers learning Rust, and they exist because the borrow checker is function-local: it needs the programmer to describe how lifetimes relate across function boundaries. ## Kairo's Answer: One Pointer, Compile-Time Safety `*T` is the only safe pointer type in Kairo. It's 8 bytes, just an address, same size as a C pointer. No runtime metadata, no fat overhead. Non-null by construction. ```kairo var x = 42 var p: *i32 = &x *p = 100 // guaranteed valid, no null check needed var bad: *i32 = &null // compile error: *T is non-null ``` The safety comes from AMT (Automatic Memory Tracking), which performs whole-program analysis at compile time. AMT tracks every pointer's lifetime, provenance, and usage across the entire program, not just within a single function. It knows where a pointer was created, what it points to, whether the target is still alive, and whether any aliasing violations exist. Because AMT sees the whole program, it doesn't need the programmer to annotate lifetimes. There are no `'a` parameters, no borrow syntax, no distinction between "I'm borrowing this" and "I'm pointing to this." You write `*T`, and the compiler figures out the rest. ## AMT Auto-Promotion: Debug vs Release AMT can automatically promote a `*T` to a smart pointer (`std::Unique`, `std::Shared`, or `std::Weak<*T>`) when it determines the pointer needs to outlive its original scope: ```kairo fn make_config() -> *Config { var cfg = @create Config(8080) return cfg } ``` **In debug builds,** AMT warns and promotes `cfg` to whichever smart pointer fits the usage pattern. This keeps you moving, you don't have to think about ownership while you're iterating on logic. **In release builds,** AMT does not auto-promote. Instead, it emits a compile error that tells you exactly where promotion would have happened, what smart pointer type is needed, and how to fix it: ``` error[AMT]: pointer 'cfg' escapes its scope and requires ownership annotation --> src/config.k:3:12 3 | return cfg | ^^^ escapes function scope note: AMT would promote to: std::Unique 2 | var cfg: std::Unique = @create Config(8080) fix: annotate the type explicitly ``` This is deliberate. Debug builds are for getting things working. Release builds are for getting things right. If your code is going to ship, the ownership model should be explicit in the source, not inferred by the compiler behind your back. If AMT can't find any safe promotion path, debug or release, it's a hard compile error in both modes. The program doesn't compile until the ownership is unambiguous. ## Nullable Pointers Are Opt-In `*T` can't be null. If you need a pointer that might not exist, you opt into nullability explicitly with `*T?`, which wraps the pointer in Kairo's standard `Nullable` system: ```kairo var p: *i32? = find_pointer() if p? { std::println(*p) // compiler verified non-null } var val = p ?? &fallback // null coalescing var forced = unwrap!(p) // panics if null ``` Null pointer dereferences are impossible on `*T`, the type can'thold null, so there's nothing to check. On `*T?`, the compilerforces you to handle the null case before you can dereference. Nullbugs become compile errors. For raw C-style pointers with no safety and no tracking, there's `unsafe *T`: ```kairo var raw: unsafe *i32 = &null // nullable, no checks, no tracking ``` Three types, clear roles: | Type | Null | Tracked | Use case | |---|---|---|---| | `*T` | No | Yes (AMT) | Default, safe, non-null | | `*T?` | Yes | Yes (AMT) | Optional pointer, might not exist | | `unsafe *T` | Yes | No | FFI, hardware, custom allocators | ## Why Not References? Because they don't add anything, and they hide information. In C++, references exist partly for safety (non-null, can't be reseated) and partly for convenience, pass-by-reference avoids copies, auto-deref hides the indirection, and the syntax is cleaner than pointer syntax. In Rust, references exist because the borrow checker needs syntactic markers to distinguish borrows from ownership. Kairo rejects the convenience argument. A reference that auto-dereferences abstracts away the fact that you're working through a pointer. That abstraction hides something significant: you are accessing memory you don't own, through an address that points somewhere else. Kairo's philosophy is explicitness at the syntax level. If you're using a pointer, the code should look like you're using a pointer. The performance argument, "references avoid copies", is handled by the compiler, not by a language feature. Kairo has an optimization pass that automatically promotes value parameters to pointer parameters when the type is larger than a pointer: ```kairo fn process(data: string) { std::println(data) } ``` If the compiler sees that `data` is larger than 8 bytes and you don't alias it, modify it, mark it `volatile`, or use the function in an `async` context, it silently rewrites the parameter to `data: *const string` and inserts dereferences where needed. You get pass-by-pointer performance with value semantics in the source code, no reference type required, no syntax change, no programmer decision. The safety argument is handled by AMT. `*T` is already non-null and lifetime-tracked. A separate `&T` would be a second spelling of the same guarantee. Three reasons languages have references. Kairo handles all three without adding a type. ## The `const` Model Mutability is controlled by `const`, not by pointer type. `const` binds left-to-right, one level at a time: ```kairo var p: *i32 = &x // mutable pointer, mutable target var p: *const i32 = &x // mutable pointer, const target const p: *i32 = &x // const pointer, mutable target const p: *const i32 = &x // const pointer, const target ``` No `&T` vs `&mut T`. No `const T&` vs `T&`. One pointer type, one const rule, applied uniformly. The `const` applies to the thing immediately to its right, no C++ ambiguity about whether the pointer or the pointee is const. ## What This Costs **Whole-program analysis is expensive.** AMT needs to see the entire program to make its safety guarantees. This is more expensive than Rust's function-local borrow checking. Kairo mitigates this with `.amt` artifacts, precomputed ownership summaries for each translation unit that AMT can consume without re-analyzing dependencies. But it's still more work than a local analysis. **No lifetime polymorphism.** Rust's lifetime parameters let you write functions that are generic over lifetimes, the function works regardless of how long the references live, as long as the constraints are satisfied. Kairo doesn't have this. AMT analyzes concrete lifetimes, not abstract ones. In practice, this hasn't been a limitation because AMT's auto-promotion handles the cases where Rust would need lifetime parameters, but it's a theoretical expressiveness gap. **Learning curve for C++ devs.** C++ developers expect to write `T&` for references. Kairo says "just use `*T`." The pointer syntax carries baggage, C++ devs associate `*` with unsafety. The reality is that Kairo's `*T` is safer than C++'s `T&`, but the syntax doesn't signal that. This is a communication problem, not a technical one. ## The Design Principle Kairo's pointer model follows the same principle as `match` replacing `switch`: if you can get the same safety guarantees with less complexity, do it. References are a patch for unsafe pointers. If the pointer is already safe, you don't need the patch. One pointer type. Compile-time safety. Zero runtime cost. No annotations. --- ## Why Kairo Has match Instead of switch URL: https://www.kairolang.org/blog/match-replacing-switch/ # Why Kairo Replaced `switch` With `match` Kairo doesn't have `switch`. It has `match`. This wasn't a cosmetic rename, it's a different construct with different semantics that eliminates an entire class of bugs that `switch` has carried forward for fifty years. ## What's Wrong with `switch` The core problem with C-style `switch` is fall-through. Every case falls into the next unless you explicitly `break`. This is the default behavior, and it's almost never what you want. ```cpp // C++ switch (status) { case 200: handle_ok(); // forgot break falls through to 404 handling case 404: handle_not_found(); break; } ``` This is a real bug that has shipped in real software for decades. The "fix" is discipline: remember to write `break` at the end of every case, every time, forever. Languages have been patching around this with warnings, linters, and attributes like `[[fallthrough]]`, all to compensate for a default that should never have been the default. Kairo's answer is simple: don't have fall-through at all. Each case in a `match` is an isolated block. There's nothing to fall through to, so there's nothing to forget. ```kairo match status_code { case 200 { handle_ok() } case 404 { handle_not_found() } case 500 { handle_server_error() } default { handle_unknown() } } ``` If you want multiple values to hit the same code, list them: ```kairo match priority { case 1, 2, 3 { handle_high() } case 4, 5 { handle_medium() } default { handle_low() } } ``` No fall-through. No `break`. No ambiguity. ## Exhaustiveness Checking `switch` in C++ doesn't care if you miss a case. You can switch on an enum, forget a variant, and the compiler says nothing (unless you turn on specific warnings, and even then it's not an error). `match` in Kairo enforces exhaustiveness. The compiler verifies every possible value is handled: | Type | Rule | |---|---| | Plain enum | All variants covered, or `default` present | | ADT enum | All variants covered, or `default` present | | Booleans | `true` + `false` covers it | | Integers / strings | `default` required | Add a variant to an enum and every `match` on that enum becomes a compile error until you handle the new case. The compiler catches it, not your users. ## ADT Destructuring This is where `match` earns its name. Kairo has ADT enums, variants that carry structured payloads: ```kairo enum ParseResult { Success { value: T, bytes_consumed: i32 }, Error { message: string, position: i32 }, EndOfInput, } ``` `match` destructures these directly. You bind payload fields with `var` or `const` and the compiler checks that your field names match the declaration: ```kairo match result { case .Success(var value, var bytes_consumed) { std::println(f"parsed {value} from {bytes_consumed} bytes") } case .Error(const message, const position) { std::println(f"error at {position}: {message}") } case .EndOfInput { std::println("nothing left to parse") } } ``` You can omit fields you don't need, exhaustive field extraction isn't required. And you can add `where` guards for conditional matching: ```kairo match result { case .Success(var value) where value > 0 { process_positive(value) } case .Success(var value) { process_non_positive(value) } default { } } ``` Try doing this with `switch`. You can't, `switch` dispatches on a single value. Destructuring a tagged union in C++ means pulling out the tag, switching on it, then manually casting or accessing the payload. It's verbose, error-prone, and the compiler can't verify you got the fields right. ## `match` as an Expression `match` produces a value. Every branch returns the same type, and the compiler enforces it: ```kairo var label = match level { case .Debug { "debug" } case .Info { "info" } case .Warning { "warning" } case .Error { "error" } case .Fatal { "fatal" } } ``` This works with ranges too: ```kairo var severity = match code { case 200..=299 { 0 } case 400..=499 { 1 } case 500..=599 { 2 } default { -1 } } ``` No need for a ternary operator or a separate variable initialized before the switch. ## Struct Matching `match` isn't limited to enums. Structs can be destructured the same way, combined with `where` guards: ```kairo match response { case Response(var status, var body) where status == 200 { process(body) } case Response(var status) where status >= 400 { log_error(f"HTTP {status}") } default { } } ``` ## What About `break` in Loops? Since `match` cases don't fall through, there's nothing to `break` out of inside a match. So when a `match` is nested inside a loop, `break` does what you'd expect, it exits the loop: ```kairo for item in items { match item.kind { case .Sentinel { break // exits the for loop } case .Data(var payload) { process(payload) } default { } } } ``` No more wondering whether `break` exits the switch or the loop. ## The Design Principle Kairo's general approach is: if the default behavior of a construct causes bugs, change the default. Don't add warnings, attributes, or linter rules to patch over a broken default. Fix it. `switch` with fall-through is a broken default. `match` with isolated cases, exhaustiveness checking, destructuring, and expression form is the fix. --- ## Why Kairo Has Bidirectional C++ Interop Instead of Bindings URL: https://www.kairolang.org/blog/why-native-interop-instead-of-bindings/ # Why Kairo Has Bidirectional C++ Interop Instead of Bindings If you're building a new systems language in 2026, you have to answer one question before anything else: how does it talk to C++? The entire systems programming ecosystem runs on C and C++ libraries. Your language is dead on arrival if it can't use them. The standard answer is bindings. Write your language, then build a tool that reads C/C++ headers and generates wrapper code your language can call. Rust has `bindgen` for C, `cxx` and `autocxx` for C++. Every language in this space has some version of this. Kairo doesn't. Kairo parses C++ headers directly, makes every declaration available as a native Kairo symbol, and goes the other direction too, C++ can `#include` Kairo files. No binding generator, no wrapper code, no serialization layer. The interop is the compiler. ## The Problem with Bindings Binding generators are a translation layer between two type systems. That translation is where things break. **Build complexity.** `bindgen` is a separate tool in your build pipeline. It reads headers, generates Rust code, and that code has to be compiled alongside your project. Your build system now has an extra step that can fail independently, and debugging build failures means understanding what the generator produced, not just what you wrote. `cxx` requires you to write a bridge definition in a DSL that describes the interface between Rust and C++, a third language in your project that you have to keep in sync with both sides. **Type mismatches.** C++ has a rich type system, classes with inheritance, templates, concepts, operator overloading, move semantics, vtables. Binding generators have to map all of this into the target language's type system, and the mapping is always lossy. Templates are particularly painful: `bindgen` can only generate bindings for concrete template instantiations you explicitly request. You can't write generic code over a C++ template from the Rust side without manually instantiating every specialization you need. **Pointer semantics.** This is where bindings get dangerous. C++ uses raw pointers, references, `const` references, `unique_ptr`, `shared_ptr`, and move semantics. The generated bindings have to express all of this in the target language's pointer model, and the generator doesn't always get it right. You end up with `unsafe` blocks wrapping binding calls where the safety contract is "trust that the generator understood the C++ lifetime semantics correctly." **Maintenance burden.** Every time a C++ header changes, the bindings need to be regenerated. If the API changes in a way the generator doesn't handle cleanly, you're debugging generated code you didn't write to figure out why the types don't line up anymore. In large codebases with hundreds of C++ dependencies, this is a constant tax. **One-way by default.** Most binding generators only go in one direction: call C++ from your language. Going the other way, letting C++ call your code, is either unsupported, requires manual `extern "C"` exports that throw away your type system, or needs yet another tool. ## How Kairo Does It Kairo's approach is: skip the translation layer entirely. ### Kairo calling C++ `ffi "c++" import` invokes Clang's frontend internally to parse the header. Every exported declaration, functions, classes, enums, templates, concepts, becomes a native Kairo symbol with its original name and signature. No generated code, no bridge file. ```kairo ffi "c++" import "my_code.hh"; fn main() { var obj = MyClass("Kairo") std::println(f"name = {obj.get_name()}") my_function(42) } ``` That's it. If the C++ header compiles with Clang, Kairo can use it. ### C++ calling Kairo The `kcc` driver, a libclang-based compiler wrapper, makes `#include "file.k"` work transparently in C++ translation units: ```cpp #include "my_code.k" #include int main() { MyKairoClass obj("C++"); std::cout << obj.get_name() << std::endl; my_kairo_function(42); return 0; } ``` `kcc` intercepts the `#include`, invokes the Kairo compiler in-process to produce a C++-compatible header and object file, and links everything together. From the C++ side, Kairo types look like native C++ types because they are, Kairo emits ABI-compatible object code following the platform's native C++ ABI (Itanium on Unix, MSVC on Windows). ### Templates cross the boundary This is where the binding approach falls apart completely, and where Kairo's approach pays off the most. C++ concepts and Kairo generic constraints are interchangeable: ```kairo ffi "c++" import "my_code.hh"; fn add(a: T, b: T) -> T { return a + b } ``` `T impl Addable` in Kairo maps directly to `requires Addable` in the generated output. The constraint crosses the boundary without erasure. A Kairo generic type can satisfy a C++ concept, and a C++ concept can constrain a Kairo generic parameter. Try doing this with `bindgen`. You can't, you'd need to manually instantiate every template specialization and generate separate bindings for each one. ## Pointer Safety Across the Boundary Kairo doesn't pretend the interop boundary is safe. Any pointer or reference passed to C/C++ requires an explicit `unsafe` block, because the compiler can't enforce safety guarantees on the C++ side: ```kairo ffi "c++" import "my_code.hh"; fn main() { var x = 41 unsafe { add_one(unsafe &x) } std::println(f"x = {x}") // 42 } ``` `unsafe &` strips bounds tracking and hands a raw pointer to C++. The caller owns the memory contract from that point. This is explicit, visible, and greppable, you can search a Kairo codebase for every FFI boundary crossing in seconds. The difference from bindings: when you call a generated binding function, the `unsafe` is there but the actual pointer conversion happened inside generated code you didn't write. In Kairo, the conversion is in your code, where you can see exactly what safety guarantees you're giving up. ## What This Costs Nothing is free. Kairo's interop approach has tradeoffs: **Clang dependency.** Kairo's `ffi "c++"` uses Clang's frontend to parse headers. The compiler ships with a Clang frontend embedded. This is a large dependency, but it's the same tradeoff as any tool that needs to understand C++, including `bindgen`, which also uses `libclang`. **C++20 modules not yet supported.** The interop layer relies on header-based inclusion. Consuming pre-compiled C++ module interfaces (BMIs) is on the roadmap but not shipped yet. **Exception direction.** Kairo can catch C++ exceptions, but throwing Kairo panics into C++ isn't supported yet. For Kairo-to-C++ error signaling, use return types. ## Why This Matters The systems programming world has decades of C++ libraries. Physics engines, game engines, database kernels, OS APIs, networking stacks, all C++. A new language that can't use these fluently is a toy. Bindings are a tax you pay on every library, every API change, every template instantiation. Kairo's position is that the compiler should handle this natively, the same way it handles everything else: parse it, type-check it, emit code. No extra tools, no extra steps, no extra languages in your build. That's the bet. So far, it's working. ---