Modules and exports
Every .zs file is a module. Declarations are private unless marked export,
and an export-only module does not need function main().
export struct Point { x: i32; y: i32;}
export function total(in point: Point): i32 { return point.x + point.y;}
function internalAudit(in point: Point): void { // private to this module}Package-internal APIs
Section titled “Package-internal APIs”internal exposes implementation declarations to sibling modules in the same
manifest-defined package without publishing them to consumers:
internal struct WindowRecord { id: String;}
export class WindowManager { internal function publish(inout this, in record: WindowRecord): void {}}Sibling modules use ordinary relative imports. A dependent package cannot
import WindowRecord or call publish, and those names are omitted from its
completion and public metadata. The owning z.json must declare
package.name; directory proximity or presence in one linked graph does not
grant access. internal and export are mutually exclusive.
The same boundary applies to internal constructor: dependent packages may
name and receive the class, but only the owning package may construct it. The
inaccessible constructor signature is omitted from public metadata and editor
call surfaces.
internal is a Z package boundary, not a C visibility annotation or a
native-code security boundary. An internal function cannot also be export c;
only explicit export c function declarations become supported C entry points
and appear in generated consumer headers. Public C wrappers may call internal Z
operations, preserving a narrow external ABI. A C-layout type that appears in
that ABI must still be described in the header even when dependent Z packages
cannot import its source declaration.
Generated framework code follows the same package boundary. A versioned
.zbuild.json overlay may assign a generated module an existing package
identity, allowing that generated module to use the package’s internal API.
A checked call adapter may carry the identity only to its synthetic replacement
member access. It does not make the member public, grant access to authored
application code, expose it through completion or metadata, or bypass
private; omitting the package identity makes the generated access external.
Namespace imports
Section titled “Namespace imports”A namespace import keeps the source module visible at use sites:
import geometry from "./geometry.zs";
const point = geometry.Point({ x: 20, y: 22 });const answer = geometry.total(point);This form is useful when a module exports several related names or when qualification makes code easier to read.
Selective imports and aliases
Section titled “Selective imports and aliases”Import only the declarations a module needs:
import { Point, total } from "./geometry.zs";import { translate as movePoint } from "./geometry.zs";Aliases are ordinary local module bindings. Hover and definition navigation should show both the local alias and original exported declaration.
Z ownership words are contextual, so an imported declaration may still use a
name such as move when the surrounding grammar is unambiguous:
import { translate as move } from "./geometry.zs";const shifted = move(point, 1, -1);Module constants
Section titled “Module constants”Modules may publish immutable compile-time values:
export const APP_NAME: String = "Z Notes";export const DEFAULT_PORT: u16 = 34115;export const ENABLE_METRICS: boolean = true;
const CONFIG_REVISION = 1;An exported or internal constant requires an explicit type so its cross-module contract cannot change when the initializer is edited. A module-private constant may infer its type.
Module storage is immutable: there is module-level const, not module-level
let. Importing a Z module does not execute a hidden initializer. Runtime
application roots use explicit ownership mechanisms such as Once<T> rather
than depending on initialization order.
The current executable constant tier accepts cleanup-free literals and owned
literal String values. General constant evaluation and ownership-bearing
runtime globals remain separate future work.
Embedded files
Section titled “Embedded files”Use std/embed when immutable file bytes should ship inside the native binary:
import embed from "std/embed";
export const INDEX_HTML: embed.StaticBytes = embed.bytes("./assets/index.html");The path must be a string literal and is resolved relative to its declaring
module. Missing files and directories are compile errors. embed.StaticBytes
is immutable process-lifetime storage with .length, checked indexing, and
implicit immutable Slice<u8> borrowing. It is cleanup-free, copyable, and
thread-shareable; runtime Arrays and Slices cannot be converted into it.
One import surface, different producers
Section titled “One import surface, different producers”The same source shape names three kinds of module:
import console from "std/console"; // standard libraryimport geometry from "./geometry.zs"; // Z sourceimport sqlite from "sqlite3.h"; // native headerResolution is not the same behind the scenes:
- Relative
.zspaths join the checked Z source graph. std/...resolves a standard-library module.- Header imports ask Clang for native ABI declarations and layouts.
Source libraries declare their package identity and public entry points in
z.json:
{ "package": { "name": "zapp", "version": "0.1.0" }, "library": { "entry": "src/zapp.zs", "exports": { "./window": "src/window.zs" } }}The consumer declares one dependency alongside its target:
{ "dependencies": { "zapp": { "path": "../zapp" } }, "target": { "name": "notes", "entry": "main.zs" }}The compiler and editor then resolve import { Application } from "zapp";
without source rewriting. The library entry supplies the package root and only
exact exported subpaths are public. Local path dependencies work transitively
in Stage 0 and the native driver; registry versions, downloads, and lockfiles
remain future layers.
Systems packages can own their native implementation requirements alongside their public source surface:
{ "package": { "name": "desktop", "version": "0.1.0" }, "library": { "entry": "src/desktop.zs", "native": { "includeDirectories": ["include"], "link": { "directories": ["lib"], "libraries": ["desktop_native"], "frameworks": ["AppKit"] } } }}Those paths resolve from the package’s own manifest and compose transitively with the consumer target. Applications select deployment policy; they do not repeat a dependency’s headers, native libraries, or frameworks. Package-native requirements are unconditional in the first tier; platform clauses follow the language’s target/comptime design later.
A native import does not introduce an automatic wrapper call. See
Native interop for header and .zd behavior.
Graph rules
Section titled “Graph rules”Z resolves an acyclic source-module graph and reports missing modules,
duplicate bindings, unavailable exports, and cycles directly. It does not use a
separate import type syntax to paper over runtime module cycles: Z imports do
not execute JavaScript-style module initialization, and accepted source graphs
remain statically resolvable.
Project entry points, artifact kinds, native include paths, libraries,
frameworks, and target settings belong in z.json. Application-specific facts
live under target; reusable implementation requirements live under
library.native. Source modules stay focused on declarations and imports.
See the complete runnable module example.
Next: Closures and function values.