Step 9. Modules and packages
Why#
A single file is fine for an experiment, but a program grows. OxL divides code into packages (the unit of build and versioning) and
modules (the unit of visibility). A module boundary is an encapsulation boundary: whatever is not declared pub is invisible from
outside, and changing the insides of a module does not break foreign code.
- A package is a directory with an
oxl.tomlfile. - A module is a directory inside
src/; all.oxlfiles of one directory form one module and share names. - The order of declarations in a module does not matter; cyclic dependencies between modules are an error.
Creating a project#
oxl new geo && cd geoYou get a package like this; add a module shapes and a test:
geo/
├── oxl.toml
├── src/
│ ├── main.oxl # the root module, fn main is here
│ └── shapes/
│ └── shapes.oxl # the module shapes
└── tests/
└── shapes_test.oxloxl.toml is the package manifest (III.2):
[package]
name = "geo"
version = "0.1.0"
edition = "2027"src/shapes/shapes.oxl:
pub(api) data Circle { pub(api) r: f64 }
pub(api) fn area(c: &Circle) -> f64 pure => 3.0 * c.r * c.r
fn secret() -> i32 pure => 42src/main.oxl:
use self.shapes { Circle, area }
fn main() io {
let c = Circle{ r: 2.0 }
print("area {area(&c)}")
}tests/shapes_test.oxl (tests see the package through super):
use super.shapes { Circle, area }
test "area of unit circle" {
assert_eq(area(&Circle{ r: 1.0 }), 3.0)
}$ oxl run
area 12.0
$ oxl test
test geo.tests::area of unit circle ... ok(This project is checked when the documentation is prepared: oxl run and oxl test give the output above.)
Walkthrough#
use self.shapes { Circle, area }- an import from a module of your own package. The first segment of the path isstd(the standard library),self(this package),super(the parent module, used in tests) or the name of a dependency fromoxl.toml.- Visibility: no modifier - inside the module;
pub- inside the package;pub(api)- to everyone who depends on the package (this is the public API:oxl publishchecks SemVer compatibility, §2.3). Hencefn secret()is not reachable from outside. - Fields of
datahave their own visibility: withoutpub(api)on the fieldr, the literalCircle{ r: 2.0 }outside the module does not compile (visibility). - The prelude
std.core(List,Map,print,sleep, …) is imported automatically; everything else isuse std.io { File }. - An unused import is a warning; a name conflict between two imports is an error if the name is used.
Project commands: oxl run -- arguments, oxl test, oxl fmt, oxl check; dependencies are added to [dependencies] of the
manifest, and oxl.lock pins the versions.
Common mistake: a wrong import#
use std.nothing { Thing }
fn main() io {
print("hi")
}unresolved-import
unresolved-import: there is no such module in std. The list of modules is in the standard library
reference; for your own modules check that the directory is in src/ and that the first path segment is self.
Remember#
- A package is
oxl.toml; a module is a directory;pubis within the package,pub(api)is outward. - Paths:
std…,self…,super…, a dependency name. - Tests live in
tests/or next to the code.
Next: tests.