Skip to content

The book · 12

Modules and ecosystem

book.md · 103 lines · 3 min read

Goal: organize code in the bin/lib model, import with use, understand the stability tiers, and get to know the official libs and the mk CLI.

Two markers, with distinct roles:

  • mk.mod marks a module, and a module is a lib: an independent, reusable box, with its own dependencies. It works as a namespace and dependency boundary. It is never compiled alone, only as part of a project’s tree (unused modules coexist at no cost).
  • mk.project marks the bin (the composer): the top module and the unit of compilation. It composes the modules into a tree, declares the language version, its deps, and the tasks/binary production.
myapp/ ← mk.project (BIN: composes modules, tasks, deps)
├── mk.project
├── mk.sum (lockfile, auto-generated)
├── main.mko → module "myapp"
├── auth/ ← mk.mod (LIB "myapp/auth", its own deps)
│ └── tokens.mko
└── store/ ← mk.mod (LIB "myapp/store")
└── db.mko

The manifest declares the language version and the deps (like go 1.21):

mk.project
package myapp
makoto 1.0
deps {
github.com/acme/http 2.3.1
}
use json // mounts the module → json.parse(...)
use json.{parse, decode} // lifts names → parse(...) directly
use json as j // alias

Where each use comes from is decided by the prefix: bare is stdlib (use io); a package name is local (use myapp/auth); URL form is external (use github.com/acme/http). Imports are absolute from the package name, never ../. Cycles are forbidden.

The ecosystem is organized by how freely the contract can be changed:

  • Tier 0, the core (the language plus the stdlib): standards that don’t change (IEEE, TCP/IP, UTF-8, linked list). It is add-only: you add, you never edit the contract. collections (List/Map/Set) is tier 0, a closed set (there will never be a “fourth fundamental collection”).
  • Tier 1, the official libs: specifications that evolve (HTTP had 3 versions; regex swaps algorithms). It has its own cadence (semver/changelog), is opt-in, and costs zero weight until the use. containers is tier 1, a roster that grows (each algorithm inside is frozen, but the collection changes).
  • Tier 2, the frameworks (web, the UI): more opinion, more churn.
  • Tier 3, the third parties: the compatibility promise is someone else’s.

The rule is that tiers only depend downward. The cut sometimes splits a concept in half: UTF-8 is tier 0, the Unicode tables are tier 1; UTC/offset is tier 0, the IANA timezone names are tier 1.

  • http: client and server, on top of net’s layer 2 (bytes + TLS). It has a version-agnostic surface (/1.1, /2, /3 negotiated by ALPN), process-per-stream (one panic in a request crashes only it), and a full client (pool/redirect/cookies/gzip). The framework (router/middleware) is web (tier 2).
use http
client := http.Client.new()
resp := client.get("https://api.example/users") catch |e| { return e }
if resp.status.is_success() { process(resp.body.to_string() catch |e| { return e }) }
  • regex: multi-engine by construction. regex.compile uses RE2 (linear, no ReDoS, the default), regex.pcre.compile uses backtracking (full features), and regex.hyperscan does multi-pattern plus streaming. A literal pattern compiles at comptime.
const WORD := regex.compile("[a-z_][a-z0-9_]*").or_panic() // comptime: validates + embeds
  • containers: Deque, PriorityQueue, SortedMap/SortedSet (B-tree), Trie (ART), RingBuffer. It follows the convention new() (natural order) / with_order(cmp) (custom):
use containers
pq := containers.PriorityQueue[Task].with_order(by_priority) // Task has no natural order → with_order

mk run / mk build / mk test / mk fmt / mk get / mk doc / mk lint / mk bench, and more (chapter 15). A single binary, an alias for makoto.

Next: 13 · FFI and low level