Modules
Files are modules
Section titled “Files are modules”One file is one module; the directory layout is the module path.
geometry/vec.cz is the module geometry.vec. There is no module
declaration inside a file.
Imports
Section titled “Imports”import geometry.vec.{ Vec2, dot } // named imports (braced list)import geometry.vec.{ dot as vdot } // name import with renameimport geometry.vec.Vec2 // one name, same as .{ Vec2 }import geometry.vec // the whole module, used qualifiedimport geometry.vec as g // module import with a custom qualifierThree forms:
import path.{ Name, ... }brings the listed names into scope bare (trailing comma allowed).Name as Localrenames a name on import:import m.{ helper as h }makeshavailable;helperis not in scope.import path.Namebrings the one trailing name into scope, exactly likeimport path.{ Name }.import pathimports the module itself. Its exports are used qualified by the path’s last segment:vec.dot(a, b), a type positionvec.Vec2, a record literalvec.Vec2 { x: 1.0 }, a static callvec.Vec2.new(...), a variant literalvec.Shape.Circle { r: 1.0 }, and a match patternvec.Shape.Circle { r } =>(the optional pattern qualifier must name the matched value’s sum type and one of its variants). The qualifier defaults to the last path segment;import path as nameoverrides it (e.g.import geometry.vec as gusesg.dot(..)). Two module imports whose qualifiers collide are rejected; rename one withasor import names directly.
Two name imports in one module cannot bind the same local name to different
targets, even inside one braced list or when both targets are in the same
module. For example, import lib.{ helper as h, other as h } is an error.
Importing the identical target under the same local name more than once is
allowed. The diagnostic points to the second local name and names both targets.
The two brace-less forms are distinguished by what exists: if the whole path
names a module, it is a module import; otherwise the last segment is a name
imported from the module named by the rest. Qualified access disambiguates:
import c.{ X } and import a.b may coexist, with bare X resolving to
c’s definition and b.X resolving to a.b’s. A local binding shadows a
qualifier (let vec = ... makes a later vec.x a field access), and a
qualifier that collides with a declared or imported name is rejected.
A declaration in another module always needs an import, even when its bare name is unique across the whole program. Program-wide uniqueness is an internal symbol-layout detail, not implicit visibility.
Import paths are resolved according to the rules in Module resolution. Import cycles are detected and reported.
Importing a type brings its methods with it: import geometry.vec.{ Vec2 }
makes both Vec2.new(...) and v.add(w) available; methods are in scope
wherever their type is. The import also gates
anonymous-record dispatch:
a literal like { x: 1.0, y: 2.0 } only adopts a type the module declares or
imports, while a value that already carries a nominal type, an imported
function’s return say, dispatches its methods without the type’s name being
imported.
Module resolution
Section titled “Module resolution”The compiler resolves an import in this order:
core.*and bare prelude module names refer to the embeddedcorelibrary. A bare spelling such asimport iois stored as the canonicalcore.iomodule while keepingioas its source qualifier. These imports never load files from disk.- If the first segment is a name in
BRASS_PACKAGES, the complete path is resolved only below that package root. A missing module does not fall through to local or include files. - Every other path is interpreted relative to the importing module. From
app/main.cz,import geometry.vecfirst meansapp/geometry/vec.cz. - The relative path is searched below the project root, then below each
BRASS_INCLUDEdirectory in order. A project file therefore shadows an include file, and an earlier include directory shadows a later one. - From a nested module, if the relative path does not exist, the path as
written is also tried from those roots. This lets a file such as
app/features/view.czimport a top-levelgeometrymodule.
BRASS_PACKAGES is an OS path list of name=directory entries. The
directory is the parent below which that package name resolves. On Unix the
entries are separated by :, and on Windows by ;:
BRASS_PACKAGES=geometry=/opt/geometry:std=/opt/brassBRASS_INCLUDE is an OS path list of open module roots:
BRASS_INCLUDE=/opt/brass-extras:/home/user/brass-modulesAvoid overlapping include roots: the same source can otherwise be loaded under two module paths. Package bindings take precedence over all local and include paths, so a declared dependency cannot be shadowed accidentally.
A distributed toolchain supplies an implicit std package rooted beside its
bin/ directory. An explicit std=... entry in BRASS_PACKAGES overrides
it. Repository builds do not have this installed layout; use the packaged
toolchain or provide std explicitly.
Visibility
Section titled “Visibility”A name is public unless it starts with _:
- a
_-prefixed function, type, or global is private to its module and cannot be imported; - a
_-prefixed module (file or last path segment) cannot be imported at all.
There is no other visibility control.
Every module is loaded with a constant naming its own source file:
println(_PATH) // /home/you/project/src/main.czThe path is absolute, so it does not depend on where the program was started.
_PATH follows the visibility rule above: its leading _ makes it private,
so a module always reads its own, never an importer’s, and no module can export
one. A module with no file on disk (an embedded core module, a plugin’s
synthesized wrapper) reads its diagnostic label instead, such as <core/io>.
To take the path apart, hand it to std.path:
Path.parse(_PATH).parent() is the directory holding the file you are writing.
The standard library
Section titled “The standard library”The embedded core/ modules (io, array, string, math, conv,
assert, error, is, default, collections) form the implicit
prelude: their public names are in scope everywhere without an import,
HashMap included. They can also be imported explicitly by their bare name
(import io.{ ... }) or core path (import core.io.{ ... }), to alias or
qualify a name.
The shipped std/ tree (fs, net, process, data.json, …) is not
embedded. It resolves as the package named std, which an installed
toolchain binds automatically, and imports with the std. prefix
(import std.fs.{ read_file }). See the
standard library reference.
Execution order
Section titled “Execution order”Each module’s top-level statements are gathered into a module initializer.
Initializers run first, in dependency order, then main is called if the
program defines one. Within a module, globals initialize in textual order, and
using a global before its initializer has run is a compile error.