OxL Reference

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.toml file.
  • A module is a directory inside src/; all .oxl files 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#

sh
oxl new geo && cd geo

You get a package like this; add a module shapes and a test:

oxl
geo/
├── oxl.toml
├── src/
│   ├── main.oxl            # the root module, fn main is here
│   └── shapes/
│       └── shapes.oxl      # the module shapes
└── tests/
    └── shapes_test.oxl

oxl.toml is the package manifest (III.2):

toml
[package]
name = "geo"
version = "0.1.0"
edition = "2027"

src/shapes/shapes.oxl:

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 => 42

src/main.oxl:

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):

oxl
use super.shapes { Circle, area }

test "area of unit circle" {
  assert_eq(area(&Circle{ r: 1.0 }), 3.0)
}
sh
$ 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 is std (the standard library), self (this package), super (the parent module, used in tests) or the name of a dependency from oxl.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 publish checks SemVer compatibility, §2.3). Hence fn secret() is not reachable from outside.
  • Fields of data have their own visibility: without pub(api) on the field r, the literal Circle{ r: 2.0 } outside the module does not compile (visibility).
  • The prelude std.core (List, Map, print, sleep, …) is imported automatically; everything else is use 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#

oxl✓ checked in CI
use std.nothing { Thing }

fn main() io {
  print("hi")
}
error
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; pub is 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.

See also

OxL 1.0.0-beta · Documentation version 1.0.0-beta© OxwaldLink · generated from the Standard, the std sources and the guides