Modules and packages
Each .mag file is a module. It may declare one model, named after the file:
motor.mag declares Motor; speed_loop.mag declares SpeedLoop. A module
containing only functions, constants, or types needs no model.
Files and imports
Section titled “Files and imports”Sources live beside Magnet.toml or under src/. Each blocks/<name>/ directory is a
separate package named <name>, with no manifest entry needed. Directories under src/
form the module path. For example, src/control/speed_loop.mag is imported as:
use crate::control::speed_loop::SpeedLoop;craterefers to the current package; a dependency name refers to that package.- Imports bind individual items. Group imports use
use crate::values::{GAIN, LIMIT};. An item is one of:- a model
- a function
- a constant
- a composite
- an enum
- Each file declares its own imports. Calls use the imported name, such as
clamp_value(x), rather than a qualified path. - Import cycles are allowed. These recursions are rejected:
- model containment
- function calls
- composite types
- constant definitions
main.magandlib.magare reserved. The package root declares no items, except in a single-file dependency, whose items import asuse util::clamp_value;.
Composition
Section titled “Composition”Declare a child as a model field and call it with self.name(inputs).
Each field has independent state. This auto.mag uses the
overview’s Motor:
use crate::motor::Motor;
model Auto { left: Motor, right: Motor,}
step Auto(a: f64, b: f64) -> (left_speed: f64, right_speed: f64) { left_speed = self.left(a); right_speed = self.right(b);}
params Auto(gain: f64 = 2.0) { Self { left: Motor(gain), right: Motor(gain) }}Calls pass inputs in declaration order. A single output is a value; multiple
outputs are bound with let (a, b) = self.child(x);. Use
let () = self.child(); for a call with no outputs.
An instance may be stepped at most once on each execution path through a tick. A child call must be the whole right-hand side of a binding or assignment; bind its result before using it in arithmetic.
Parameters
Section titled “Parameters”model fields declare storage; params declares the constructor’s arguments
and initializes that storage. Argument names need not match field names.
// Alternative constructor for Motor.params Motor(scale: f64 = 1.0, tau: f64 = 0.2) { let k = 2.0 * scale; Self { k, tau }}- Arguments require types.
= valuesupplies a default; an argument without a default is required. - Defaults may reference constants, but not other arguments. Body
letbindings can read arguments and earlier bindings. - The body ends with
Self { ... }, initializing every model field exactly once.Self { k }is shorthand forSelf { k: k }. - Values follow the const expression rules.
Child calls such as
Motor(2.0)pass positional constructor arguments; omitted trailing arguments require defaults. - Without a
paramsitem, the model has an implicit constructor over its fields: data fields first, then child fields, in declaration order within each group. Data fields are required; a child field defaults to the child’s empty constructor call if all of that child’s arguments have defaults.
At simulation entry, --param gain=3.0 supplies or overrides the selected
model’s constructor argument. Child arguments are set by the parent constructor:
--param left.k=3.0 is rejected. Missing required arguments prevent the run.
The former params Name { ... } syntax and pub field modifier are not accepted.
Stored field literals
Section titled “Stored field literals”Inside a constructor, a model literal sets stored fields directly:
let motor = Motor { k: 2.0, tau: 0.1 };let faster = Motor { tau: 0.05, ..motor };Motor(...) takes constructor arguments; Motor { ... } takes model field
names. A literal must supply every field, or end with ..base to copy omitted
fields from a value of the same model type. ..Motor() uses the constructor’s
defaults. Bare .. is invalid, and the final Self { ... } cannot use a base.
Packages
Section titled “Packages”A package groups .mag modules under a Magnet.toml manifest. A library uses
the same layout as an application; no separate library declaration is required.
control-lib/├── Magnet.toml└── src/ ├── gain.mag └── math.magDependencies
Section titled “Dependencies”Declare dependencies in the consuming package’s manifest:
[package]name = "plant"
[dependencies]control-lib = { path = "../control-lib" }Import the package, module, and item explicitly:
use control_lib::gain::Gain;use control_lib::math::{deadband, saturate};- The dependency key supplies the import name. Hyphens become underscores.
- A
pathmay point to a package directory or a single.magfile. Directory dependencies load modules beside the manifest and recursively undersrc/. - Git dependencies use
gitwith at most one oftag,rev, orbranch. See manifest settings for syntax. - The root manifest must declare the required DSL dependencies; dependency manifests do not add transitive DSL dependencies.
coreis the embedded standard library and needs no manifest entry, for exampleuse core::integrator::Integrator;.
Imported models use the same constructor and call rules as local models. Imported types retain their identity: declaring another composite with identical fields does not create the same type.
Use magnet add, remove, and update to manage
dependencies. Rust implementations use the
external declaration boundary.