opaque_counter example¶
The simplest possible opaque-state object: a heap-allocated counter that Python
can increment and query, demonstrating the create_impl / destroy_impl
lifecycle without any scalar state fields.
TL;DR — see it work first¶
Prerequisites¶
Or with pip if just-makeit is already installed:
What it demonstrates¶
- Declaring an opaque state field (
uint64_t *counter on the heap) - Writing
create_impl/destroy_implbodies inline in the TOML fragment - Why opaque fields have no auto-generated constructor param, getter, or setter — Python sees nothing of the pointer
- The
no_state = "true"flag combined with opaque fields (the auto-generated struct machinery is bypassed; you own the struct entirely) - The
jm apply <fragment>workflow: declare the component once in TOML, materialize everything with one command
1. Write the fragment¶
# counter.toml
[counter]
arg_type = "void"
return_type = "uint64_t"
mutable = "true"
no_state = "true"
create_impl = """
obj->count = calloc(1, sizeof(uint64_t));
if (!obj->count) { free(obj); return NULL; }
"""
destroy_impl = "free(state->count);"
[[counter.state]]
name = "count"
type = "uint64_t *"
opaque = true
Two things to notice:
no_state = "true"suppresses the auto-generated constructor parameters and getter/setter scaffolding for all fields. Combined with theopaque = truefield, Python gets a plainCounter()with no arguments.- The
create_implbody seesobj(the freshlycalloc'd struct pointer);destroy_implseesstate. The trailingfree(state)is appended automatically.
2. Apply and build¶
just-makeit new opaque_demo
cd opaque_demo
just-makeit apply ../counter.toml
cmake -B build && cmake --build build
ctest --test-dir build
jm apply copies counter.toml into objects/counter.toml, registers it
via include = ["objects/*.toml"], and materialises every file the spec
implies — C source, binding, tests, type stub, CMake wiring — all green on
first build.
3. Implement step()¶
Open native/inc/counter/counter_core.h and fill in the stub:
4. Use from Python¶
from opaque_demo import Counter
c = Counter()
print(c.step()) # 1
print(c.step()) # 2
c.reset()
print(c.step()) # 1
with Counter() as c2:
c2.step()
c2.step()
print(c2.step()) # 3
# destroy() frees the heap counter automatically
Key concepts¶
Opaque fields are invisible to Python. The uint64_t *count field appears
in the C struct but generates no __init__ parameter, no getter, and no setter.
create_impl initialises it; destroy_impl releases it; reset_impl (if
provided) preserves or re-zeros it.
create_impl sees obj, not state. The fresh struct is named obj
inside create_impl to avoid a C redeclaration error when a state field is
called state. Every other lifecycle function uses state.
Always pair create_impl with destroy_impl for owned pointers. The
validator requires create_impl when opaque fields exist; it does not require
destroy_impl (because some pointers are borrowed). For owned heap memory,
always add teardown.
See also¶
- Declarative scaffolding — opaque state fields
- delay_line example — a realistic circular buffer using the same pattern
- Types reference — state variable types