Modules and imports
Importing
Section titled “Importing”import "./thing.knip";import "../lib/shapes.knip";import "./thing.knip" as util;Paths are relative to the importing file. The .knip extension is optional. Imports go at
the top of the file.
Namespacing
Section titled “Namespacing”An import brings its symbols in under a namespace — the file’s basename, or the alias:
import "./shapes.knip";import "./shapes.knip" as geo;
private p: num = shapes.perimeter(4, 3);private q: num = geo.perimeter(4, 3);Visibility
Section titled “Visibility”Only public symbols cross a file boundary:
public sides: num = 3;private secret: num = 42;
public proc perimeter(side: num, count: num) -> num { return side * count;}import "./lib.knip";
private a: num = lib.sides; # okprivate b: num = lib.secret; # error: 'secret' is privateWhat crosses a file boundary
Section titled “What crosses a file boundary”A public procedure and a public constant with a literal initializer cross and
build. A public variable or list does not:
public SIDES: num = 3; # literal — crosses, folds at the call sitepublic counter: num = 0; # variable — does not crosspublic shared: list<num> = [1, 2]; # list — does not cross
public proc twice(n: num) -> num { return n * 2; }public proc bump() -> void { counter += 1; }import "./lib.knip";
private a: num = lib.SIDES; # ✅ folds to 3private b: num = lib.twice(4); # ✅ the proc is lowered into this targetprivate c: num = lib.shared[1]; # 🔴 check: only members with a literal initializer are supportedlib.bump(); # 🔴 build: undeclared variable 'counter'An imported procedure may take parameters and return a value, but it cannot touch a variable or list declared in its own module — that variable is never emitted. Keep shared state in the entry file and pass it through parameters and returns.
Imports are not re-exported
Section titled “Imports are not re-exported”If a.knip imports b.knip, a file that imports a.knip cannot reach b through it:
import "./b.knip";public proc quad(n: num) -> num { return b.double(b.double(n)); }import "./a.knip";
a.quad(2); # ok — a uses b internallya.b.double(2); # error — b is not re-exportedEach file names its own dependencies. There is no transitive namespace.
The import graph
Section titled “The import graph”Each file is read and parsed exactly once, no matter how many files import it. Unresolvable paths and import cycles are reported against the file that did the importing, with a source span.
Resolvers
Section titled “Resolvers”Import resolution is pluggable. The CLI resolves against the filesystem; the VS Code extension resolves against open documents so unsaved edits are picked up; an embedding can supply virtual files, which is how a browser playground would work.
If you are embedding the compiler, you pass a resolver — see Embedding the compiler.