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
// main.k
import math              // imports the math library (math/math.k)
import math::vector      // imports math/vector.k specifically
import math::matrix      // imports math/matrix.k specifically

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:

// 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

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:

// 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:

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:

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:

module {
    // declarations here are scoped but not namespaced
    var internal_state: i32 = 0
}

Module visibility

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.


Module Extending

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.


Scope Resolution (::)

The :: operator resolves names across all scoping contexts:

ContextExample
Module accessstd::println(...)
Class static membersCounter::count
Enum variantsDirection::North
Nested typesPacket::Header
Base class method callsBase::method(self)
Library submodulesmath::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:

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/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:

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()

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:

import std                          // Kairo standard library
import libcxx::vector               // C++ std::vector
import libcxx::iostream::{cout, endl}

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 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:

// 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.


Library Entry Files

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

Summary

// 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