Modules & Namespaces

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:

main.sun
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>_test binary -c emits); 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 -l flags; 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, macos or windows — the OS names _target_is accepts). 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's source_files are placed before the shared list, since per-OS files typically define the primitives shared code consumes; this is how the standard library carries target_linux.sun and target_darwin.sun in 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:

  1. Relative to the entrypoint file — the directory containing the entrypoint
  2. A sun-config.json — any sun_path directories from the config files above the entrypoint (see Sun Config)
  3. --lib-path — directories given on the command line
  4. SUN_PATH — directories in the SUN_PATH environment variable
  5. The install location — /usr/lib/sun from the Debian package, $(brew --prefix)/lib/sun from Homebrew, and lib/sun beside the running sun binary

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.moon

Path 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 too

Using 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

main.sun
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"]
}
mathlib/entry.sun
/** 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
./main

Compilation Pipeline

When you run sun main.sun:

  1. Parse entrypoint — Parse main.sun and extract the manifest
  2. Parse dependencies — Parse all files listed in source_files; synthesize a module of message classes for each schema in protos
  3. Merge ASTs — Combine all parsed ASTs into a single merged AST
  4. Load libraries — Load type stubs and compiled code from libraries
  5. Semantic analysis — Analyze the merged AST with moon type information
  6. Code generation — Generate LLVM IR for the merged AST
  7. Link — Link with moon compiled code
  8. 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.sun

The compiler:

  1. Parses the entrypoint and all files listed in source_files
  2. Merges them into a single AST
  3. Compiles and serializes the code and type information
  4. 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.moon

The 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:

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" ] }
    }
}
ModuleContents
allocator.sunMemory allocators (HeapAllocator, IAllocator interface)
bigint.sunArbitrary-precision BigUint
byte_order.sunstd.byte_order — endianness conversions
env.sunstd.env — environment variables, working directory, args
event_loop.sunstd.io — epoll/kqueue readiness with EventLoop
errors.sunStandard error types (EmptyError, NotFoundError, etc.)
http.sunHTTP/1.1 server (HttpServer); the client is in tls.moon
io.sunstd.io — files, directories, Poller
iterator.sunIteration protocol (IIterator<T, Container>, IIterable<T, Self>)
json.sunJson parsing and serialization
linked_list.sunDoubly-linked list LinkedList<T>
map.sunGeneric hash map Map<K, V>
matrix.sunMatrix<T> and MatrixView<T> N-dimensional arrays
networking.sunTCP sockets (TcpStream, TcpListener) and DNS
option.sunOption<T> and _Result<T, E> payload enums
print.sunOverloaded print/println/eprint/eprintln
process.sunstd.process — Command, signals, raw fork/exec
proto_wire.sunProtobuf wire format
queue.sunFIFO Queue<T> built on LinkedList<T>
shared.sunShared<T> reference-counted handle with Mutex-guarded access
string.sunString and StringView classes
sys.sunThe stdlib's private extern "C" declarations
target_linux.sun / target_darwin.sunPer-OS constants (errno, socket options)
test.sunstd.test — assertions and the test_function runner
thread.sunstd.thread — spawn, Thread<T>, Mutex
time.sunstd.time — Duration, Instant, DateTime
unique.sunUnique<T> smart pointer with automatic cleanup
vec.sunGeneric growable array Vec<T>

When building from source, the stdlib is automatically compiled:

cmake -B build && cmake --build build
# Creates build/stdlib.moon

The 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.sun

Step 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/lib
manifest {
    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.moon

Any 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.