# 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-05T01:16:08.263Z --- ======================================================================== 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 } 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.len() != 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.len() == 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. Built by the [Kairo Software Foundation](/ksf). --- ## 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 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 LLVM submodule (see [below](#stage-1-optional)). 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 The distro Clang is often older than 18 or defaults to libstdc++, so install from LLVM's apt repo (18 is the minimum; newer is fine): ```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 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`: ```bash brew install llvm xmake echo 'export PATH="'"$(brew --prefix llvm)"'/bin:$PATH"' >> ~/.zshrc source ~/.zshrc ``` > [!NOTE] > Apple's bundled Clang reports its own version numbers that do not map to upstream LLVM > releases, and may not satisfy the Clang 18+ / C++23 / libc++ requirements. Use the Homebrew > LLVM toolchain. #### 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 (optional) Stage 1 is the self-hosted compiler, still in development. It requires a working Stage 0 build (above) plus the LLVM submodule. ```bash git checkout canary git submodule update --init --recursive # pulls LLVM this time large download kbld # must be on PATH, or use ./build/release//bin/kbld ``` 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 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/), 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. --- ## 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 using UTF-8 encoding internally with small string optimization (SSO). Strings up to 23 bytes are stored inline without a heap allocation. Longer strings are heap-allocated. - **Codepoint indexing:** `s[i]` returns a `char` (4-byte Unicode scalar value) at codepoint position `i`. This is O(1) amortized; the implementation uses a sparse breadcrumb cache to locate the nearest codepoint boundary and decodes from there. - **Byte indexing:** `s.bytes[i]` returns a raw `byte` 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. 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 and Grapheme Clusters 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. `len()` returns the codepoint count, not the visual glyph count. `string.graphemes()` returns an iterator of grapheme clusters per UAX #29 for code that needs to operate on user-visible characters. ### 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 | | `as unsafe *i8` | Null-terminated UTF-8 | Passing to C functions expecting `const char*` | 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. - **Null termination**: `string` is not null-terminated internally. Conversion methods for C interop add a null byte. Passing raw string data directly to a C function expecting a null-terminated string is undefined behavior. --- ## 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 CST 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 CST 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 | Kairo does not emit its own intermediate object format. The output of a compilation is a standard native object file that any conformant linker for that platform can process. --- ## 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<*T>`, `std::Shared<*T>`, `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) strings up to 23 bytes are stored inline without a heap allocation. Longer strings are heap-allocated. ```kairo var greeting = "Hello, Kairo! 📣" // 18 UTF-8 bytes fits in SSO var name = "Dhruvan" // 7 bytes SSO ``` Because UTF-8 is a variable-width encoding, indexing by codepoint (`s[i]`) is O(1) amortized (since it looks up the nearest codepoint boundary and decodes from there), while indexing by byte (`s.bytes[i]`) is O(1) always, but returns raw bytes, not characters. ```kairo var s = "Hello 📣" s.bytes[0] // byte: 0x48 ('H') O(1) s[6] // char: '📣' codepoint indexing, O(1) **amortized** 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 function return type and as the target of an `unsafe` pointer (`unsafe *void`), but it cannot be used as a type parameter or variable type. ```kairo fn log(msg: string) -> void { // ... } var opaque: unsafe *void = get_handle() // raw, untyped pointer ``` --- ## 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 up to 23 bytes) // 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 = std::create(10) // equivalent to: var ptr: *i32 = std::create(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. ### 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 ``` ### 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}") } ``` ### `op delete` `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. ```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 ``` --- ## `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. They live in the same scope use distinct names like `get()` and `get_mut()` instead: ```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 } ``` --- ## 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 return type is inferred when omitted. ```kairo var add = fn (a: i32, b: i32) -> i32 { return a + b } var double = fn (x: i32) -> i32 { return 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 op: 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 reference `|&|` To capture all variables by reference, append `|&|` after the parameter list. The closure can read and modify the original variables: ```kairo var count = 0 var increment = fn ()|&| { count += 1 // modifies the original count } increment() increment() count // 2 ``` ### Mixed capture `|a, &b|` Specify capture mode per variable. Unqualified names are captured by copy, `&`-prefixed names by reference: ```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 reference } closure(5) // 5 + 10 + 21 = 36 a // still 10 b // 21 ``` ### Capture summary | Syntax | Behavior | |---|---| | *(none)* | All captures by copy (default) | | `\|&\|` | All captures by reference | | `\|a, b\|` | Named variables captured by copy | | `\|&a, &b\|` | Named variables captured by reference | | `\|a, &b\|` | Mixed `a` by copy, `b` by reference | --- ## 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 ``` --- ## `panic` Specifier Closures can be marked `panic` to indicate they may panic. Callers must handle the panic via `try`/`catch`, the same as with regular functions: ```kairo var risky = fn () panic -> i32 { if bad_condition { panic std::Error::Runtime("failed") } return 42 } try { risky() } catch e { std::println(f"Caught: {e}") } ``` See [Panic](/docs/language/panic) for the full panic model. > [!NOTE] > Closures cannot be `async` or `eval`. Asynchronous work should use async free functions or methods. > Compile-time evaluation requires named `eval` functions. See [Concurrency](/docs/language/concurrency) > and [Eval](/docs/language/eval). --- ## 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 reference 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 reference in a closure that escapes the current scope is always an AMT > error. There is no way to promote a reference 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: ```kairo fn outer() -> i32 { var x = 10 fn inner(y: i32) -> i32 = y + 1 // cannot access x inner is a plain function // 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, 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 } // Closure capture by copy (default) var x = 10 var add_x = fn (y: i32) -> i32 { return y + x } // Closure capture all by reference var count = 0 var inc = fn ()|&| { count += 1 } // 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 } // 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 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 run at the end of the enclosing scope in reverse declaration order. --- ## 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.len() == 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 `std::create()`, 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 = std::create(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 `std::create()`: ```kairo var local = Point { x: 1.0, y: 2.0 } // stack var *heap = std::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.len() } } ``` > [!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.len() } } ``` 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?` 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` `void` represents the absence of a value. It is the default return type for functions with no explicit return type. `void` cannot be used as a variable type or type parameter. It can appear in pointer types for opaque, untyped pointers: ```kairo var handle: unsafe *void = get_opaque_handle() var safe_handle: *void = get_tracked_handle() // var x: void // compile error // var y: Foo // 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` `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.len() { 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.len() > 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.len() > 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.len() { 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.len() > 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.len() > 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 // 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<*T>` | Single-owner, exclusive access, freed on drop | | `std::Shared<*T>` | 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 = std::create(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<*Config> = std::create(8080) var shared: std::Shared<*Config> = std::create(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 `std::create()`: ```kairo var stack_val = Config(8080) // stack-allocated var heap_ptr = std::create(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 = std::create(8080) // Smart pointer (explicit) var unique: std::Unique<*Config> = std::create(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. --- ## 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.len() // 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. 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 ``` ### Structs Structs are always trivially copyable via `memcpy`. They have no transfer constructors, no destructors, and no lifecycle categories. Assignment always copies: ```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.len() // 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). 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 ``` 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: ```kairo var p: *i32 { var x = 42 p = &x } *p = 10 // hard error: x is destroyed, p is dangling ``` **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. Where the buffer is mutated through a path AMT cannot trace (an opaque container, a raw FFI call), AMT conservatively errors rather than allowing an unprovable access. See [AMT Analysis scope](/docs/language/amt#analysis-scope-and-the-limits-of-inference). **Stack escape.** A pointer to a stack-allocated value cannot outlive the value. Returning `&x` where `x` is a local is always a hard error. There is no heap allocation behind a stack value, so no smart pointer can rescue it this is why a stack escape is an error rather than a promotion candidate. See [AMT Stack Pointers](/docs/language/amt#stack-pointers). > [!NOTE] > **Data-race detection across threads** is part of the intended model but depends on Kairo's > concurrency design (`spawn`, the memory model, happens-before semantics), which is not yet > finalized. See [Concurrency](/docs/language/concurrency). Once that model is pinned, AMT will > treat concurrent write+read and write+write to the same allocation without synchronization as a > hard error. The semantics here are a target, not a current guarantee. ### 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 once concurrency is finalized 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: ```kairo // Single owner, no aliasing -> Unique var cfg = std::create(8080) return cfg // AMT: cfg has one owner -> Unique ``` ```kairo // Aliased, both pointers escape -> Shared var cfg = std::create(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 = std::create(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#promotion-decision) 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.len() // 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, it is a hard error stack pointers cannot be promoted: ```kairo fn make_closure() -> fn() -> i32 { var x = 42 return fn ()|&| -> i32 { return x } // hard error: x is stack-allocated, closure would outlive it // AMT will not promote stack escapes are never promotable } ``` Capture by transfer avoids this the closure owns its own copy: ```kairo fn make_closure() -> fn() -> i32 { var x = 42 return fn ()|=| -> i32 { return x } // ok: x is copied into the closure, no lifetime dependency } ``` ### 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<*T>`: the object destructor runs at the end of the owning binding's lexical scope, in reverse declaration order. - `std::Shared<*T>`: 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. Destruction is tied to lexical scope, so side effects in destructors (file close, lock release, flush) are predictable from reading the source. --- ## 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. There is no runtime check 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) ``` ```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: 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 analysis pass that tracks pointer lifetimes, determines ownership, and automatically promotes pointers to the appropriate smart pointer type in debug builds. It provides memory safety without requiring lifetime annotations from the programmer. AMT operates on safe pointers (`*T`) only. Raw pointers (`unsafe *T`) are not tracked, and stack allocations are never promoted AMT only promotes heap-allocated pointers created through `std::create()` or equivalent allocator calls. Invalid use of stack pointers (dangling, escaping scope) is always a hard compile error in both debug and release builds. > [!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. The semantics are the target, not the current state. Details may change during implementation. --- ## The Analysis Model AMT performs whole-program analysis. Every translation unit is analyzed and its results are cached in a `.amt` file. The final link pass consumes all `.amt` files to resolve cross-TU pointer flows. The analysis tracks three properties for every safe pointer: - **Provenance**: where the pointer was created and what allocation it points into. - **Lifetime**: the scope in which the pointer is live and the scope in which its target is valid. - **Aliasing**: whether multiple live pointers refer to the same allocation, and how they relate (exclusive, shared, back-reference). AMT does not require annotations. It infers all three properties from usage. When the analysis can prove a pointer's lifetime, provenance, and aliasing unambiguously, it proceeds silently. When it cannot, the behavior depends on the build mode. ### Analysis scope and the limits of inference Within a single function, AMT tracks pointer flow directly. Across function and TU boundaries, it relies on the `.amt` summaries: each function's summary records what it does to every pointer parameter (dereferences it, stores it, aliases it, returns it, frees it). When a pointer is passed into a function, AMT consumes that function's summary rather than re-walking its body. This is what makes cross-TU iterator-invalidation and escape detection possible: the summary for `Vec::push` records that it may reallocate the backing buffer, so any live `*T` into that buffer is flagged at the call site. Where AMT has no summary a pointer stored into an opaque structure it cannot trace, or passed through a code path it cannot analyze it does not guess. It emits a hard error (see [When AMT cannot decide](#when-amt-cannot-decide)). --- ## Debug vs Release AMT behaves differently in debug and release builds. The difference is deliberate: debug builds are for getting things working, release builds are for getting things right. ### Debug mode When AMT determines that a heap pointer outlives its original scope or needs ownership semantics, it **automatically promotes** the pointer to the appropriate smart pointer type (`std::Unique<*T>`, `std::Shared<*T>`, or `std::Weak<*T>`) and emits a **warning** that describes: - Where the promotion happened and why. - What smart pointer type was chosen. - What the programmer should annotate to make the code release-ready. ``` warning[AMT]: pointer 'cfg' promoted to std::Unique<*Config> --> src/server.k:12:16 12 | var cfg = std::create(8080) | ^^^ escapes function scope via return on line 15 note: AMT promoted this pointer because it has a single owner and no aliases help: annotate explicitly for release builds: 12 | var cfg: std::Unique<*Config> = std::create(8080) ``` Debug mode also inserts **runtime safety checks** that are not present in release builds: - Null dereference checks on safe pointer access. - Use-after-free detection via poisoned memory patterns. - Double-free detection. These checks have runtime cost and exist only to catch bugs during development. ### Release mode AMT does **not** auto-promote in release builds. If a pointer requires promotion, AMT emits a **hard compile error** with the same diagnostic information that debug mode would have produced as a warning the location, the reason, the suggested smart pointer type, and the fix: ``` error [AMT]: pointer 'cfg' escapes its scope and requires ownership annotation --> src/server.k:12:16 12 | var cfg = std::create(8080) | ^^^ escapes function scope via return on line 15 note: in debug mode, AMT would promote to: std::Unique<*Config> help: annotate the type explicitly: 12 | var cfg: std::Unique<*Config> = std::create(8080) help: or restructure to avoid the escape ``` This is the "no hidden allocations" guarantee. In a release build, every smart pointer in the binary is visible in the source code. The compiler does not silently change pointer types behind the programmer's back. ### Summary | | Debug | Release | |---|---|---| | Auto-promotion | Yes, with warning | No hard error | | Runtime null checks | Yes | No | | Use-after-free detection | Yes | No | | Double-free detection | Yes | No | | Stack escape | Hard error | Hard error | | Unprovable safety | Hard error | Hard error | --- ## Promotion Decision When AMT determines that a heap pointer needs ownership semantics, it selects one of three smart pointer types based on the pointer's usage pattern across the entire program. ### `std::Unique<*T>` single owner The pointer has exactly one owner at any point in its lifetime. No other live pointer refers to the same allocation. Ownership may transfer between scopes (via return or assignment), but at every point in the program, exactly one binding holds the pointer. ```kairo fn make_config() -> *Config { var cfg = std::create(8080) return cfg // AMT: cfg has one owner, transferred to caller -> Unique } ``` ### `std::Shared<*T>` multiple owners Multiple live pointers refer to the same allocation and AMT cannot prove single ownership. The allocation is reference-counted and freed when the last `Shared` pointer is destroyed. ```kairo fn share_config(a: *Server, b: *Server) { var cfg = std::create(8080) a.config = cfg b.config = cfg // AMT: cfg is aliased across a and b -> Shared } ``` ### `std::Weak<*T>` back-reference A pointer that, if promoted to `Shared`, would form a reference cycle (A -> B -> A). `Weak` pointers do not contribute to the reference count and do not prevent deallocation. Accessing a `Weak` pointer requires checking whether the target is still alive. ```kairo class Node { var child: *Node var parent: *Node // AMT: parent points back to the owner of this node -> Weak } ``` ### No promotion needed Most pointers do not need promotion. A pointer that is created, used, and destroyed within a single scope or whose lifetime is provably contained within its referent's lifetime remains a plain `*T`. No smart pointer overhead is added: ```kairo fn process() { var data = std::create(1024) fill(data) consume(data) std::destroy(data) // AMT: data's lifetime is fully contained, no aliases -> plain *T } ``` ### When AMT cannot decide If AMT cannot prove provenance, cannot determine aliasing, or cannot trace the pointer's flow (e.g., the pointer is stored in an opaque data structure or passed through a code path AMT cannot analyze), it does **not** guess. It emits a hard compile error in both debug and release modes: ``` error [AMT]: cannot determine ownership of pointer 'p' --> src/engine.k:42:12 42 | opaque_container.store(p) | ^ pointer escapes into unanalyzable context help: use 'unsafe *T' if this pointer is managed externally help: use 'forget!(p)' in an unsafe block to drop AMT tracking ``` The distinction: AMT *heuristically promotes* when it has strong evidence (one owner -> Unique, multiple owners -> Shared, cycle -> Weak). AMT *hard errors* when it has insufficient evidence to make any determination. The heuristic is a best-effort assist in debug mode. The hard error is a safety guarantee in both modes. --- ## Stack Pointers AMT does not promote stack-allocated values. Stack memory is managed by scope variables are destroyed at the closing brace in reverse declaration order. Promotion does not apply because there is no heap allocation to transfer ownership of. What AMT does with stack pointers: - **Tracks lifetime**: ensures no pointer to a stack variable outlives the variable. - **Detects escapes**: a pointer to a stack local returned from a function is always a hard error. - **Detects dangling**: a pointer used after the referent's scope has ended is always a hard error. ```kairo fn bad() -> *i32 { var x = 42 return &x // hard error: x is stack-allocated and destroyed at end of bad() // AMT will not promote &x stack escapes are never promotable } fn also_bad() { var p: *i32 { var x = 42 p = &x } *p = 10 // hard error: x is destroyed at the closing brace, p is dangling } ``` These are hard errors in both debug and release. No warning, no promotion, no workaround except restructuring the code or using `unsafe *T` with manual lifetime management. There is no heap allocation behind a stack value, so no smart pointer can rescue an escaping stack pointer. This is why `return &local` is a hard error rather than a promotion candidate the rule the [Ownership](/docs/language/ownership) page refers to as "stack escape." --- ## Allocator Interaction AMT is aware of the allocator system. Kairo supports two kinds of allocators: ### Global allocator The default allocator used by `std::create()` and all standard library heap operations. It can be overridden with `@mem::set_allocator(MyAllocator)` at the top of a file. **Intentional friction**: `@mem::set_allocator` must appear at the top of **every file in the dependency graph** that the override affects. If a library sets a global allocator, every file that transitively depends on that library must also carry the annotation. This makes global allocator overrides visible and discourages libraries from changing the global allocator library authors should use scoped allocators instead. ### Scoped allocator A scoped allocator is an allocator with RAII semantics it frees all memory it allocated when it goes out of scope. Set one with `@mem::set_scoped_allocator(MyAllocator)` on a function or block. ```kairo @mem::set_scoped_allocator(ArenaAllocator) fn process_frame() { var a = std::create(vertices) var b = std::create(pixels) // a and b are allocated through ArenaAllocator // ArenaAllocator frees everything when process_frame returns } ``` AMT tracks scoped allocator boundaries. A pointer allocated through a scoped allocator that escapes the allocator's scope is a **hard error** the allocator will free the memory when the scope ends, so the escaped pointer would dangle: ```kairo @mem::set_scoped_allocator(ArenaAllocator) fn bad() -> *Config { var cfg = std::create(8080) return cfg // hard error: cfg was allocated by ArenaAllocator, which frees at end of bad() // returning cfg creates a dangling pointer no promotion can fix this } ``` This is not a promotable situation. No smart pointer can rescue a pointer whose backing memory is freed by the allocator. The only fix is restructuring: allocate through the global allocator, or move the scoped allocator boundary outward. ### Freestanding allocator A variant of the scoped allocator interface for embedded and bare-metal targets. Freestanding allocators conform through `static` functions only no pointer indirection, no vtable. The compiler replaces `std::create()` calls with direct static calls: ```kairo // Instead of: cur_allocator->alloc(...) // Compiler emits: FreestandingAlloc::alloc(sizeof(T) * N) + placement construction ``` AMT treats freestanding allocators identically to scoped allocators for lifetime tracking purposes. The optimization is purely a codegen concern. --- ## Destruction Timing When AMT destroys a value, *where* it places the destruction depends on whether the type's destructor is observable. ### Trivial destructors A type whose destructor has no observable side effects (no user-defined `op delete`, no member with one. Just memory to reclaim) is destroyed at **last use**. AMT places the `std::destroy` as early as the final use permits: ```kairo var foo = std::create() // SomeClass has a trivial destructor use(foo) // AMT inserts std::destroy(foo) here foo is never used again std::println(10) ``` This is safe because there is nothing observable to reorder. The only effect is reclaiming the backing memory, and reclaiming it after the last use is indistinguishable from reclaiming it at the scope brace. ### Non-trivial destructors A type with a side-effecting destructor (a user-defined `op delete`, or any member with one. File close, lock release, flush, network disconnect) is destroyed 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 = std::create(8080) // Config has a side-effecting destructor use(cfg) std::println(10) } // cfg's destructor runs here, at the closing brace. Not after use(cfg) ``` This is the "predictable from source" guarantee: for any type whose destruction is observable, the side effect fires at the scope brace exactly as written. `finally` blocks, lock guards, and NON_TRANSFER scope guards all rely on this. Their effects are tied to lexical scope, not to whenever the optimizer last touched the value. ### Unique and stack-bound objects For `std::Unique<*T>` and non-promoted values, the object destructor runs at the owning binding's closing brace, in reverse declaration order relative to other bindings in the same scope. ### Shared objects `std::Shared<*T>` separates two events: - **Refcount decrement** happens at each `Shared` binding's own scope boundary, in reverse declaration order like any other binding. - **Object destruction** (the pointed-to object's destructor, then deallocation) happens exactly once, when the *last* live `Shared` reference's scope ends and the count reaches zero. These are not the same guarantee. The per-binding decrement order is lexical and deterministic. The object destructor fires at the last owner, which may not be the last-declared binding in any single scope. For shared objects, reason about destruction timing as "when the final owner goes away," not "in reverse declaration order of the binding I'm looking at." `std::Weak<*T>` scope exit does not affect the reference count. The `Weak` pointer simply becomes invalid if the target has already been freed. --- ## Copy Elision For COPY types, AMT may emit a move instead of a copy when it proves the source is not used after the transfer. AMT does not elide if the move constructor is deleted, and the programmer does not opt into or out of this. Elision is permitted only when it is unobservable. The source binding would be destroyed at its own scope exit either way; elision means its destructor runs at the transfer point instead. AMT performs the elision **only when the destructor's observable effects do not depend on timing** that is, when the type's destructor is trivial, or its effects (a `free`, a refcount decrement) produce the same observable program behavior whether they fire at the transfer or at scope exit. If the destructor has timing-sensitive side effects a file 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. This keeps the language's core promise intact: destructor side effects are predictable from reading the source. Elision is a silent optimization precisely because it is only applied where it cannot be observed. --- ## C/C++ Interop AMT's behavior at the FFI boundary depends on the C++ declaration's type signature. ### Smart pointer parameters C++ functions that use smart pointer types are mapped automatically: | C++ type | Kairo AMT type | Requires `unsafe` | |----------------------|---------------------------|-------------------| | `std::unique_ptr` | `std::Unique<*T>` | No | | `std::shared_ptr` | `std::Shared<*T>` | 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++ function's expected type is a compile error. ### Raw pointer parameters C++ functions that take raw pointers (`T*`, `void*`) require an `unsafe` block. AMT does not track the pointer across the FFI boundary the C++ side is opaque. Use `forget!()` to release the pointer from tracking before handing it to C++. See [Unsafe](/docs/language/unsafe#forget) for the full mechanics of `forget!()` and FFI ownership transfer. ### Shallow C++ analysis For raw pointer FFI calls, AMT performs a **one-level heuristic analysis** of the C++ function body (if the source is visible via the imported header). If the function only dereferences the pointer without aliasing, storing, or forwarding it, AMT may classify the call as safe and not require an `unsafe` block. This analysis does not recurse AMT will not walk the C++ call graph beyond the immediate function. If the C++ function's body is not visible (forward-declared, in a compiled library), AMT treats all raw pointer parameters as opaque and requires `unsafe`. --- ## Generic Code AMT tracks through generic instantiations. When a generic function is monomorphized, AMT analyzes the concrete instantiation with full type information: ```kairo fn take_ownership(x: *T) { // AMT knows x is the sole pointer to the allocation after this call } fn caller() { var cfg = std::create(8080) take_ownership(cfg) // AMT: cfg's ownership transferred into take_ownership // Using cfg after this point is a compile error } ``` AMT understands lifecycle categories. If `T` has a `@move` transfer constructor, AMT tracks ownership transfer through the move. If `T` is `@copy`, AMT knows both the source and destination are live after the copy. Generic code is not an opaque boundary AMT analyzes each monomorphization as if it were a concrete function. --- ## What AMT Does Not Do - **Garbage collection**: AMT is a compile-time analysis. There is no runtime garbage collector, no tracing, no mark-and-sweep. Reference counting for `Shared` pointers is the only runtime cost, and it only exists when multiple ownership is required. - **Runtime borrow checking**: AMT does not insert runtime aliasing checks. All aliasing analysis is done at compile time. If AMT cannot prove aliasing safety statically, it is a compile error. - **Lifetime annotations**: AMT does not require `'a`-style lifetime parameters. The whole-program analysis infers lifetimes from usage. There is no lifetime polymorphism AMT analyzes concrete lifetimes, not abstract ones. - **Automatic reference counting everywhere**: AMT promotes to `Shared` (which uses refcounting) only when it determines multiple ownership exists. Single-owner pointers are promoted to `Unique`, which has no refcount overhead. Pointers with fully contained lifetimes are not promoted at all. - **Cross-language analysis**: AMT does not analyze C++ code beyond the one-level heuristic described in [C/C++ Interop](#cc-interop). C++ code is treated as opaque beyond the immediate function signature and body. --- ## `.amt` Files Each translation unit produces a `.amt` file alongside its object file. The `.amt` file contains a summary of every pointer's provenance, lifetime bounds, and promotion decisions for that TU. The link-time pass reads all `.amt` files to resolve cross-TU flows. `.amt` files are cached and invalidated when the source or any of its dependencies change. They do not need to be committed to version control the build system regenerates them. > [!IMPORTANT] > The `.amt` file format and the details of the link-time resolution pass are still being > finalized. The information above describes the intended architecture. Specifics may change > before 1.0. --- ## Summary | Type | Tracked? | Promoted? | Error on escape? | |------------------|-----------|---------------------------|-------------------------------------| | Plain *T (stack) | Yes | Never | Always (hard error) | | Plain *T (heap) | Yes | Debug: yes \| Release: no | Release: hard error (must annotate) | | unsafe *T | No | Never | No (programmer's problem) | | Scoped alloc ptr | Yes | Never | Always (hard error) | | FFI smart ptr | Yes | Mapped 1:1 | Type mismatch = error | | FFI raw ptr | No | Never | Requires unsafe block | ```kairo // Stack pointer AMT enforces lifetime, no promotion var x = 42 var p: *i32 = &x // Heap pointer AMT tracks and may promote (debug) or require annotation (release) var cfg = std::create(8080) // Explicit annotation works in both debug and release var cfg: std::Unique<*Config> = std::create(8080) // Scoped allocator AMT enforces no escape @mem::set_scoped_allocator(ArenaAllocator) fn frame() { var mesh = std::create(data) // mesh cannot outlive frame() hard error if it tries } ``` --- ## 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 = std::create(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 = std::create(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<*T>`, `std::shared_ptr` maps to `std::Shared<*T>`, `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 = std::create(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 = std::create(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 = std::create(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.len(), "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 = std::create(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 = std::create(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.len() == 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.len() == 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 { 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 } fn fatal(msg: string) -> ! { loop { } // 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.len() == 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 { try { return complex_operation(a, b) } catch e: std::Error::Runtime { return -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. > [!NOTE] > `eval for` fully computes the loop at compile time and embeds the result. For large iteration > counts, this increases binary size (the unrolled result is stored as data). Use regular `for` > loops for runtime iteration. --- ## 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" ``` ### 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) ```kairo enum Mode { Debug, Release, Test } eval BUILD_MODE = Mode::Release ``` ### Types that are NOT eval-compatible Classes with constructors, types with destructors, heap-allocated types (`[T]`, `{K: V}`, `{T}`), and any type involving pointers cannot 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() // runtime: reads a file eval MAX_CONNECTIONS = 1024 // compile time: baked into binary ``` --- ## Eval and Where Clauses `eval` expressions are valid in `where` clauses. When a `where` clause contains only `eval`-compatible expressions, it is checked at compile time: ```kairo fn stack_alloc() -> T where sizeof(T) <= 4096 { // guaranteed at compile time: T fits on the stack } ``` See [Where Clauses](/docs/language/bounds) for the full constraint system. --- ## 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. Each `.k` file is a module. A directory containing a `.k` file with the same name as the directory is a library its entry file re-exports contents from sibling files in the directory. --- ## File-to-Module Mapping Each `.k` file is a module named after the file (without the extension). A directory with an entry file forms a library: ``` project/ main.k math/ math.k <- library entry for "math" vector.k matrix.k internal/ helpers.k ``` ```kairo // main.k ``` The library entry file (`math/math.k`) controls what the library exports. Files in the directory are accessible via `libraryname::filename`. --- ## Import Syntax Imports bring names from other modules into the current scope: ```kairo // Import an entire module/library // Import a specific item // Import multiple items // Import everything from a module (wildcard) // Rename on import // Source file only (disallow library resolution) ``` ### `import module` `import module` restricts the import to source files only it will not resolve library entry files. Use this when you need a specific file and want to avoid ambiguity with a library of the same name. --- ## Visibility on Imports Imports can have visibility modifiers. A `priv import` brings names into the current file but does not re-export them to files that import the current module: ```kairo // network.k pub import std::collections // re-exported to anyone importing network priv import std::internal // only available in this file prot import std::platform // available to files in the same directory/library ``` By default, imports are public anything you import is visible to modules that import you. Use `priv import` for implementation details that should not leak. --- ## Top-Level Declarations All top-level declarations (functions, classes, structs, enums, variables) can have visibility modifiers: ```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 ``` The default visibility for top-level declarations is `pub`. Use `priv` to restrict access to the current file and `prot` to restrict access to the current file and modules that are in the same directory or library. --- ## Modules (Namespaces) The `module` keyword creates a namespace within a file. It is equivalent to C++'s `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 { // declarations here are scoped but not namespaced var internal_state: i32 = 0 } ``` ### Module visibility Modules themselves can have visibility modifiers: ```kairo pub module api { // accessible from anywhere fn handle_request(req: Request) -> Response { ... } } priv module cache { // only accessible within this file var store: {string: Data} = {} fn lookup(key: string) -> Data? { ... } } prot module platform { // accessible within this file and sibling modules in the same library fn detect_os() -> string { ... } } ``` The default module visibility is `pub`. --- ## Module Extending A module can be extended across multiple files. The second file imports the module and reopens it to add more declarations: ```kairo // encoding.k pub module codec { pub fn encode_base64(data: [byte]) -> string { ... } pub fn decode_base64(input: string) -> [byte] { ... } } ``` ```kairo // compression.k pub module codec { // visibility must match the original declaration pub fn compress(data: [byte]) -> [byte] { ... } pub fn decompress(data: [byte]) -> [byte] { ... } } ``` ```kairo // main.k compression::codec::encode_base64(data) // defined in encoding.k compression::codec::compress(data) // defined in compression.k ``` Reopening a module with a different visibility than the original is a compile error. --- ## Scope Resolution (`::`) The `::` operator resolves names across all scoping contexts: | Context | Example | |---|---| | Module access | `std::println(...)` | | Class static members | `Counter::count` | | Enum variants | `Direction::North` | | Nested types | `Packet::Header` | | Base class method calls | `Base::method(self)` | | Library submodules | `math::vector::cross_product(...)` | `::` works uniformly across all contexts. There is no separate syntax for module access vs type member access. --- ## The Standard Library (`std`) `std` is not automatically imported. The user must import it explicitly: ```kairo std::println("hello") std::create(42) ``` Individual items can be imported directly: ```kairo println("hello") ``` > [!NOTE] > The core language primitives (`i32`, `string`, `bool`, etc.) and built-in syntax (`if`, `for`, > `match`, etc.) are available without any import. Only standard library functions and types > (`std::println`, `std::Error`, `std::Shared`, etc.) require an import. --- ## C/C++ Header Imports C and C++ headers are imported via the `ffi` keyword. The header is parsed by the compiler and all exported declarations become available: ```kairo ffi "c++" import "graphics.hh" ffi "c" import "legacy_api.h" ``` By default, all declarations from the header are dumped into the current namespace (matching C++ `#include` behavior). Use `as` to namespace the import: ```kairo ffi "c++" import "graphics.hh" as gfx gfx::create_window(800, 600) gfx::RenderContext() ``` ### C++ `std` namespace collision If a C++ header defines names in the `std` namespace, those names collide with Kairo's `std` module. The C++ standard library is accessible through the `libcxx` module: ```kairo std::println("Kairo's println") cout << "C++ cout" << endl ``` Inside `inline "c++"` blocks, `std::` resolves to Kairo's standard library (the block emits inside `namespace kairo`). Use `libcxx::` imports for C++ standard library types. See [C/C++ Interop](/docs/language/c-c++) for the full FFI model. --- ## Circular Imports Circular imports are a compile error. If module A imports module B and module B imports module A, the compiler rejects the cycle: ```kairo // a.k // b.k ``` Break circular dependencies by extracting shared declarations into a third module that both A and B import. --- ## Library Entry Files A directory with a `.k` file matching the directory name acts as a library. The entry file controls exports: ```kairo // network/network.k (library entry) priv import dns // network/dns.k not re-exported // Anything defined or publicly imported here is accessible via "import network" pub fn connect(host: string, port: i32) -> Connection { ... } ``` ```kairo // main.k network::connect("localhost", 8080) // from network.k network::tcp::listen(8080) // from network/tcp.k // network::dns::resolve("...") // compile error: dns is priv imported ``` --- ## Summary ```kairo // File imports // Visibility on imports pub import std::collections priv import std::internal // Modules (namespaces) module serialization { pub fn to_json(data: Config) -> string { ... } } // Module extending pub module codec { pub fn compress(data: [byte]) -> [byte] { ... } } // C++ header import with namespace ffi "c++" import "engine.hh" as engine engine::initialize() // Standard library std::println("hello") // Scope resolution (uniform) std::println(...) // module Counter::count // static member Direction::North // enum variant Packet::Header { ... } // nested type ``` --- ## 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 `std::create()` 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 = std::create() 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 | | `std::create()` | 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 "c++"` 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 C++ For small amounts of C++ that don't warrant a separate header, use `inline "c++"` blocks directly in Kairo source. ```kairo fn main() { inline "c++" { // std:: here refers to Kairo's standard library, not libstdc++ std::println("Hello from inline C++!"); // C++ standard library types use the libcxx:: prefix cout << "Hello from libcxx!" << endl; } } ``` > [!WARNING] > Inside `inline "c++"` blocks, `std::` resolves to the **Kairo** standard library > (the block emits inside `namespace kairo`). Access the C++ standard library through the `libcxx` module, > importing the specific header you need (e.g., `import libcxx::vector;`). See [Modules](/docs/modules) for > more on imports. --- ## 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 = std::create(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. --- ======================================================================== 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.len() == 0 { panic ParseError("empty request", 0) } // Parse request line: "GET /path HTTP/1.1" var parts = lines[0].split(" ") if parts.len() < 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.len() && lines[i].len() > 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.len() { 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.len()}\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<*T>`, `std::Shared<*T>`, or `std::Weak<*T>`) when it determines the pointer needs to outlive its original scope: ```kairo fn make_config() -> *Config { var cfg = std::create(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<*Config> 2 | var cfg: std::Unique<*Config> = std::create(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. ### Inline C++ for the edge cases When you need a few lines of C++ without a separate header: ```kairo fn main() { inline "c++" { cout << "Hello from C++!" << endl; } } ``` ## 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. ---