ktm v0.28.3

Modules and packages

A module is a directory of .ktm files with a name. A program imports it and uses the names the module marks pub, with the module's name in front: geo.area(5). A package is a module that lives in its own git repository and is imported by its path, as in Go. This chapter covers writing a module, what is visible where, methods and private fields across modules, imports and how modules are found, start-up order, the prelude, packages with ktm.mod, ktm get, and the prebuilt package cache.

Importing a module

The standard library is a set of modules. import makes one available by its name:

import str;

i32 main() {
    scoped String s = str.from("modules");
    s.append(" and packages");
    printf("%.*s\n", s.len, s.ptr);
    return 0;
}
modules and packages

Several modules are imported in one line, separated by commas:

import cstr, mem;

i32 main() {
    u8 *copy = cstr.dup("kept");
    printf("%s %d\n", copy, cstr.len(copy));
    mem.free(copy);
    return 0;
}
kept 4

A module in a subdirectory is imported with dots, and used by its last name: import net.http; loads the directory net/http, whose files say module http;, and its names are http.get, http.Request.

import name = path; gives the module another name in this file:

import s = str;

i32 main() {
    scoped String greeting = s.from("aliased");
    printf("%.*s\n", greeting.len, greeting.ptr);
    return 0;
}
aliased

An import that is never used is a warning, so leftover imports don't pile up.

Writing a module

A module is a directory. Every file in it starts with module and the directory's name. Its pub declarations are what importers see; the rest is private to the module.

shapes/
├── main.ktm
└── geo/
    ├── shapes.ktm
    └── helpers.ktm
// geo/shapes.ktm
module geo;

pub struct Point { i64 x, y; };
pub enum Kind { square, circle };
pub const i64 SIZE = 4;

pub i64 area(Kind k, i64 side) {
    if (k == Kind.square) {
        return square(side);
    }
    return 3 * square(side);
}
// geo/helpers.ktm
module geo;

i64 square(i64 n) {
    return n * n;
}
// main.ktm
import geo;

i32 main() {
    geo.Point p = {1, 2};
    i64 cells[geo.SIZE];
    printf("%d %d %d\n", geo.area(geo.Kind.square, 5), p.y, sizeof(cells));
    return 0;
}
$ ktm main.ktm -o shapes && ./shapes
25 2 32

Using a private name from outside is an error:

$ cat bad.ktm
import geo;

i32 main() {
    return geo.square(3);
}
$ ktm bad.ktm -o bad
error: `square` is private to module geo

Methods across modules

Only the module that defines a type can define its methods. Outside, its pub methods are called on a value with no prefix, and its private methods can't be called:

// bank/account.ktm
module bank;
import mem;

pub struct Account {
    u8 *owner;
    i64 balance;
};

pub Account *open(u8 *owner) {
    Account *a = mem.allocZero(sizeof(Account));
    a.owner = owner;
    return a;
}

pub void Account.deposit(i64 x) {
    self.balance += x;
    self.audit();
}

void Account.audit() {
    printf("audit: %d\n", self.balance);
}

pub void Account.close() {
    mem.free(self);
}
// main.ktm
import bank;

i32 main() {
    bank.Account *a = bank.open("ann");
    a.deposit(5);
    printf("%s %d\n", a.owner, a.balance);
    a.close();
    return 0;
}
$ ktm -jit main.ktm
audit: 5
ann 5

Calling a.audit() from main.ktm is an error: "the method audit of bank.Account is private to module bank". &bank.Account.deposit is the method as a function pointer.

Private fields

A field marked private can only be used inside the module that defines the struct:

// chat/conn.ktm
module chat;

pub struct Conn {
    i32 fd;
    private u8 *buf;
};

pub void reset(Conn *c) {
    c.buf = null;
}
// main.ktm
import chat;

i32 main() {
    chat.Conn c;
    c.fd = 3;
    chat.Conn copy = c;
    chat.reset(&c);
    printf("%d %d\n", copy.fd, sizeof(chat.Conn));
    return 0;
}
$ ktm -jit main.ktm
3 16

Writing c.buf = null; in main.ktm is an error: "buf is a private field of chat.Conn".

How modules are found

import geo; looks for the directory geo:

  1. next to the main file of the program;
  2. in the standard library;
  3. in each directory of the KTM_PATH environment variable, separated by :.
