Packaging and libraries
Reusable magscript goes in its own package, wired up through the manifest. This guide builds a small library, consumes it, and shares it by repository. It assumes a project from the quickstart.
A path dependency
Section titled “A path dependency”A dependency comes from a directory on disk (path) or from a repository (git). A path may
point at a package directory or at a single .mag file:
[package]name = "rig"
[dependencies]control_lib = { path = "../control_lib" }Pure functions
Section titled “Pure functions”fn declares a pure function: stateless and never
recursive. A library of them in control_lib/util.mag:
fn saturate(val: f64, lo: f64, hi: f64) -> f64 { if val < lo { lo } else if val > hi { hi } else { val }}
fn deadband(val: f64, width: f64) -> f64 { if abs(val) < width { 0.0 } else { val }}Every call needs a use, because there are no inline qualified calls:
use core::pid::Pid;use control_lib::util::{saturate, deadband};
model Rig { pid: Pid,}
step Rig(e: f64) -> (u: f64) { let cleaned = deadband(e, 0.05); let raw = self.pid(cleaned); u = saturate(raw, -10.0, 10.0);}$ magnet check Checking rig ok 1 model (Rig), 0 functionscheck counts what this package declares. Pid, saturate and deadband are compiled in but
belong to other packages. Only what the entry step reaches is compiled, so a large library costs
nothing.
Modules and imports
Section titled “Modules and imports”A use binds in the module that writes it, so every file states what it reaches for. A sibling
module of the same package is reached through crate, as in use crate::pid::Pid;.
Each module carries its own params, so a library module runs on its own. magnet simulate src/pid.mag runs Pid alone, and magnet simulate src/rig.mag runs Rig and builds its Pid
from the call params Rig writes, pid: Pid(2.0): the arguments passed there, and Pid’s own
defaults for the ones the call stops before.
Constants
Section titled “Constants”A const is an item, so use reaches it like any other. A module of constants suits values a
package states more than once, or values a script computes:
// src/values.mag, written by tools/filter.pyconst CUTOFF_HZ: f64 = 12.5;const SOC_MIN: f64 = 0.0;const SOC_MAX: f64 = 1.0;A constant’s value is an expression with arithmetic between any of these:
- numbers
- bools
- math constants
- other constants
- arrays
- composite literals
For example:
const RADIUS_M: f64 = 0.15;const SPEED_MPS: f64 = 3.6;const OMEGA_RADS: f64 = SPEED_MPS / RADIUS_M;What a constant’s expression may contain, and how it is typed, is in the reference.
A constructor takes expressions too, so a call’s argument can be a derivation. The bounds every
cell shares are stated once, as constants, and a value the command line should reach is an
argument of the constructor instead, like ic here:
use crate::values::{CUTOFF_HZ, SOC_MAX, SOC_MIN};
params Rig(ic: f64 = 0.5) { Self { filter: LowPass(CUTOFF_HZ / 2.0), soc: Integrator(0.6, SOC_MIN, SOC_MAX), soc_estimate: Integrator(ic, SOC_MIN, SOC_MAX), }}Depending on a repository
Section titled “Depending on a repository”To share a library across a team or projects, point the manifest at its repository. The two functions above are published as a public package:
[dependencies]magnet-tutorial = { git = "https://codeberg.org/AstraLabs/magnet-tutorial.git", tag = "v1.1" }The key is the import name, with hyphens folded to underscores:
use magnet_tutorial::util::{saturate, deadband};magnet checks the repository out under .magnet/deps/ on first use and works from there. Only
the first run fetches, and later builds work offline.
$ magnet check Fetching magnet-tutorial (https://codeberg.org/AstraLabs/magnet-tutorial.git) Checking rig ok 1 model (Rig), 0 functionsmagnet add writes the same manifest line, and magnet remove takes it and the checkout away. The
fetch runs before the manifest is written, so an unreachable repository leaves it unchanged.
Pinning
Section titled “Pinning”A dependency takes at most one of tag, rev or branch:
| Selector | Behaviour |
|---|---|
tag = "v1.0" |
Reproducible. Prefer this. |
rev = "a1b2c3d" |
Reproducible and exact. |
branch = "main" |
Moving. Frozen at its first resolved commit. |
| none | The default branch. Also moving and frozen. |
A tag or rev is checked against the checkout on every build, so editing the manifest is how
you change versions. A branch resolves once and holds, and every build says so:
$ magnet checkwarning: dependency `magnet-tutorial` tracks branch `main` and is frozen at 9fddb00 — run `magnet update magnet-tutorial` to move it, or pin a `tag` or `rev` for reproducible buildsMove it on request:
$ magnet update Fetching magnet-tutorial (https://codeberg.org/AstraLabs/magnet-tutorial.git) Updated magnet-tutorial 9fddb00 -> 97c7705The core library
Section titled “The core library”core is the standard library, written in magscript and shipped inside the compiler. Its
builtins and constants
are in the reference; in short, it provides four kinds of names:
- Stateful blocks such as
UnitDelay,IntegratorandPid, declared as instance fields and stepped like your own models. A stateless block such as a gain is just its expression. - Builtins, always in scope:
min,max,abs,limit, the transcendentals fromsintopowf, the array reductionsdot,amaxandnorm, and the 3-vector productcross. - Constants, always in scope and spelled as Rust spells them:
PI,TAU,E,SQRT_2and the rest ofcore::f64::consts. - Element-wise lifting. Given an array, a builtin applies per element, broadcasting scalar
arguments, as in
limit(v, -1.0, 1.0).