C++ can #include a .kro file. Kairo can call any C++ library with zero bindings. The boundary doesn't exist.
import std::Parallel
import std::Error::DimensionError
import std::Interfaces::Numeric
fn <T impl Numeric> dot(a: [T], b: [T]) -> T {
if a.length() != b.length() {
panic DimensionError("dot requires equal lengths")
}
var result = Parallel::reduce(
policy: Parallel::Policy::SIMD,
range: 0..(a.length()),
init: 0 as T,
step: fn (i: usize) -> T = a[i] * b[i],
combine: fn (x: T, y: T) -> T = x + y
)
return result ?? 0
}
test "dot product of two vectors" {
var a, b = [1, 2, 3, 4, 5], [6, 7, 8, 9, 10]
var result = dot(a, b)
assert result == 130, f"excepted 130, got {result}"
} Alpha stage · Compiler written in C++ · Self-hosting in progress
import std // import only what you need, nothing more
// No main() required top-level code runs immediately
// or is callable when imported as a module.
const name = "Kairo"
var count = 0
for i in 0..5 {
std::print(f"Hello, {name}! Count is {count}.")
count++
}
std::print(f"Ran {count} times.") // Call C++ code directly from Kairo with perfect ABI compatibility.
// my_cpp_lib.hpp contains:
// namespace my_cpp_lib {
// int add(int a, int b) { return a + b; }
// std::string greet(std::string name) { return "Hello, " + name + "!"; }
// }
//
// C++ namespaces map directly to Kairo module syntax.
// Functions with no namespace are callable without qualification.
ffi "c++" import "my_cpp_lib.hpp" // import a C++ header directly as a module.
// no wrapper generation, no binding step.
const sum = my_cpp_lib::add(3, 4) // direct call, zero overhead.
var greeting = my_cpp_lib::greet("Kairo")
greeting += " Welcome to seamless interop!" // C++ std::string, manipulated
// directly in Kairo, no conversion.
std::print(greeting) // Hello, Kairo! Welcome to seamless interop! // Call Kairo functions directly from C++ with full ABI compatibility.
// Compile the Kairo module with `kairo -cc --kro-impl-emit kairo_ffi.cpp`
// to expose it to C++.
// Without -cc, use `kairo --emit-bindings` to generate C++ or C headers
// for shipping a Kairo library to C++ consumers.
//
// kairo_ffi.kro:
// import std
// fn add(a: i32, b: i32) -> i32 { return a + b }
// fn greet(name: string) -> string { return f"Hello, {name}!" }
//
// Kairo wraps the module under its filename as the exposed C++ namespace.
#include "kairo_ffi.kro" // include the Kairo module directly.
#include <iostream>
int main() {
int sum = kairo_ffi::add(3, 4); // direct call, no overhead.
std::string greeting = kairo_ffi::greet("C++");
greeting += " Welcome to seamless interop!";
std::cout << greeting << std::endl; // Hello, C++! Welcome to seamless interop!
return 0;
} import std
struct Buffer {
const size: usize
var data: *u8 // safe pointer, bounds checked automatically
// unless you explicitly opt out.
fn op new (self, size: usize) {
self.data = std::Memory::alloc(size)
self.size = size // only the constructor can set size.
}
fn op delete (self) { // required since we own an allocation.
std::Memory::free(self.data)
}
}
var buf = Buffer(1024) // 1KB on the heap.
// destructor runs automatically when buf goes out of scope.
buf.data[0] = 42 // bounds checked; panics at runtime if out of range.
// we can also set up the compiler to make out-of-bounds
// do a no-op or return a default value instead of panicking.
try {
buf.data[buf.size] = 99 // out of bounds
} catch std::Error::OutOfBounds {
std::print("Caught out of bounds access!")
} import std
import json // imagine this is a complex JSON library
@json::Serializable
struct Config {
host: string,
port: u16,
workers: u32 = 4,
}
fn <T impl json::Serializable> json_parse(raw: string) panic -> T;
// imagine this has a complex implementation that can panic with Json::Error::Parse
fn read_file(path: string) panic -> string;
// imagine this has a complex implementation that can panic with Fs::Error::NotFound,
// or Fs::Error::PermissionDenied
fn parse_config(path: string) panic -> Config {
var raw: string
raw = read_file(path)
return json_parse(raw)
catch Fs::Error::NotFound {
std::print(f"Config file not found: {path}")
return Config { host: "localhost", port: 8080 } // recover with a default.
} catch Json::Error::Parse as e {
std::print(f"Failed to parse config with error: {e}")
return Config { host: "localhost", port: 8080 } // recover with a default.
} catch e { // We don't care about Fs::Error::PermissionDenied thats the
// caller's problem, if we don't catch all possible panics,
// the compiler will error, we can catch it and re-panic.
panic e // propagate unexpected errors without wrapping or losing context.
}
}
fn start_server(path: string) -> Config {
try {
const config = parse_config(path)
// parse_config is marked `panic`, so we must catch its errors that
// can reach this scope (in this case, Fs::Error::PermissionDenied).
std::print(f"Starting {config.host}:{config.port} {config.workers}workers...")
return config
} catch Fs::Error::PermissionDenied {
std::print(f"Permission denied when reading config file: {path}")
return Config { host: "localhost", port: 8080 } // recover with a default.
} // every reachable panic is handled, no panic is needed in this function.
} // callers of start_server need no error handling at all. Readable from day one
var count = 0 the compiler knows it's an integer. Annotate when you want to, not because you have to. f"" strings let you embed expressions directly. No concatenation, no format specifiers. main() boilerplate. Code at the top level runs on execution and can still be imported as a module. Drop in, not rewrite
Adopt Kairo without touching your C++ codebase
kairo -cc, your Kairo module is includable directly from C++. No build system changes, no extra steps. -cc, use --emit-bindings to generate clean C++ or C headers. Consumers see a normal library. They never need to know it's Kairo. Control without the footguns
buf goes out of scope, or earlier if the function returns or panics. No leaks, no double frees, no annotations required. The compiler knows what can fail
panic and the compiler traces every failure reachable in its entire call graph. Nothing hidden, nothing implicit. catch e { panic e } to handle what you care about and push the rest upstream. The original panic travels intact, no wrapping, no lost context. One command gets you the compiler, formatter, LSP, and package manager. No separate installs, no configuration.
Compiler, standard library, and toolchain are on GitHub. Apache 2.0 with runtime exception.
Apache 2.0 License · Kairo Software Foundation · Open Source