Owned, Shared & Mutable
A hal program becomes C++, and these three type annotations decide the memory behind it: who owns a value, who may read it, and who may write it. They are checked at transpile time — the mistakes that become dangling pointers and surprise copies in C++ are errors and warnings here.
- Owned<T> — the value is yours. Assigning it to someone else moves it; using it afterward is refused.
- Shared<T> — a read-only borrow. Zero copy, any number of them, and nothing can be written through one.
- Mutable<T> — a writable borrow. Writes through it reach the original.
Owned<T>
Marks a value you own. Assignment transfers ownership, and the transpiler tracks the transfer: the source becomes unusable, so use-after-move bugs never reach the board.
Example first
import { UART0 } from '@typecad/hal';
let packet: Owned<number[]> = buildPacket();
let archived = packet; // ownership moves — 'packet' no longer has it
UART0.writeLine(String(packet.length));
// ✗ error: 'packet' was moved and cannot be used againEvery unannotated variable already behaves this way — assignment copies, and each copy is its
own value. What Owned adds is the tracking: the move is recorded and a later use
of the source is an error.
Shared<T>
A read-only borrow: the data stays where it is and you get a const view of it. Non-primitives lower to const T& — no copy at all; primitives borrow by value as const.
Example first
import { UART0 } from '@typecad/hal';
const view: Shared<number[]> = readings; // zero-copy, read-only
const limit: Shared = config.threshold; // primitives borrow by value
UART0.writeLine(String(view.length)); // reading is what it's for
view = filtered;
// ✗ error: Cannot assign to 'view' — it is an immutable borrow
// hint: change 'view: Shared<number[]>' → 'view: Mutable'Any number of Shared borrows of one value can coexist — that is the point of
immutable data. Writing through one is refused at transpile time, with a hint that names the Mutable annotation that would allow it.
Mutable<T>
A writable borrow. The function that receives one can change what it points at, and the changes are visible to the caller — no copies, no return values, no out parameters.
Example first
function scale(values: Mutable<number[]>, factor: number) {
for (let i = 0; i < values.length; i++) {
values[i] = values[i] * factor; // writes reach the caller's array
}
}
scale(samples, 0.5); // in place — no copy, nothing returnedNon-primitives lower to T&. Primitives stay passed by value — a borrow of an int saves nothing — so annotate the arrays, strings, and structs.
Why parameters want it
An unannotated struct parameter arrives as a copy, and mutating that copy silently does nothing to the caller's value:
function zero(reading: Reading) { // no annotation — passed by value
reading.value = 0;
// ⚠ changes are invisible to the caller
// hint: use 'reading: Mutable<Reading>' to pass by reference (T&)
}The transpiler catches this pattern — changes are invisible to the caller — and
the fix is the annotation: reading: Mutable<Reading> passes by reference
so the write lands.
bus ownership — take / release
Memory isn't the only thing worth owning. Buses are shared hardware, and the same opt-in model guards them: take a bus before using it, release it when you're done. I2C0, I2C1, SPI0, SPI1, and UART0 are recognized.
Example first
const i2c = I2C0.take();
const sensor = i2c.device(0x44);
const reading = sensor.readReg(0x00);
i2c.release(); // other code may take it nowNothing is emitted — take() returns the same bus object; the ownership is a
compile-time claim. The validator checks the discipline:
- Taking an already-taken bus — error, with the fix in the hint
- Releasing, or doing I/O on, a bus nobody took — error
- Taken and never released — warning (a leak, not necessarily a bug)
The configuration is the setup
There is no begin(). Peripherals are ready the moment your program runs, because
construction is the configuration — the arguments you pass become devicetree nodes,
Kconfig lines, and guarded one-time driver calls, not runtime state.
- Use-before-configure can't happen — there is no configure step to skip
- Double-configure can't happen — the facts are compile-time, applied once before main()
- Two peripherals claiming the same pins is a build error (conflict detection)
- Requesting a peripheral the board lacks is a module-resolution error
Runtime verbs that genuinely reconfigure — PWM.setPeriod(), WiFi.leave() — are ordinary methods on a configured object, not a second
constructor.
What each one lowers to
| Annotation | Meaning | C++ the transpiler emits |
|---|---|---|
| (no annotation) | A private copy — every assignment copies | C++ value |
| Owned<T> | You own the value; assigning it moves it | C++ value — use after the move is an error |
| Shared<T> | Read-only borrow — as many as you like | const — non-primitives borrow as const T& |
| Mutable<T> | A writable borrow — the one place writes are allowed | T& for non-primitives; writes reach the source |
The checks
All of this runs while your program is transpiled — nothing on the board, nothing at runtime. Errors stop the build; warnings and infos explain themselves in the output.
| Check | Severity | When it fires |
|---|---|---|
| Use after move | error | Reading a variable whose ownership already moved — 'was moved and cannot be used again' |
| Assign to Shared | error | Writing through an immutable borrow — the hint says exactly what to change to Mutable |
| Update to Shared | error | ++ / -- through an immutable borrow |
| Shared passed as Mutable | error | A read-only borrow handed to something that writes — 'expects a mutable borrow' |
| Dangling borrow | error | Returning a borrow of a local — 'destroyed when this function returns' |
| Borrowing a temporary | warning | Shared/Mutable initialized from a call result — C++ cannot reference an rvalue, so it falls back to a copy |
| Silent copy | info | An unannotated non-primitive assigned from a variable — suggests Shared for zero-copy |
| Const suggestion | info | A let never reassigned emits as C++ const — the compiler can place it in ROM and fold it |