The Missing Bit

Giving every function a controlled context

2025-07-21

One of the first ideas I have for ten is the ability to control the execution of each function through meta arguments.

In Zig for example, every function that does an allocation requires an Allocator. I think this is a very important concept, because it lets you control how memory will be allocated in your program, and there are environments where this is a necessity (Embedded, Wasm, ...).

But when I first encountered the Zig allocator pattern, I had been doing embedded development for some time, and I thought "this is not enough, I want to be able to pass more down, for example a HAL on how to do I2C".

The idea is to be able to do led_on() on Linux or embedded target, and on Linux it writes 1 to a file and on the embedded target it writes to I2C.

To control this, I thought of meta arguments. Meta arguments are passed to all functions down the stack.

So if you do this for example:


fn a() {
  return b();
}

fn b() {
  return @alloc(10);
}


fn main() {
  a[alloc=my_allocator]();
}

It is like an implicit context (like in OpenGL) but with a clearly defined language semantic. Also, it requires a mechanism to be made thread safe, I don't have the full solution to this yet, but I have some ideas.

Currently, I have a fairly good idea on the memory related meta arguments.

  • alloc which defines the allocator to be used
  • memory which defines the memory management strategy, at present I thought of arc (automatic reference counting, which would be the default) and leak which would simply leak memory (for arena allocators). But it could also be gc or anything else.

The meta arguments are available to the functions themselves, but also to the compiler. For example, the memory meta argument would be used by the compiler to introduce retain and release calls.

In addition to the memory, as I explained earlier with my led_on() example, I want to be able to control all IO (effects).

Some are pretty standard, like time, but I want to support user defined ones, like the I2C example above, the embedded SDK would define custom ones with an interface.

Something like:


const micro_ten = import("micro_ten"); // Some generic interface definition for
                                       // embedded
const stm32 = import("stm32_hal"); // The actual stm32 implementation
const linux = import("micro_ten.linux_hal"); // Some emulation layer for testing


// We access the hal meta argument inside the micro_ten namespace, which is a
// variable
fn led_on() {
  micro_ten@hal.i2c.write(0xff, 0xff);
}


// we run on stm32
fn main() {
  led_on[micro_ten=stm32]();
}

// we run on linux
fn main() {
  led_on[micro_ten=linux]();
}

I am not fully sure about the syntax, because it puts the function arguments far from the function name, so I might do:


// we run on linux
fn main() {
  [micro_ten=linux] {
    led_on();
  }
}

Also, I am still thinking on how to properly namespace it, but I think you would just have to require the library like this:


// This uses the micro_ten library as
// declared in the build file, can be anything (git repo...)

const micro_ten = import("micro_ten");
const micro_ten2 = import("micro_ten2");

// we run on linux
fn main() {
  [micro_ten=linux, micro_ten2=linux] {
    led_on();
  }
}

Now, in the led_on() function itself, you also require the library:


// We actually require the micro_ten2 library, even if we call it micro_ten
const micro_ten = import("micro_ten2");

fn led_on() {
  micro_ten@hal.i2c.write(0xff, 0xff);
}

Inside the micro_ten library, you would have the hal trait definition:


// syntax idea
export const hal = {
  i2c: trait = {
    fn write(a: u8, b: u8);
  }
}
If you wish to comment or discuss this post, just mention me on Bluesky or email me.