Visibility and Privacy

Module-private defaults, exports, imports, re-exports, private members, and generated bundle interfaces.

Each source file is a module. Top-level declarations are module-private by default, even inside package dependencies. Package boundaries affect module resolution and reproducibility; they do not make declarations public.

A top-level declaration becomes part of a module interface only when it is marked with export, @export, or named in an export { ... }; list.

export fn parse(text: str) -> i32 {
    return 0;
}

fn helper() -> i32 {
    return 1;
}

export {
    parse
};

extern, foreign, and intrinsic do not export a declaration by themselves.

Imports

Module imports bind a namespace value. Unaliased dotted imports bind the final path component and also keep the dotted path usable for member access.

import std.os;        // binds os and std.os
import std.os as sys; // binds only sys

from imports bind exported members directly:

import { write } from std.os;
import { write as out } from std.os;

Imported declarations must be exported by the imported module. Aliases only change the local binding name; they do not bypass module privacy and do not also introduce the original name.

Re-exports

Export lists may re-export imported declarations, but only when the imported declaration is already exported by its original module.

import { write } from std.os;

export {
    write
};

Private Members

Fields and methods of a visible struct are public by default. @private on a field makes ordinary field selection type-private: only methods of the declaring struct may read or write that field.

Code in the declaring module may still initialize private fields in struct literals, so constructors and factory functions can build values. Code in other modules cannot name those fields.

export struct Counter {
    @private value: i32,
}

fn Counter.new() -> Counter {
    return Counter { value: 0 };
}

fn Counter.get(self: &Counter) -> i32 {
    return self.value;
}

@private on a method makes the method module-private. It may be called inside the declaring module, but not through imports.

Interfaces

Interfaces are static constraints in the current language. An exported interface can be named in generic constraints by importing modules; a non-exported interface cannot be imported or used across a module boundary.

Default methods on interfaces are resolved statically and follow the same exported-interface visibility rule.

Generated Interfaces

Generated .zxm interfaces expose the exported module interface. They may also contain non-exported dependency declarations required to type-check an exported signature or generic body, but those declarations are not re-exported from the module.

Non-exported helper functions and unused private types are omitted from the generated interface.

Prelude

The prelude is the set of exported declarations from module builtin that are available without an explicit import in ordinary modules. Today that set is Copy, Integer, Signed, Unsigned, and DynamicError.

The builtin module namespace is also available as builtin. Keywords, built-in type tokens, Unique, and require are language/compiler names, not prelude imports.