Modules and Manifests
Sun uses a manifest-based module system where programs declare their dependencies in a manifest block. Multiple source files are merged into a single AST before compilation, enabling efficient cross-file optimization.
Module declarations
Dotted names are shorthand for nested modules. For example,
public module a.b.c { ... } is equivalent to three nested public modules:
public module a { public module b { public module c { ... } } }.
Without public, each module in the path uses the default private visibility.
Declarations inside the body keep their own visibility.
A comment above a dotted declaration documents only its innermost module:
/** Helpers for reading files. */
public module app.files.readers {
}Here the comment documents readers, not app or files. Hovering over each
name in the declaration shows that module; only readers shows this comment.
Use explicit nesting to give each module a separate comment.
Using statements
A using statement makes names available only to code in the same source file
and within its enclosing scope. A file-level using also reaches nested modules
in that file. A using inside a module or function stays within that scope and
its children.
Files contributing to the same module share declarations, but each file must
provide its own imports. Module-level imports apply regardless of their position
in the file. Generic definitions retain their own file's imports when specialized
from another file or loaded from a .moon library. Qualified names such as
std.Vec do not require a using statement.
Manifests
Every Sun program has an entrypoint file containing a manifest block that declares all dependencies:
using std;
function main() void {
println("Hello!");
}
manifest {
source_files: ["utils.sun", "math.sun"] // Source files to compile together
libraries: ["stdlib.moon"] // Precompiled libraries to link
}Manifest Syntax
manifest {
source_files: ["file1.sun", "file2.sun"] // Optional: additional source files
test_files: ["file1_tests.sun"] // Optional: test-only source files
libraries: ["lib1.moon", "lib2.moon"] // Optional: precompiled libraries
protos: ["schemas/msgs.proto"] // Optional: protobuf schemas to import
archives: ["vendor/libfoo.a"] // Optional: C libraries to carry along
target: { // Optional: per-OS entries
macos: { source_files: ["target_darwin.sun"] }
linux: { source_files: ["target_linux.sun"] }
}
}- source_files — Source files (
.sun) to parse and merge with the entrypoint - test_files — Source files loaded only when compiling tests (
sun test, or the<output>_testbinary-cemits); see Testing - libraries — Precompiled library bundles (
.moon) to link - protos — Protobuf schemas (
.proto) compiled into message classes; see Protobuf - archives — Native static libraries (
.a) to carry inside the bundle being built, so importers link against them without-lflags; only meaningful with--emit-moon. Their symbols are renamed under a hash of the listed archives together, so the bundle binds the copies it carries even when another bundle carries a different version. A bundle built on top of this one carries them forward along with the code that calls into them, so a program needs only the bundle it imports. See C FFI - target — Entries that only apply when compiling for one operating
system (
linux,macosorwindows— the OS names_target_isaccepts). Each block takes the same sections as the manifest itself. The manifest never decides the target:--target(or the host, when absent) does, at compile time — the block just says which entries belong to builds for that OS. A block'ssource_filesare placed before the shared list, since per-OS files typically define the primitives shared code consumes; this is how the standard library carriestarget_linux.sunandtarget_darwin.sunin one manifest.
All fields are optional (entries are newline-separated; a trailing , or ;
is tolerated). A minimal program needs only a main() function.
Path Resolution
Paths in the manifest are resolved in order:
- Relative to the entrypoint file — the directory containing the entrypoint
- A
sun-config.json— anysun_pathdirectories from the config files above the entrypoint (see Sun Config) --lib-path— directories given on the command lineSUN_PATH— directories in theSUN_PATHenvironment variable- The install location —
/usr/lib/sunfrom the Debian package,$(brew --prefix)/lib/sunfrom Homebrew, andlib/sunbeside the runningsunbinary
An installed compiler finds stdlib.moon through step 5 with no setup, so
SUN_PATH is for a build you have not installed — a source checkout, or a
bundle you dropped somewhere of your own:
export SUN_PATH=/path/to/sun/build
sun myprogram.sun # Finds build/stdlib.moonPath Variables
Manifest entries can reference variables with $NAME — in source_files, libraries
(paths and urls) and protos entries alike. Here the manifest asks for
$LIBS/mathlib.moon:
manifest {
libraries: ["stdlib.moon", "$LIBS/mathlib.moon"]
}This folder's sun-config.json supplies both the library search path (where
stdlib.moon is found) and the variable:
{
"sun_path": ["../../build"],
"path_variables": { "LIBS": "libs" },
"root": true
}The compiler merges every sun-config.json from the entrypoint's folder up
to the filesystem root: the nearest definition of a variable wins, search
dirs are searched nearest-first, and relative entries resolve against their
own config file's folder. So a workspace root can define sun_path and shared
variables once, while each subfolder adds or overrides only what is local to
it. A config with "root": true stops the upward search — this example uses
it to stay self-contained. Explicit --path-var arguments override config
values; config values override the sun.pathVariables editor defaults and
the environment:
sun --path-var LIBS=libs --compile -o main main.sun # overrides config values
LIBS=libs sun --compile -o main main.sun # environment works tooUsing a variable that is defined nowhere is a compile error. Variables are expanded before path resolution, so a variable can hold a relative directory — which keeps a manifest portable when libraries live in a different place on each machine.
The config can also declare build products in an entrypoints array. See
Sun Config for the entry fields and how to build or test a
whole project.
Source
using std;
using mathlib;
/** Runs the example that calls a library imported using a path variable. */
function main() void {
println(triple(14));
}
manifest {
libraries: ["stdlib.moon", "$LIBS/mathlib.moon"]
}/** Groups the declarations used by this example program. */
public module mathlib {
/** Returns three times the input to demonstrate a path-variable import. */
public function triple(x: i32) i32 {
return x * 3;
}
}
manifest {
source_files: []
libraries: []
}Build and run
./build.sh
./mainCompilation Pipeline
When you run sun main.sun:
- Parse entrypoint — Parse
main.sunand extract the manifest - Parse dependencies — Parse all files listed in
source_files; synthesize a module of message classes for each schema inprotos - Merge ASTs — Combine all parsed ASTs into a single merged AST
- Load libraries — Load type stubs and compiled code from
libraries - Semantic analysis — Analyze the merged AST with moon type information
- Code generation — Generate LLVM IR for the merged AST
- Link — Link with moon compiled code
- Execute or output — JIT execute or emit native binary
Precompiled Libraries (.moon)
Moon files (.moon) are precompiled library bundles containing compiled code and type information. They enable fast compilation and reliable distribution.
Sun is designed to work with Moon, a safety-critical package manager (coming soon) that pins dependencies to the exact hash of the binary and compiler version. Every change is a breaking change — Moon prioritizes reproducibility over convenience.
Using Moon Files
Declare moon dependencies in your manifest:
using std;
function main() i32 {
var allocator = make_heap_allocator();
var m = Matrix<i32>(allocator, [3, 3]);
m[0, 0] = 42;
return m[0, 0];
}
manifest {
libraries: ["stdlib.moon"]
}Exact dependency types and module aliases
Exported nominal types identify their defining bundle by its exact content hash,
original declaration path, and name in a canonical QualifiedName. The compiler
checks the referenced declaration’s kind where an interface is required.
If a.moon exports a function that accepts a type from b-v1.moon, the application must explicitly import both
bundles. Import order does not matter. Supplying b-v2.moon produces an exact
dependency error, even when its type has the same name and layout.
Aliases change source lookup names only. For example,
--moon b-v1.moon:b=old_b --moon b-v2.moon:b=new_b allows both versions to
coexist. Their compiled symbols retain their original hash-scoped names, and
a.moon continues to accept old_b values. Passing a new_b value fails type
checking. A module alias also applies to its nested modules. Different versions
need distinct visible module names.
Module references in exported generic bodies carry their original qualified
names, including the defining bundle hash. Retained using targets are also
canonicalized when the library is built, while preserving their file-local scope.
These imports do not become imports in the caller's file.
Dependencies used only by compiled implementations can run from the embedded
bitcode without a separate declaration import. Dependency declarations are not
loaded automatically.
Moon format changes require rebuilding libraries with the current compiler, including the standard library and TLS bundles for every target.
Downloaded dependencies
Manifests name local .moon files. Configure downloads in sun-config.json:
{
"dependencies": {
"IMAGE_CORE": {
"target": {
"x86_64-linux-gnu": {
"moon": {
"url": "https://example.com/image-core-x64.moon",
"hash": "<lowercase SHA-256>",
"filename": "image_core.moon"
}
},
"aarch64-linux-gnu": {
"moon": {
"url": "https://example.com/image-core-arm64.moon",
"hash": "<lowercase SHA-256>",
"filename": "image_core.moon"
}
}
}
}
}
}The source remains the same for both targets:
manifest {
libraries: ["$IMAGE_CORE/image_core.moon"]
}--target selects the dependency; without it, Sun uses the host target.
Dependency names bind local directories beneath
~/.sun/cache/dependencies/<NAME>/<normalized-target>/<sha256>/. Override the
cache root with SUN_DEPENDENCY_CACHE. Names must not collide with ordinary
path variables, explicit overrides, editor defaults, or environment variables.
A dependency accepts an ordinary descriptor, or default and target
descriptors. A descriptor contains moon, package, or a Git source selection.
A moon descriptor requires a simple .moon filename; package accepts a
Sun .tar.gz package and exposes its extracted root. Both accept exactly one
of url or config-relative path. URLs require a SHA-256 hash; local hashes
are optional. Supported URL schemes are http://, https://, and file://.
A Git source selection uses the dependency's own config:
{
"dependencies": {
"MYLIB": {
"git": "ssh://git@example.com/team/mylib.git",
"version": "v1.2.3",
"config": "sun-config.json",
"entrypoint": "mylib"
}
}
}Alternatively, select a source entrypoint directly:
{
"dependencies": {
"MYLIB": {
"git": "git@example.com:team/mylib.git",
"version": "v1.2.3",
"path": "src/mylib.sun"
}
}
}path is relative to the repository and cannot be combined with config or
entrypoint. Direct source builds use the consumer's configuration, including
sun_path, path variables, and named dependencies; the repository's config is
not loaded. The output filename is the source filename with .moon replacing
.sun, so the consumer imports "$MYLIB/mylib.moon". Both forms build libraries
without running dependency tests and share Git caching and SSH support.
The git field also accepts HTTPS and SCP-style SSH (git@example.com:team/mylib.git).
version is required. When path is absent, config defaults to
sun-config.json; entrypoint is
optional only when exactly one enabled library is available for the selected
target. Use default/target descriptors to restrict platform availability.
The selected library's output_name basename (or source stem plus .moon)
is exposed under $MYLIB. Its tests, other entrypoints, and packages are not built.
Transitive dependencies resolve through the library's own config. Source configs
own their path variables; explicit --path-var overrides still apply. The
consumer's sun_path directories are searched before the dependency's configured
search directories, including for transitive builds. To compile against your
local stdlib, put the directory containing your built stdlib.moon in the
consumer's sun_path.
Git checkouts and library builds use SUN_GIT_CACHE (default ~/.sun/cache/git).
Builds are separated by consumer, source selection, target, and compiler settings.
--refresh-sources refreshes each repository revision once per command;
--force-rebuild recompiles dependencies as well. Dependency cycles and missing,
ambiguous, or non-library selections are compilation errors.
For a package dependency named IMAGE_TOOLS, import a library by its path:
manifest {
libraries: ["$IMAGE_TOOLS/lib/helpers.moon"]
}Downloads occur only when a dependency is used. Sun verifies the bytes and package inventory before exposing local files. An unsupported target fails when the dependency is used. Importing a library does not redistribute its package's other files or resources.
Manifest objects with a url are no longer accepted. Move their URL and hash
into config, set a local filename, and reference $NAME/file.moon instead.
Local library objects can still use path and rename.
For private GitHub assets, pass --gh-token or set GH_TOKEN or
GITHUB_TOKEN. Authentication is sent only to GitHub hosts. Use a GitHub API
release-asset URL in the config to download private release assets.
Distributions and resources
Build configured distributions with sun -c sun-config.json:
{
"root": true,
"entrypoints": [
{
"name": "viewer",
"path": "apps/viewer.sun",
"type": "binary"
},
{ "name": "helpers", "path": "lib/helpers.sun", "type": "library" }
],
"packages": [
{
"name": "viewer-tools",
"entrypoints": ["viewer", "helpers"],
"resources": [
{ "path": "assets", "destination": "share/viewer/assets" },
{ "path": "LICENSE", "destination": "LICENSE" }
]
}
]
}This produces dist/viewer-tools-<normalized-target>/ and the corresponding
.tar.gz. The directory remains available for inspection. Set package
output_name to change the extensionless output base. Named binaries live
under bin/; named libraries live under lib/ with .moon extensions.
package.json records the target, artifact paths, and payload hashes.
Resources belong only to packages; entrypoints do not accept a resources
field. To distribute one executable with assets, declare a package containing
that entrypoint. Packages can contain several binaries and libraries, even
without resources. Entrypoints outside a package produce only their build
outputs. Tests and compiler intermediate files are excluded. Package membership does not change compilation order.
Resource paths resolve relative to their config and support $NAME variables.
For example, explicitly copy dependency resources with
{ "path": "$IMAGE_TOOLS/share/profiles", "destination": "share/profiles" }.
Directories contribute their contents recursively. Destinations must remain
inside the archive. Missing inputs, conflicting mappings, links, and special
files are errors. Applications control runtime resource lookup.
Resource changes cause repackaging without recompiling unchanged code. Sun normalizes archive ordering, timestamps, ownership, and permissions. Direct source builds, tests, and JIT execution do not assemble distributions.
Creating Moon Files
Create a moon from an entrypoint file with a manifest:
mylib.sun (entrypoint)
public module mylib {
public function helper() i32 { return 42; }
}
manifest {
source_files: ["vec2.sun", "utils.sun"]
}sun --emit-moon -o mylib.moon mylib.sunThe compiler:
- Parses the entrypoint and all files listed in
source_files - Merges them into a single AST
- Compiles and serializes the code and type information
- Packages everything into
mylib.moon
Building only what changed
Only the compiler knows which files a manifest pulled in, so the compiler is the one that can tell whether a build has anything to do, and it checks by default. Before compiling, it hashes every input: the text of each source and test file, the proto schemas, the native archives, the bundles imported, the target and flags, and the compiler itself. Every artifact it builds records that hash, and an artifact whose recorded hash matches is left untouched:
sun --emit-moon -o mylib.moon mylib.sun # Successfully created: mylib.moon
sun --emit-moon -o mylib.moon mylib.sun # Up to date: mylib.moon
sun --emit-moon --force-rebuild -o mylib.moon mylib.sun # Successfully created: mylib.moonThe same goes for sun -c, per artifact: editing a test_files: entry
rebuilds app_test and leaves app alone. --force-rebuild skips the check
and builds everything; the artifacts still record their inputs, so the run
after it can skip again. Flags that print something besides the artifact
(--emit-ir, --debug, --dump-proto-sun) also always build.
A build system does not need to be told the inputs. Have it run sun on
every build and let it skip the work; an untouched artifact keeps its
timestamp, so nothing downstream is rebuilt either. With CMake that is an
add_custom_target naming the artifact under BYPRODUCTS; Sun's own
CMakeLists.txt builds the standard library this way.
The same hash names a bundle, with or without the flag: its symbols are spelled under it, so two bundles that differ in any input never collide.
The Standard Library
Sun's standard library is built from stdlib/stdlib.sun:
manifest {
source_files: [
"allocator.sun", "unique.sun", "sys.sun", "option.sun", "math.sun",
"byte_order.sun", "iterator.sun", "slice.sun",
"contiguous_buffer.sun", "vec.sun", "matrix.sun", "string.sun",
"errors.sun", "print.sun", "map.sun", "linked_list.sun", "queue.sun",
"thread.sun", "test.sun", "shared.sun", "io.sun", "env.sun",
"time.sun", "process.sun", "networking.sun", "http.sun",
"proto_wire.sun", "json.sun", "bigint.sun"
],
target: {
macos: { source_files: [ "target_darwin.sun" ] }
linux: { source_files: [ "target_linux.sun" ] }
}
}| Module | Contents |
|---|---|
allocator.sun | Memory allocators (HeapAllocator, IAllocator interface) |
bigint.sun | Arbitrary-precision BigUint |
byte_order.sun | std.byte_order — endianness conversions |
env.sun | std.env — environment variables, working directory, args |
event_loop.sun | std.io — epoll/kqueue readiness with EventLoop |
errors.sun | Standard error types (EmptyError, NotFoundError, etc.) |
http.sun | HTTP/1.1 server (HttpServer); the client is in tls.moon |
io.sun | std.io — files, directories, Poller |
iterator.sun | Iteration protocol (IIterator<T, Container>, IIterable<T, Self>) |
json.sun | Json parsing and serialization |
linked_list.sun | Doubly-linked list LinkedList<T> |
map.sun | Generic hash map Map<K, V> |
matrix.sun | Matrix<T> and MatrixView<T> N-dimensional arrays |
networking.sun | TCP sockets (TcpStream, TcpListener) and DNS |
option.sun | Option<T> and _Result<T, E> payload enums |
print.sun | Overloaded print/println/eprint/eprintln |
process.sun | std.process — Command, signals, raw fork/exec |
proto_wire.sun | Protobuf wire format |
queue.sun | FIFO Queue<T> built on LinkedList<T> |
shared.sun | Shared<T> reference-counted handle with Mutex-guarded access |
string.sun | String and StringView classes |
sys.sun | The stdlib's private extern "C" declarations |
target_linux.sun / target_darwin.sun | Per-OS constants (errno, socket options) |
test.sun | std.test — assertions and the test_function runner |
thread.sun | std.thread — spawn, Thread<T>, Mutex |
time.sun | std.time — Duration, Instant, DateTime |
unique.sun | Unique<T> smart pointer with automatic cleanup |
vec.sun | Generic growable array Vec<T> |
When building from source, the stdlib is automatically compiled:
cmake -B build && cmake --build build
# Creates build/stdlib.moonThe TLS Bundle
TLS ships as its own bundle, tls.moon, rather than as part of the standard
library, so programs that do not speak TLS carry none of it:
manifest {
libraries: ["stdlib.moon", "tls.moon"]
}It holds TlsStream, HttpsClient and the OpenSSL bindings, and carries a
static copy of OpenSSL inside the bundle — so nothing needs -lssl and the
compiled binary needs no OpenSSL installed. See TLS & HTTPS.
Example: Building a Custom Moon
Step 1: Create library source files
mylib/vec2.sun
class Vec2 {
var x: f64;
var y: f64;
init(x_: f64, y_: f64) {
this.x = x_;
this.y = y_;
}
method add(other: ref Vec2) Vec2 {
return Vec2(this.x + other.x, this.y + other.y);
}
method length() f64 {
return _sqrt(this.x * this.x + this.y * this.y);
}
}mylib/utils.sun
function clamp(val: f64, min: f64, max: f64) f64 {
if (val < min) { return min; }
if (val > max) { return max; }
return val;
}Step 2: Create entrypoint with manifest
mylib/mylib.sun
manifest {
source_files: ["vec2.sun", "utils.sun"]
}Step 3: Build the moon
sun --emit-moon -o mylib.moon mylib/mylib.sunStep 4: Use the library
function main() i32 {
var a = Vec2(3.0, 4.0);
var len = a.length(); // 5.0
var t = clamp(1.5, 0.0, 1.0); // 1.0
return 0;
}
manifest {
libraries: ["mylib.moon"]
}Installing Moon Files
Per-project:
cp mylib.moon myproject/libs/manifest {
libraries: ["libs/mylib.moon"]
}System-wide:
mkdir -p ~/.sun/lib
cp mylib.moon ~/.sun/lib/
export SUN_PATH=~/.sun/libmanifest {
libraries: ["mylib.moon"] // Found via SUN_PATH
}The Debian package installs stdlib.moon and tls.moon to /usr/lib/sun,
and Homebrew installs them to $(brew --prefix)/lib/sun on macOS. Both are
searched without any SUN_PATH setting, as is lib/sun beside whatever
directory the running sun binary sits in.
Installing a Released Bundle
Releases carry stdlib.moon and tls.moon as downloads of their own, for
dropping next to a compiler you already have.
A bundle's bitcode is compiled for one architecture and operating system, and the compiler rejects one built for another — so each file is named for its target. Take the one matching yours and install it under the canonical name, since manifests resolve bundles by exact file name:
curl -LO https://github.com/namo-robotics/sun/releases/download/dev/stdlib-x86_64-linux-gnu.moon
sudo cp stdlib-x86_64-linux-gnu.moon /usr/lib/sun/stdlib.moonAny directory on SUN_PATH — or one passed with --lib-path, or listed in a
sun-config.json — works as well as /usr/lib/sun.
The compiler automatically searches target subdirectories of installation
directories before their top-level bundles. For example,
--target aarch64-linux-musl finds the compatible bundles installed under
/usr/lib/sun/aarch64-linux-gnu/. GNU and musl spellings are compatible
for Sun bundle selection. Explicit paths and user-supplied search directories
retain their existing precedence; a wrong-target override still produces an error.
For bundles outside installation directories, keep different targets in separate
directories, then pass the
matching directory with --lib-path or list it in sun_path:
mkdir -p ~/.sun/lib/aarch64-linux-gnu
cp stdlib-aarch64-linux-gnu.moon ~/.sun/lib/aarch64-linux-gnu/stdlib.moon
mkdir -p ~/.sun/lib/arm64-apple-darwin
cp stdlib-arm64-apple-darwin.moon ~/.sun/lib/arm64-apple-darwin/stdlib.moon
sun --emit-obj --target aarch64-linux-gnu \
--lib-path ~/.sun/lib/aarch64-linux-gnu main.sun--target selects the compilation target; it does not append a target
subdirectory to configured search paths. If a manifest dependency already
resolves beside the entrypoint or through sun_path, that file takes
precedence over --lib-path. See
Cross-compiling configured entrypoints
for config build commands and output paths.
Picking the wrong file is not silent — the compiler names both triples:
Module '07f0c36b...' was compiled for 'aarch64-linux-gnu' but the current
target is 'x86_64-pc-linux-gnu'; rebuild the .moon with --target ...Namespaces (module blocks)
Use module blocks to group related types and functions:
public module math {
public function square(x: i32) i32 {
return x * x;
}
public function cube(x: i32) i32 {
return x * x * x;
}
}From outside the module, its symbols are reached by qualified name (e.g., math.square).
Using Statements
Use using to import all symbols from a module:
using std; // Import all symbols from the sun module
function main() i32 {
var allocator = make_heap_allocator(); // No qualification needed
var v = Vec<i32>(allocator, 8);
v.push(42);
return v.get(0);
}
manifest {
libraries: ["stdlib.moon"]
}Without using, you would need to write each symbol's qualified name, such as std.Vec<i32>.
Module Scope
Inside a module, symbols can reference each other directly:
public module math {
public function square(x: i32) i32 {
return x * x;
}
public function sum_of_squares(a: i32, b: i32) i32 {
// Can call square() directly - same module
return square(a) + square(b);
}
}Module-Level Variables
A var declared directly in a module is a single piece of storage shared by
the whole program — it lives for the life of the process rather than for a
call. It can be read and written from inside its module, through a using
import, and by its qualified name:
public module registry {
public var count: i64 = 0;
public function add() void {
count = count + 1; // same module
}
}
using registry;
function main() i32 {
add();
count += 1; // through the `using` import
registry.count = 10; // by qualified name
return registry.count; // 10
}Use const instead of var for one that never changes; assigning to it is a
compile error. A module-level variable of a class or payload-enum type is owned
like any other value: assigning to it drops what was there and moves the new
value in.
A module-level var is ordinary shared mutable state, and nothing
synchronizes it. When more than one thread touches it, guard it with a
Mutex — see Threads.
Visibility
Everything is private by default. public is the only modifier; there is
no private keyword — an item is private simply by not being marked public.
Privacy is scoped to the module: a private item is reachable from anywhere
inside the module that declares it, including nested (child) modules, and from
nowhere else. Files are not a boundary — every opening of module std { ... }
across the stdlib's files is one module.
public module geometry {
// Private helper: callable anywhere inside `geometry` (and its children)
function square(x: f64) f64 { return x * x; }
public class Point {
var x: f64; // private field: `geometry` only
var y: f64;
init(x: f64, y: f64) { this.x = x; this.y = y; }
public method norm2() f64 { return square(this.x) + square(this.y); }
}
module internal { // private nested module
public function scratch() i32 { return 0; }
}
public module shapes { // public nested module
public function unit() Point { return Point(1.0, 0.0); }
}
}
using geometry;
function main() i32 {
var p = shapes.unit();
var n = p.norm2(); // OK: public method
// p.x // error: 'x' is private to class 'Point' in module 'geometry'
// square(2.0) // error: function 'square' is private to module 'geometry'
// internal.scratch() // error: module 'internal' is private to module 'geometry'
return 0;
}The modifier applies to every kind of module-level item — functions, classes,
interfaces, enums, globals, extern and declare declarations, and nested
modules — and to class and interface members. public module a.b { ... }
makes both a and b public. All openings of a module must agree on its
visibility. using M; never exposes M's private items.
Items declared outside any module belong to the program's root scope and are
reachable everywhere in that program (a single-file program needs no public
at all).
Visibility and .moon bundles
A bundle keeps every item — private ones included, since generic method
bodies shipped in the bundle may use them — but importers can only reach the
public API. --emit-moon requires each top-level module of the bundle to be
public; a bundle whose root module is private is rejected.
Globals that need code to initialize them, such as a class value built by its
constructor, are set up before main runs. A library's globals are always
initialized before those of any library or program that imports it, and only
once, even when the library is imported both directly and through another
library.
A bundle also publishes the value of every const it worked out at compile
time. An importer can compute with such a value just as the library could: in
the initializer of its own constants, and as an array size. The storage is
still the library's; only the value is shared.
using limits; // limits.moon: public const ROWS: i64 = 3;
const CELLS: i64 = limits.ROWS * 2; // computed at compile time
var row: array<i32, limits.ROWS> = [1, 2, 3];A library const that only gets its value at startup has no value to publish,
so it cannot be an array size in an importer either.
A bundle's globals, extern "C" var declarations included, must be declared
inside a module. A global outside any module could not be reached by an
importer, so --emit-moon rejects it, and importing a bundle that carries one
is an error.