Skip to content

[Roadmap] Appendix B Q6 — decide how Brainrot owns native resources (gates struct returns) #323

Description

@leo-aa88

Problem / motivation

Appendix B Q6 in docs/ROADMAP.md asks how
Brainrot owns native resources:

Ownership of native resources. Textures, sockets, and map entries all
outlive statements. Brainrot has no destructors and no GC. Handles sidestep
this by keeping ownership in C — is that the general answer?

It is now the single largest constraint on the FFI, and it is measurable.
STDROT_STRUCT (#208, ABI v3) carries an aggregate into a native by value,
but a struct return is rejected outright at analysis time, because
returning one needs an ownership answer this ABI has not chosen. The roadmap's
own sketch for the generated binding is not implementable as written for
exactly that reason — stdrot_struct("Texture2D", &tex, sizeof(tex)) returns
the address of a local that dies with the call, and STDROT_STRING's
"deep-copy immediately" trick cannot rescue it, since that trick relies on the
borrowed storage still being alive at the moment of return.

The cost, from the generator's own report:

Skipped by rayrot-gen Count
struct returns 113
handle-like struct params (Image, Font, Model, Sound, Shader) 102
structs with pointer/array fields 19
const char * returns 14
callback params 7
varargs 2

113 of the 239 skipped raylib functions — more than the other five categories
combined — and the 102 handle-like params are the same question wearing a
different hat. Answering Q6 is worth more to this binding than every other open
item put together.

Proposed solution

This needs a decision before it needs code. Three shapes, none obviously
right:

  1. Handles, ownership stays in C — what Road A already does
    (rayrot/raylib.c keeps a Texture2D textures[] and Brainrot holds an
    integer index). Proven, and already shipped. But every resource type needs
    its own table, handles have no generation counter so a stale index silently
    aliases whatever loads into that slot next, and the generator cannot
    invent a table per type without being told which types are resources.
  2. Ownership transfer to the interpreter — a returned aggregate becomes an
    interpreter-owned blob freed with the scope. Needs an allocator agreement
    across the .so boundary, and an answer for aggregates that own nested
    heap memory (Image.data, Font.glyphs) — a byte copy of those is a
    double-free waiting to happen.
  3. Something GC-shaped or refcounted — a language-design project of its
    own, and the roadmap does not otherwise commit to one.

Whichever is chosen, the follow-on work is: lift the STDROT_STRUCT return
rejection in semantic_check_native_call(), add the return-side marshalling
marshal_native_return_value() (ast.c) currently has no case for, and teach
rayrot/rayrot_gen.py to emit returning adapters — its skip bookkeeping
already reports exactly which functions become available.

Alternatives considered

Leaving it. That is a defensible answer and is what has happened so far — the
argument direction alone was enough to make the generated binding useful, and
the rejection is loud and documented rather than silent. What it costs is that
Vector2 GetMousePosition(void)-shaped functions, which is most of raylib's
math and query surface, stay unreachable and each needs a hand-written
scalar-flattening wrapper.

Additional context

  • Roadmap: Appendix B Q6, and the Phase 5 Road B section which records the
    113-function cost.
  • The STDROT_STRUCT comment in stdrot/stdrot_api.h documents precisely why
    the return direction was left out and what would have to be true to add it —
    worth reading before designing, since it names the failure mode of the
    obvious approach.
  • Appendix B Q7 (generated code in-tree vs built) was resolved the same
    way this one should be: written down as a decision with the rejected
    alternatives and why, before any code depended on it.
  • Related: the STDROT_CSTRING return gap and STDROT_HANDLE are the same
    question in miniature — both are rejected today for want of an ownership
    model, and both would likely fall out of whatever answer Q6 gets.

Not a good first issue. This is an architectural decision with
language-wide consequences, and the code that follows it touches the ABI, the
semantic analyzer, the return marshaller and the generator. It wants a
maintainer's call on direction first; the implementation after that is
substantial but tractable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestffiForeign-function interface / native ABIhelp wantedExtra attention is neededphase-5Phase 5 — Bindings and the first cursed gameroadmapItem from docs/ROADMAP.md

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions