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 imports

Split a program into modules: reusable logic in one, main in another, tests in a third. After this page you can build, test and run a three-module project by hand.

Want a ready-made layout instead? Run semaprax new <dir> (see Manifests). To follow along, create a tutorial/ directory with a src/ subdirectory and save each block at its stated path.

1. Describe the project

Save this as tutorial/semaprax.toml:

schema = "semaprax.manifest.v1"

[package]
name = "handbook-demo"
version = "0.1.0"

[modules]
entry = "tutorial.app"
sources = ["src/app.spx", "src/core.spx", "src/tests.spx"]
tests = ["tutorial.tests"]

[exports]
web = ["tutorial.add"]

entry is the module with main. sources lists the files. [exports] web lists the functions a web build exposes, by stable ID.

2. Write the reusable function

Save this as tutorial/src/core.spx:

module tutorial.core;

@id("tutorial.add")
fn add(left: i64, right: i64) -> i64
    requires left >= 0
    requires right >= 0
    ensures result == left + right
{
    left + right
}

This module has no main: it only provides add. Check it through the project, not alone (a single file without main fails with SPX-T105).

3. Import it into the entry module

Save this as tutorial/src/app.spx:

module tutorial.app;
use function @id("tutorial.add") from tutorial.core as add;

@id("tutorial.main")
fn main() -> i64
{
    add(19, 23)
}

Read the import as a sentence: use the function with ID tutorial.add from module tutorial.core, and call it add here. An import names a stable ID, not a file path. Imports come right after the module line, before any permit block.

4. Add tests

Save this as tutorial/src/tests.spx:

module tutorial.tests;
use function @id("tutorial.add") from tutorial.core as add;

@id("tutorial.tests.add")
fn test_add() -> i64
{
    if add(19, 23) == 42 { 0 } else { 1 }
}

@id("tutorial.tests.zero")
fn test_zero() -> i64
{
    if add(0, 0) == 0 { 0 } else { 1 }
}

@id("tutorial.tests.main")
fn main() -> i64
{
    0
}

Each test_* function returns 0 to pass. The manifest’s tests entry tells the runner which module to read. See Testing.

5. Run the project

From inside tutorial/:

semaprax fmt .
semaprax check .
semaprax test .
semaprax run .
semaprax query . --id tutorial.add

test prints project tests passed, run prints 42, and query prints the tutorial.add declaration. A directory operand (.) selects its semaprax.toml.

Keep names and paths separate

src/core.spx          file containing source
    tutorial.core     module declared by that file
        tutorial.add  persistent identity of one function
            add       local name used at a call site

A display rename keeps tutorial.add. Moving a file means updating the manifest path. Renaming a module means updating its imports. Three separate edits, which is why the ID is the stable handle.

Fix common mistakes

SymptomCheck first
The new file seems invisibleIts path is present in sources.
An import cannot be resolvedThe provider’s module name and declaration ID both match.
Tests are not runningThe module is in tests, and test functions use the test_ prefix.
A helper works alone but fails when linkedThe project’s profile admits its signature.
A previous semantic preview is staleRe-query the project after changing source or the manifest.
SPX-G172 or SPX-T105 on one fileCheck the project (semaprax check .), not the single file.

Use a standard-library module

Declare the package, then import from it:

semaprax add . std.num "^0.1.0"     # adds one [dependencies] row
semaprax help library std.num       # signatures and stable IDs

add edits only the manifest. Bundled std.* packages are version 0.1.0 and need no download. See the standard library.

Next: Choose a profile for richer data, or learn how the test runner reports failures.