$ KTM_PATH=$HOME/ktm-modules ktm main.ktm -o app

Start-up order

Each module is compiled once, before the code that imports it. When the program starts, a module's globals are set up before its importers' globals and before main, and a module's files in the order of their names.

A blank import, import _ = name;, loads a module only for what it does when the program starts: import _ = net.https; gives the HTTP client https:// URLs. It is never reported as unused.

The prelude

The prelude's names are seen by every file with no import and no prefix:

i32 main() {
    printf("%d %d %u\n", I32_MAX, I8_MIN, U16_MAX);
    printf("%d %d\n", EXIT_OK, EXIT_FAIL);
    return 0;
}
2147483647 -128 65535
0 1

The runtime module rt is part of every program but internal: import rt; outside the standard library is an error.

A local with a module's name

A local variable or a parameter may have an import's name; within its scope the name is the local:

import str;

i64 count(u8[] str) {
    return str.len;
}

i32 main() {
    scoped String s = str.from("module again");
    printf("%d %.*s\n", count("four"), s.len, s.ptr);
    return 0;
}
4 module again

A global with an import's name is an error, since it would hide the module everywhere.

Packages

A package is a module in its own git repository, imported by its path in quotes:

import "github.com/ozzenerol/ktm-pg";

Its name in the code is its module line, so this package is used as pg.connect(...). import p = "github.com/user/repo"; gives it another name, and a directory of a package is a package too: import "github.com/user/repo/sub";.

ktm.mod

A project that uses packages has a ktm.mod file at its root. ktm mod init makes one:

$ mkdir hello && cd hello
$ ktm mod init example.com/me/hello
ktm: created ktm.mod for example.com/me/hello
module github.com/you/users-api

ktm 0.23

require (
	github.com/ozzenerol/ktm-pg v0.2.0
	github.com/someone/color v1.1.0 // indirect
)

replace github.com/someone/color => ../color

Getting, building and running

$ ktm get github.com/ozzenerol/ktm-pg
ktm: added github.com/ozzenerol/ktm-pg v0.2.0
$ ktm build              # main.ktm of this directory, into ./users-api
$ ktm run . --port 8080  # or build and run it, with its arguments

A local package through replace, end to end:

// ../greet/greet.ktm  (its ktm.mod says: module example.com/acme/greet)
module greet;

pub void hello(u8 *who) {
    printf("hello, %s\n", who);
}
// main.ktm
import "example.com/acme/greet";

i32 main() {
    greet.hello("world");
    return 0;
}
$ cat ktm.mod
module example.com/me/hello

ktm 0.23

require example.com/acme/greet v0.0.0

replace example.com/acme/greet => ../greet
$ ktm build && ./hello
hello, world
$ ktm mod list
example.com/me/hello
example.com/acme/greet v0.0.0 => ../greet
CommandWhat it does
ktm mod init [path]a new ktm.mod here
ktm get path@versionadds or changes a requirement and fetches it: @v1.2.0, @latest (the default), @none removes it
ktm mod tidymakes ktm.mod and ktm.sum match the imports
ktm build [dir], ktm run [dir] [args]builds (or runs) main.ktm, fetching what is missing
ktm test [dir], ktm test ./...runs the *_test.ktm tests of a directory, or of every one below
ktm mod list, ktm mod why paththe versions a build uses; who needs a package
ktm mod download, ktm mod verifyfetch into the cache; check the cache against ktm.sum

Versions

A version is a git tag vMAJOR.MINOR.PATCH. For each package a build uses the highest version anything requires: a package always gets at least what it asked for, and nothing newer than anyone asked for, so the same ktm.mod always gives the same build. One major version of a package per build. A package is downloaded only when the project imports one of its directories, directly or through another package. Fetching never runs a package's code.

To publish a package: a repository whose root has a ktm.mod and the module's .ktm files; tag a version and push the tag (git tag v0.1.0 && git push origin v0.1.0). Never move a tag: ktm.sum files that recorded it will refuse the new files.

Prebuilt packages

A compiled build turns each package from the cache into a library the first time it needs it, and later builds link that library instead of compiling the package again. Nothing has to be done for it.

Symbols

A module's names get symbols of their own (geo.area is _K3geo4area), so they never clash with C functions or the program's names. A C function a module declares with extern "c" keeps its C symbol (see Calling C).

Rules