Packages and Modules

Rux organises code at two levels. A package is the unit that is compiled, versioned and shared: a Rux.toml manifest and the source files under Src/. A module is a named namespace inside a package, declared with module A::B { … }. Packages are described by the packaging guide; this chapter covers what the source sees of them — modules, imports and visibility.

module-decl = [ "pub" ] "module" identifier { "::" identifier } "{" { declaration } "}"

Files are not modules

Every .rux file under Src/ belongs to the package, and the compiler reads them all as one unit. A file adds nothing to any name: its own name means nothing to the compiler, and a package may be split across as many files as is convenient — or none of them besides Src/Main.rux. There are no mod lists or file-level module X; headers; where a declaration lives is decided only by the module { } blocks around it.

error: expected '{' to start the module body before ';'

is what a file-level module Text; produces. Write the block form.

The package root

A declaration that is not inside any module block sits at the package root. Every root item of a package is one namespace, whichever file declares it — two files cannot both declare a root function with the same signature:

error: function 'Area' has the same parameter signature as an earlier overload

Seen from another package, a root item is reached as Package::Name, where Package is the package's import name (see Imports).

Module declarations

module opens a namespace and holds any declarations: functions, types, constants, extend blocks, extern blocks, imports, when blocks and further modules. A module may appear at the package root, inside another module, or inside a when branch — never inside a function.

A path declares the nested modules in one step. These two are the same declaration:

module Shape::Circle {
    func Area(radius: float64) -> float64 { return 3.14159 * radius * radius; }
}

module Shape {
    module Circle {
        func Area(radius: float64) -> float64 { return 3.14159 * radius * radius; }
    }
}

(Written together, as here, they would clash: both declare Shape::Circle::Area with the same signature.)

A module is open: every module block with the same path adds to the same module, in the same file or in different ones. Two functions called Area do not clash when they are in different modules, because a module's name is part of the full name of everything in it.

Example

A package called Geometry with two files. Src/Shapes.rux declares a root constant and two modules under a shared parent:

// The package root: these declarations are in no module.
const Pi: float64 = 3.14159;

module Shape::Circle {
    func Area(radius: float64) -> float64 {
        return Pi * radius * radius;
    }
}

module Shape {
    module Rectangle {
        func Area(width: float64, height: float64) -> float64 {
            return width * height;
        }
    }
}

Src/Main.rux reaches them in two ways:

import Io::PrintLine;
import Geometry::Shape::Circle;

func Main() -> int {
    PrintLine("circle    {}", Circle::Area(2.0));
    PrintLine("rectangle {}", Shape::Rectangle::Area(3.0, 4.0));
    return 0;
}
circle    12.56636
rectangle 12.0
flowchart LR
    pkg(["`package Geometry`"]) --> pi["`Pi`<br/>(root)"]
    pkg --> main["`Main`<br/>(root)"]
    pkg --> shape["`module Shape`"]
    shape --> circle["`module Circle`<br/>`Area`"]
    shape --> rect["`module Rectangle`<br/>`Area`"]

Name lookup inside a package

A name is looked up in the innermost module that encloses its use, then in each enclosing module, and finally at the package root. So Circle::Area above can use the root constant Pi unqualified, and code inside Shape::Rectangle could call Circle::Area through their shared parent.

Every root item — including each top-level module — is visible throughout the package without an import, which is why Main can write Shape::Rectangle::Area(…). A nested module or an item inside one is reached either by its path from the root or by importing it: import Geometry::Shape::Circle; makes Circle a name of its own. An import always starts with a package name, the package's own included — see Imports.

Inside one package every declaration is usable from every file, pub or not. pub only matters to the packages that depend on this one — see Visibility.

See also