-
Notifications
You must be signed in to change notification settings - Fork 3
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.
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/
module.k <-- library entry for "math"
vector.k
matrix.k
internal/
helpers.k
// main.k
import math // imports the math library (math/math.k)
import math::vector // looks for vector qualification in math/module.k
import math::matrix // looks for matrix qualification in math/module.k
// math/module.k
pub import vector // these are relative to the module
pub import matrix // re-exports matrix
The library entry file (math/module.k) controls what the library exports. Files in the directory
are accessible via libraryname::filename.
Imports bring names from other modules into the current scope:
// Import an entire module/library
import math
// Import a specific item
import math::Vector3
// Import multiple items
import math::{Vector3, Matrix4, cross_product}
// Import everything from a module (wildcard)
import math::*
// Rename on import
import math as m
import math::{Vector3 as Vec3, Matrix4 as Mat4}
// Source file only (disallow library resolution)
import module math
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.
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:
// 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.
All top-level declarations (functions, classes, structs, enums, variables) can have visibility modifiers:
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.
The module keyword creates a namespace within a file. It is equivalent to C++'s namespace:
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)
A module without a name creates a scope for grouping declarations without introducing a named namespace:
module {
// declarations here are scoped but not namespaced
var internal_state: i32 = 0
}
Modules themselves can have visibility modifiers:
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.
A module can be extended across multiple files. The second file imports the module and reopens it to add more declarations:
// encoding.k
pub module codec {
pub fn encode_base64(data: [byte]) -> string { ... }
pub fn decode_base64(input: string) -> [byte] { ... }
}
// compression.k
import encoding::codec
pub module codec {
// visibility must match the original declaration
pub fn compress(data: [byte]) -> [byte] { ... }
pub fn decompress(data: [byte]) -> [byte] { ... }
}
// main.k
import compression
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.
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.
std is not automatically imported. The user must import it explicitly:
import std
std::println("hello")
std::create<i32>(42)
Individual items can be imported directly:
import std::{println, create}
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 and C++ headers are imported via the ffi keyword. The header is parsed by the compiler and all
exported declarations become available:
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:
ffi "c++" import "graphics.hh" as gfx
gfx::create_window(800, 600)
gfx::RenderContext()
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:
import std // Kairo standard library
import libcxx::vector // C++ std::vector
import libcxx::iostream::{cout, endl} // kairo module based wrappers arround C++ std
ffi "c++" import "iostream" // gets impoted into the libcxx namespace instead of std::
std::println("Kairo's println")
cout << "C++ cout" << endl
See C/C++ Interop for the full FFI model.
Circular imports are a compile error. If module A imports module B and module B imports module A, the compiler rejects the cycle:
// a.k
import b // compile error if b.k imports a
// b.k
import a // circular dependency
Break circular dependencies by extracting shared declarations into a third module that both A and B import.
A directory with a .k file matching the directory name acts as a library. The entry file
controls exports:
// network/network.k (library entry)
import tcp // network/tcp.k
import udp // network/udp.k
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 { ... }
// main.k
import network
network::connect("localhost", 8080) // from network.k
network::tcp::listen(8080) // from network/tcp.k
// network::dns::resolve("...") // compile error: dns is priv imported
// File imports
import math
import math::Vector3
import math::{Vector3, Matrix4}
import math::*
import math as m
// Visibility on imports
pub import std::collections
priv import std::internal
// Modules (namespaces)
module serialization {
pub fn to_json(data: Config) -> string { ... }
}
// Module extending
import encoding::codec
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
import std
std::println("hello")
// Scope resolution (uniform)
std::println(...) // module
Counter::count // static member
Direction::North // enum variant
Packet::Header { ... } // nested type
This wiki mirrors the language reference at kairolang.org/docs. To edit a page, edit the source at kairo-web/src/content/docs/language changes sync automatically.
Start here: Primitives
1. Fundamentals
2. Functions & Control Flow
3. Types
4. Modules & Metaprogramming
5. Memory & Safety
6. Interop & Concurrency