19. Built-in modules and the first standard library
- Status: Accepted
- Date: 2026-09-03
Context
ADR 0018 built the module system and split specifiers
two ways: "./x" is a file, anything else is a "built-in module". This ADR
decides what a built-in module is, how import tells the two apart, and what
the first one (math) contains.
Constraints:
- Korrin's builtins (
print,range, …) are a deliberately tiny set of constructors and I/O (ADR 0016). Numeric helpers likesqrtdo not belong there — they are a library, and a program that never does maths should not carry them. - The module system already gives us namespacing (
math.sqrt) and lazy loading (a module builds on firstimport). A standard library should use it, not a second mechanism.
Decision
"name" vs "./name"
The specifier's first characters decide, with no filesystem probe:
- starts with
./or../→ a file (ADR 0018); - anything else → a built-in module, looked up by exact name in a fixed table.
So import "math" is always the standard library's math, even if a
math.kor sits next to the importing file. To import that file, write
import "./math". This keeps the two namespaces from ever shadowing each other,
in either direction, and makes an import line's meaning obvious without knowing
what files exist.
What a built-in module is
A built-in module is a scope populated in Rust and handed to the loader, which
wraps it in the same Value::Module a file import produces and caches it under
builtin:<name>. From Korrin it is indistinguishable from a file
module: type() is "module", members are module.x, _-names are private,
loading happens once.
Built-in modules live in crates/korrin/src/stdlib/, one file each, registered
in stdlib::load. crates/korrin/tests/builtins.rs checks each module's
exported names against its specification section, both ways.
math (Milestone 2)
math is float-flavoured: operations that are naturally real-valued return
float; the rounding operations return int.
| Name | Result |
|---|---|
math.sqrt(x) | √x as a float; x must be ≥ 0 (E0304) |
math.pow(base, exp) | base ** exp as a float |
math.floor(x) / math.ceil(x) / math.round(x) | nearest int in that direction (round is half-away-from-zero); E0304 if it will not fit int |
math.abs(x) | absolute value, keeping x's type (int → int, float → float) |
math.min(a, …) / math.max(a, …) | the smallest / largest argument (one or more numbers), returned unchanged |
math.pi / math.e | the constants, as float |
io (file reading and writing, CLI arguments) is delivered the same way —
see ADR 0020, which also writes down the standard-library
principles this ADR started.
Consequences
- The global scope stays tiny; numeric code opts in with one
import "math". - A standard library is "just modules" — no new runtime concept, and a
third-party could ship a module that is imported identically (
import "./their/lib"). import "math"meaning the stdlib unconditionally is a small foot-gun for someone who wants to shadow it with their ownmath.kor— but that is rare, and"./math"is right there. The predictability is worth it.- Native modules cannot (yet) be written in Korrin —
mathis Rust. A future "stdlib written in Korrin, bundled as source" is possible on this same mechanism and is not foreclosed. - Rounding functions returning
int(notfloat) is a small inconsistency withsqrt/pow, chosen becauseint(math.floor(x))everywhere would be noise and a floored value is conceptually a whole number.
Alternatives considered
- A filesystem probe — try
./math.kor, fall back to built-in. Rejected: animportline's meaning would depend on which files exist, and adding a file could silently change behaviour. - A
std:or@prefix for built-ins (import "std:math"). Explicit, but ugly for the common case and a new bit of syntax. "No./means built-in" is a rule you learn once. - Putting
sqrtetc. in the global builtins. Against ADR 0016's "builtins construct or convert, they are not a library". Every program would carry them. - A bigger first cut (
json,time,random, …). Deferred.mathandioare what "write a real tool" needs first; the rest can land per-module with no design cost now that the mechanism exists.