Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Modules and Packages

We have used mod, use, pub, and prelude.ar informally throughout the book. This chapter pulls them together: how a package is laid out, what the visibility tiers actually mean, and how the conventions compose.

Files are modules

Every .ar file under src/ is a module. The file path under src/, with / replaced by ::, is the module’s qualified path:

src/prelude.ar              →  lease_tutorial::prelude
src/party.ar                →  lease_tutorial::party
src/lease.ar                →  lease_tutorial::lease
src/legal/california.ar     →  lease_tutorial::legal::california
src/legal/mod.ar            →  lease_tutorial::legal

Two file-name conventions carry weight:

  • prelude.ar is the package’s library entry. It is also aliased as pkg::prelude, so consumers use lease_tutorial::prelude::* to bring in the whole curated surface in one line.
  • mod.ar is a directory module’s primary file. When you have src/legal/california.ar and want to add additional content to the legal module itself (rather than legal::california), put it in src/legal/mod.ar. Without mod.ar, src/legal/ is a namespace only — you can use lease_tutorial::legal::california but lease_tutorial::legal itself has no items.

A third file role: root.ar is the project entry — declared in [project] entry, it is the file the toolchain starts module discovery from. Both library packages (which expose a curated surface through prelude.ar) and application packages have a root.ar; the file’s job is to use foo::* every internal submodule so the project’s full module graph is reachable from one anchor.

Inline mod

A module can also be declared inline:

mod legal {
    pub kind Statute { ... }

    mod federal {
        pub kind FederalLaw : Statute { ... }
    }
}

Inline modules nest. The path for FederalLaw is lease_tutorial::legal::federal::FederalLaw. Use inline mod when the sub-module is small enough that splitting into its own file would be overkill. Use a separate file when the sub-module has substantial content.

Variants: _catalog/mod-inline/{minimal,nested-mod,mod-with-pub-items,cross-package,negative-bad-shape}/ — the cross-package/ variant exercises inline-mod items re-exported across a workspace boundary; the nested-mod/ variant demonstrates the qualified-path resolution shown above.

A worked excerpt from _catalog/mod-inline/nested-mod/src/prelude.ar:

use metatypes::*

mod jurisdiction {
    pub kind Statute {
        cite: String,
        text: String,
    }

    mod federal {
        pub kind FederalLaw <: Statute {
            uscode_section: String,
        }
    }

    mod state {
        pub kind StateLaw <: Statute {
            state_code: String,
        }
    }
}

// Qualified-path resolution works across inline mods:
use jurisdiction::federal::FederalLaw
use jurisdiction::state::StateLaw

Inline modules nest as a tree; each level adds one segment to the qualified path. The compiler treats jurisdiction::federal exactly as it would a src/jurisdiction/federal.ar file.

use

Three import shapes:

use ufo::prelude::*                          // glob — every exported item from the path
use ufo::a::endurants::{Object, Person}      // named — specific items
use ufo::a::endurants::{Object as Thing}     // named with rename
use lease                                    // module — the module itself, not its items

The as rename form is the canonical fix when two upstream packages export the same name and you need both in scope. Variants: _catalog/use-rename/{minimal,cross-package-rename,composition-with-re-export,negative-name-collision,idiom}/.

// continuing the import patterns above
use produce::Apple
use tech::{Apple as Computer}   // disambiguates the collision

Choose by intent. Glob imports are convenient for prelude-style modules whose items you almost always want; named imports are explicit about what you depend on; module imports let you write lease::Lease when you want the qualifying segment to appear at the call site.

pub use re-exports

pub use is the canonical pattern for curating a public surface:

// in src/prelude.ar
pub use metatypes::*
pub use party::*
pub use lease::*

Each pub use ::* brings the named module’s exported items into prelude and re-exports them. A consumer who writes use lease_tutorial::prelude::* sees everything, without knowing or caring which internal module declared what.

The pattern decouples your public API from your internal organization. You can split lease.ar into three files later — core.ar, subtypes.ar, lifecycle.ar — and update prelude.ar to re-export from them; no consumer code changes.

Two-tier visibility

There are exactly two visibility levels:

VisibilityReachDefault?
pubCross-module (and cross-package, when re-exported)No — opt-in
Module-internalFile-scoped onlyYes

There is no third tier. There is no per-package or per-workspace pub(crate) equivalent. The two-tier discipline is the design choice — when you want a wider surface, re-export through prelude.ar; when you want a narrower one, leave items unmarked.

The default direction matters: an Argon item is file-scoped until you export it. This is the reverse of OOP-conventional public-by-default. Opting into export is explicit.

The direct-dependencies rule

A package can use only items reachable through its own [dependencies], not items reachable transitively through some dependency’s dependencies.

If your package imports cofris and cofris itself imports coex, your package cannot use coex::Foo unless you also list coex in your own [dependencies]. The compiler rejects the transitive import; the rule is not a warning.

The intent is clarity: a package’s manifest is a contract over what it depends on. Transitive use creates accidental coupling — when an upstream removes a dep, downstreams break unexpectedly. The direct-dependencies rule eliminates the surprise.

Putting the conventions together

A typical small package:

my-domain/
├── ox.toml                # package manifest
├── ox.lock                # lockfile (generated)
└── src/
    ├── prelude.ar         # pub use re-exports — the curated surface
    ├── core.ar            # central concept declarations
    ├── rules.ar           # derives + asserts
    ├── queries.ar         # named queries
    ├── computes.ar        # pure functions
    ├── mutations.ar       # state-changing operations
    └── tests.ar           # test blocks

prelude.ar re-exports from each file. Internal modules stay private-by-default; only items they pub slip through prelude’s globs.

A larger package adds directories:

my-large-domain/
├── ox.toml
├── ox.lock
└── src/
    ├── prelude.ar
    ├── party/
    │   ├── mod.ar
    │   ├── person.ar
    │   ├── org.ar
    │   └── relationship.ar
    └── lease/
        ├── mod.ar
        ├── lease.ar
        ├── lifecycle.ar
        └── subtypes.ar

Each directory’s mod.ar re-exports from its siblings. The package’s prelude.ar re-exports from each top-level directory. Consumers use my_large_domain::prelude::* and the whole curated surface lands.

Edge cases worth knowing

  • Module names canonicalize. my-package becomes my_package for use paths. Hyphens in package names are friendly for the registry; underscores are the canonical form in code.
  • No item renames at the pub use site. You can rename at the use site (use foo::{Bar as Baz}); you cannot at the pub use site. Rename via a wrapper module if you need this.
  • Glob re-exports do not propagate beyond direct re-export. If pkg-A’s prelude is pub use pkg-B::* and pkg-B’s prelude is pub use pkg-C::*, then pkg-A’s prelude exposes everything from pkg-B’s prelude including its re-exports of pkg-C. The fixpoint is computed at elaboration; chain re-exports work as expected.
  • pub does not mean “registered globally.” Visibility is per-package; the registry is a separate concern.

Summary

Files are modules; mod declares inline modules; use imports; pub opts items into the cross-module surface; pub use re-exports through the prelude. The default visibility is module-internal; the two-tier system is the discipline. The direct-dependencies rule keeps imports honest. The lease tutorial uses these conventions throughout.