Resource Ownership

Cuttlefish has an optional set of features that help you share memory and hardware buses safely between different parts of your program. They’re turned off by default — if you don’t use them, Cuttlefish behaves exactly like ordinary TypeScript.

Note

Who this is for. These features are aimed at larger or more demanding programs where you’re sharing data or buses between several pieces of code and want the tool to catch mix-ups for you. If you’re just blinking an LED or reading a sensor, you can skip this whole section.

Warning

Naming note. This overview page talks about the types as Ref<T> and MutRef<T>, but the dedicated pages and the rest of the docs use the names Shared<T> and Mut<T>. They’re the same feature — only the name differs. (TODO: this rename should be reconciled.)


Hardware Ownership (take & release)

In embedded systems, multiple tasks (such as a sensor reader and a display driver) often share the same hardware bus (e.g., I2C0). This can lead to race conditions where one task interrupts another’s communication, causing bus collisions or corrupted data.

The Problem

// Task A: Reading a Sensor
I2C0.device(0x76).readByte(0x00);

// Task B: Updating a Display (could potentially interrupt Task A)
I2C0.device(0x3C).writeByte(0x00, 0x55);

The Solution: Exclusive Claims

Cuttlefish provides an exclusive acquisition pattern. When you take() a bus, you receive a handle that uniquely owns that resource.

import { I2C0 } from '@typecad/board';

// Claim exclusive access
const bus = I2C0.take(); 

if (bus) {
  // We now exclusively own I2C0
  bus.device(0x76).readByte(0x00);
  
  // Return the bus to the shared pool when finished
  bus.release();
}

Implementation Details:

  • Single-Threaded (e.g., Arduino Uno): take() and release() are emitted as comments and have zero runtime cost. However, the transpiler still validates that you don’t “double-take” or forget to “release.”
  • Multi-Threaded (e.g., ESP32): These calls map to actual mutex acquisition and release, providing true thread-safe peripheral sharing.

Memory Safety Types

Cuttlefish introduces Phantom Types that allow you to specify how data is owned and borrowed. These types are erased during transpilation—resulting in zero runtime overhead—but they guide the transpiler’s static analyzer to prevent common embedded programming errors.

Owned<T>: Single Ownership

The Owned type indicates that a variable is the sole owner of its data.

  • Benefit: Prevents Use-After-Move errors.
  • How it works: When you assign an Owned variable to another, ownership is “moved.” The original variable becomes invalid, preventing bugs where two different parts of your code try to manage the same memory simultaneously.
import { Owned } from '@typecad/hal';

function process(data: Owned<Uint8Array>) { /* ... */ }

let buffer: Owned<Uint8Array> = new Uint8Array(32);

process(buffer); // Ownership is MOVED into the function

// ERROR: 'buffer' is no longer valid here because 'process' now owns it.
// console.log(buffer[0]); 

Ref<T>: Immutable Borrowing

The Ref type is used when you want to share data for reading without allowing any modifications.

  • Benefit: Prevents Accidental Mutation and Memory Bloat.
  • How it works: Cuttlefish ensures that any variable marked as Ref cannot be the target of an assignment or update.
Note

Why not just use const? While const in TypeScript prevents the variable itself from being reassigned, Ref<T> provides three additional layers of safety and performance:

  1. Deep Immutability: Unlike a standard const array, you cannot modify the contents (elements) of a Ref array.
  2. Zero-Copy (C++ References): For non-primitive types, the transpiler emits a C++ reference (const T&). This means the data is shared by address rather than being copied, saving CPU cycles and RAM.
  3. Stability Guarantees: The analyzer ensures that while a Ref borrow is active, no other part of the code can obtain a MutRef to the same data, preventing “spooky” bugs where data changes while you are reading it.
import { Ref } from '@typecad/hal';

const config: Ref<number[]> = [1, 2, 3];

function readConfig(c: Ref<number[]>) {
  console.log(c[0]);
  // c[0] = 5; // ERROR: Ref prevents modifying elements (Deep Immutability)
}

readConfig(config); // Pass-by-reference (Zero-Copy)

MutRef<T>: Mutable Borrowing

A MutRef is an explicit “mutable borrow.” It allows you to pass data to a function or variable that needs to modify it in-place.

  • Benefit: Prevents Shared Mutability bugs.
  • How it works: While Ref allows many readers, MutRef documents that a specific part of your code has temporary, exclusive permission to change the data. It makes data flow explicit and easier to debug.
import { MutRef } from '@typecad/hal';

function calibrate(val: MutRef<number>) {
  val = val + 10; // OK: MutRef specifically allows mutation
}

let offset: number = 5;
calibrate(offset);

When to Use These Types

If you are a developer coming from high-level languages like JavaScript, you might be used to objects being shared automatically. In the embedded world, this “hidden sharing” often leads to:

  1. Race Conditions: Two tasks changing the same variable at once.
  2. Memory Corruption: A function modifying a buffer that another function thought was constant.
  3. Dangling Pointers: Using a piece of memory after it has been deleted or repurposed.

By using Owned, Ref, and MutRef, you turn these dangerous runtime crashes into simple red squiggles in your editor.


Zero-Cost Abstractions

A core principle of Cuttlefish is that safety should not come at the cost of performance. Because these rules are enforced at the transpiler level, the generated C++ is identical to hand-written code:

  • Owned<number> emits as int.
  • Ref<number> emits as const int.
  • take() and release() on 8-bit platforms emit as code comments.

API & Type Reference

Bus Ownership Handle

MethodDescription
bus.take()Attempts to acquire exclusive ownership. Returns undefined if already owned.
bus.release()Returns ownership of the bus to the system.

Ownership Phantom Types

TypeC++ EmissionRule Enforced
Owned<T>TPrevents use-after-move (Transfer of ownership).
Ref<T>const TPrevents reassignment and modification (Immutability).
MutRef<T>TExplicitly marks a mutable reference for clarity.

Complex Example: Safety Across Functions

import { Owned, Ref } from '@typecad/hal';

/** Reads data without taking ownership (Borrowing) */
function analyze(data: Ref<Uint8Array>) {
  const first = data[0];
  console.log(`Analyzing: ${first}`);
}

/** Processes data and takes full ownership (Moving) */
function archive(data: Owned<Uint8Array>) {
  // Logic to move data to long-term storage...
}

function run() {
  const myData: Owned<Uint8Array> = new Uint8Array([0x01, 0x02]);

  analyze(myData); // OK: Borrowing allowed while owned
  
  archive(myData); // OK: Ownership transferred (MOVE)
  
  // analyze(myData); // Error: Cannot borrow 'myData' after ownership was moved
}