Module System
Shape modules are loaded through imports and project/script configuration.
For the target scoping model, see Names and Scope.
With the scoped import model, user-facing names come into scope only through
use / from ... use or the tiny implicit prelude. Importing a module
namespace does not inject its functions, types, or annotations as bare names.
Import Forms
Section titled “Import Forms”Namespace import
Section titled “Namespace import”use std::core::state
let frame = state::capture()Namespace alias
Section titled “Namespace alias”use std::core::state as s
let frame = s::capture()Named import
Section titled “Named import”from std::core::snapshot use { Snapshot }Named alias
Section titled “Named alias”from math::stats use { mean as avg }Annotation import
Section titled “Annotation import”from std::core::remote use { @remote }
@remote("worker:9527")fn process(data) { data }Namespace-qualified annotation
Section titled “Namespace-qualified annotation”use std::core::remote
@remote::remote("worker:9527")fn process(data) { data }Module Paths
Section titled “Module Paths”Module paths use :: separators:
from mylib::transforms::normalize use { normalize }Namespace calls use :: as well:
use mylib::transforms::normalize as normalize
let value = normalize::run(input)Resolution Order
Section titled “Resolution Order”Imports resolve in this order:
- Extension-provided modules (highest priority)
- Bundle-provided modules (from
.shapecdependencies) - Embedded standard library (
std::*) - Filesystem: project root, dependency paths,
[modules].paths, stdlib path
Exports
Section titled “Exports”pub fn clamp(x: int, lo: int, hi: int) -> int { if x < lo { lo } else if x > hi { hi } else { x }}
pub type Bounds { lo: int, hi: int}
pub annotation traced(tag) { targets: [function]}Then:
from math_utils use { clamp, Bounds, @traced }Module File Layout
Section titled “Module File Layout”Shape maps the filesystem to module paths directly. A file at
math/linalg.shape is importable as math::linalg. A file at
transforms/normalize.shape becomes transforms::normalize.
myproject/├── shape.toml├── main.shape└── math/ ├── linalg.shape → import as "math::linalg" └── stats.shape → import as "math::stats"To add extra search roots beyond the project directory, configure [modules]
in shape.toml:
[modules]paths = [ "./vendor", # local vendor directory "/usr/local/shape", # system-wide modules]With this config, vendor/mylib/utils.shape is importable as mylib::utils.
Two-File Library Example
Section titled “Two-File Library Example”A minimal library with two files that import each other:
from std::core::intrinsics use { Vec }from math::stats use { mean }
pub fn normalize(v: Vec<number>) -> Vec<number> { let mu = mean(v) v.map(|x| x - mu)}from std::core::intrinsics use { Vec }
pub fn mean(v: Vec<number>) -> number { v.sum() / v.length}
pub fn variance(v: Vec<number>) -> number { let mu = mean(v) v.map(|x| (x - mu) * (x - mu)).sum() / v.length}Then from main.shape:
from math::linalg use { normalize }from math::stats use { variance }
let data = [10.0, 12.0, 11.5, 13.0, 9.5]let normed = normalize(data)let v = variance(data)print(f"normalized: {normed}")print(f"variance: {v}")- Use
from ... useas the default import style in this book. - Use
use module_pathwhen you want a namespace andmodule::member(...)call sites. - Import annotations explicitly with
@so they remain distinct from ordinary values and types. - Keep module APIs typed and small.
- Extensions expose modules through the same runtime module loader path.