Skip to content

Modules & Imports

Kōdo programs can span multiple files. Each file contains a single module, and modules can import functions and types from other modules.

Every .ko file contains exactly one module:

module my_module {
meta {
purpose: "What this module does"
version: "0.1.0"
}
// functions, types, etc.
}

The module name should match the filename (e.g., math.ko contains module math).

Use import to bring another module’s definitions into scope:

import math

This makes all functions and types defined in math.ko available in the current module.

When the compiler sees import math, it looks for math.ko in the same directory as the importing file. The imported module is compiled first, and its exported definitions are made available.

math.ko — a utility module:

module math {
meta {
purpose: "Math utilities"
version: "0.1.0"
}
pub fn add(a: Int, b: Int) -> Int {
return a + b
}
pub fn multiply(a: Int, b: Int) -> Int {
return a * b
}
}

main.ko — uses the math module:

module main {
import math
meta {
purpose: "Main program"
version: "0.1.0"
}
fn main() {
let sum: Int = add(3, 4)
let product: Int = multiply(5, 6)
print_int(sum)
print_int(product)
}
}

Compile and run:

Terminal window
cargo run -p kodoc -- build main.ko -o main
./main

Output:

7
30

The compiler resolves the import, compiles math.ko, and links everything into a single binary.

Kōdo’s standard library provides two foundational types that are available in every program without an explicit import:

  • Option<T> — represents an optional value (Some(T) or None)
  • Result<T, E> — represents success or failure (Ok(T) or Err(E))

These types are automatically injected before your code is type-checked. You can use them immediately:

module my_program {
meta {
purpose: "Using stdlib types"
version: "0.1.0"
}
fn maybe_double(x: Int) -> Option<Int> {
if x > 0 {
return Option::Some(x * 2)
}
return Option::None
}
fn main() {
let result: Option<Int> = maybe_double(21)
match result {
Option::Some(v) => { print_int(v) }
Option::None => { println("nothing") }
}
}
}

Output: 42

Kōdo provides built-in collection types available in every program:

A dynamic array of elements, accessed via free functions:

let nums: List<Int> = list_new()
list_push(nums, 10)
list_push(nums, 20)
list_push(nums, 30)
let len: Int = list_length(nums) // 3
let first: Int = list_get(nums, 0) // 10
let has: Bool = list_contains(nums, 10) // true
FunctionDescription
list_new()Create a new empty list
list_push(list, value)Append a value to the end
list_get(list, index)Get value at index
list_length(list)Number of elements
list_contains(list, value)Check if value exists
list_pop(list)Remove and return the last element
list_remove(list, index)Remove element at index
list_set(list, index, value)Set value at index
list_slice(list, start, end)Get a sub-list from start to end (exclusive)
list_sort(list)Sort the list in ascending order (in place)
list_join(list, separator)Join list elements into a String with separator

A generic key-value hash map. The type is determined by the annotation on the let binding:

// Map<Int, Int> — fully supported
let scores: Map<Int, Int> = map_new()
map_insert(scores, 1, 100)
let val: Int = map_get(scores, 1) // 100

Note: Map<Int, Int> is fully supported. Other type combinations (Map<String, Int>, Map<Int, String>, Map<String, String>) pass type checking but may produce link errors at build time. Full Map type variant support is being implemented.

All functions work with any Map<K, V> where K, V are Int or String:

FunctionDescription
map_new()Create a new empty map (type from annotation)
map_insert(m, k, v)Insert or update a key-value pair
map_get(m, k)Get value by key
map_contains_key(m, k)Check if key exists
map_length(m)Number of entries
map_remove(m, k)Remove a key-value pair
map_is_empty(m)Check if map is empty

Splits a string by a separator, returning a List<String>:

let parts: List<String> = "a,b,c".split(",")

Parses a string as an integer. Returns 0 on failure:

let valid: Int = "42".parse_int() // 42
let bad: Int = "hello".parse_int() // 0

You can use :: as a path separator for imports, particularly useful for standard library modules:

import std::option
import std::result

The dot separator (.) is also supported for backward compatibility:

import math.utils

Both forms resolve to the same module.

To bring specific names from a module into scope, use the from...import syntax:

from std::option import Some, None
from math::utils import add, multiply

This imports only the named items, keeping the local scope clean.

When importing a module, you can use qualified calls with dot notation or :::

import math
let result: Int = math.add(1, 2)
let result2: Int = math::add(1, 2)

This is equivalent to calling add(1, 2) directly — the module prefix makes the origin explicit.

When you compile a Kōdo program, the compiler emits a compilation certificate alongside the binary. For hello.ko, the compiler creates hello.ko.cert.json:

{
"module": "hello",
"purpose": "My first Kōdo program",
"version": "0.1.0",
"contracts": {
"requires_count": 1,
"ensures_count": 1,
"mode": "static",
"static_verified": 1,
"runtime_checks_needed": 1,
"failures": 0
},
"functions": ["main", "validate"],
"confidence": [
{
"name": "main",
"declared": 0.95,
"effective": 0.90,
"callees": ["validate"]
},
{
"name": "validate",
"declared": 0.90,
"effective": 0.90,
"callees": []
}
],
"source_hash": "sha256:...",
"binary_hash": "sha256:...",
"certificate_hash": "sha256:..."
}

This certificate is a machine-readable record of what was compiled. AI agents can use certificates to verify:

  • What the module claims to do (from meta)
  • How many contracts are in place, and which were statically verified vs runtime-checked
  • Per-function confidence scores (declared and effective after transitive propagation)
  • Whether the source has changed since the last compilation

Use kodoc audit <file> --json for a consolidated report combining confidence, contracts, and annotations with a deployability verdict.