None of what Ring++ does is a new idea. It's three deliberate choices already built into Ring, read closely enough to use on purpose — and a promise about how Ring++ keeps working when Ring changes.
Ring is a small, friendly language sitting on top of a large, unforgiving one. Its own C source is not simple — hundreds of thousands of lines, memory managed by hand, every mistake a segfault with no message. Almost none of that reaches a Ring programmer. That gap — a small surface hiding something vast — is Ring's central design decision, made once, deliberately, years before Ring++ existed. Ring++ doesn't add anything new to that shape. It walks through three doors that were already left open in it.
Somewhere in Ring's C source, one macro decides whether a
value crossing into a function is handed over by address or copied whole:
RING_VM_STACK_PUSHCVAR, vm.h:230. An object
crosses by reference. A string is copied, in full, every time.
That split is not an oversight — it's the conservative choice, made correctly. Ring's objects are reference-counted: the runtime always knows how many places hold one, and can safely hand out its address because it knows exactly when the last reference goes away. A plain string carries none of that bookkeeping, so copying it is the one answer that can never surprise anyone, at the cost of a copy nobody asked for once that string gets large.
Ring++'s RppBuffer doesn't fight that rule —
it moves the declaration "this needs to be held, not copied" into
the machinery Ring already trusts for exactly that: an object. No VM
change, no new syntax, one class. The measured
difference is 75×, and where it loses is stated just as
plainly.
Deep in Ring's parser (stmt.c) sits a comment
that just says Support Type Identifier — the exact line where
the parser reads a type in front of a parameter, like
int func Sum(int x, int y), and keeps parsing rather than
rejecting it as a syntax error. Nothing was ever wired to it. It costs
nothing and does nothing, and it has been true for years.
Read as a design choice, that's a language leaving a hook
for a future it doesn't have to build yet — syntax an ecosystem can grow
into, without the language itself needing to change first.
ringpp check is what finally walks through that specific,
dated hook: it reads the same channel Ring's own scanner already validates,
and treats what was silently accepted as meaning something. What it found by doing that: two functions in
Ring's own shipped standard library that had never worked — since fixed
upstream, because of the report.
Run ring yourfile.ring -go on stock Ring today
and it writes yourfile.ringo — bytecode, no C compiler
involved, ever. Run ring yourfile.ringo and it just runs. This
is how Ring itself starts up fast; it has always been true. Ring's own
packaging tool, ring2exe, took the other fork from
there — writing a C file and handing it to a real compiler.
ringpp build takes the fork that was already
open: it keeps the bytecode Ring already knows how to write, and attaches a
small prebuilt copy of Ring itself that knows how to run it. Nothing new was invented — one command, wrapped
around a door that was standing open the whole time.
Same pattern, three times: a capability Ring's own design already has, sitting unused because nothing pointed at it yet. That's the actual curriculum here — not Ring++'s implementation, but the habit of reading a language's internals closely enough to notice what it already lets you do, and building the smallest possible thing that makes doing it ordinary.
Ring++ depends on Ring's internals, and internals change — that's normal for any language under active development. The standing rule here is never fight the VM: no fork, no C patch, no shadow allocator. Instead, Ring++ writes down, in one document, the complete list of what it actually relies on — not implementation details, not struct layouts, not the pool manager. Everything outside that list stays entirely free to change without asking anyone, because Ring++ never touches it.
ringpp.ring loadsThe
contract itself is machine-checked by rpp/probe.ring on
every load — not "does this function exist" but "does it still behave the
way Ring++ assumes." A few rows are HARD: the pointer arithmetic the
fast path is entirely built on. If one of those ever changes, using it
raises immediately, by name, rather than silently corrupting memory. A few
are soft — ringvm_genarray, sub-states — and degrade to
a slower, pure-Ring path when they don't hold, saying so. The rest are
informational.
The point isn't paranoia, it's honesty: if a future Ring changes one of these specific, named behaviours, the probe says exactly which claim broke, in words, the moment the library loads — instead of someone finding out through corrupted data three weeks later.
The long-term intent, drafted but deliberately not sent — findings about Ring go to the Ring Google Group, posted by the maintainer himself, after review, never as a pull request: propose this exact list as a two-way agreement. A small, named surface treated as observable-and-stable — or at least flagged in release notes before it changes — in exchange for Ring++ promising to depend on nothing outside it. No forking, no patching, no reaching past the documented API into C internals, ever.
It's the same shape as Ring's own relationship to C: a small, opinionated surface over something vast, kept honest by checking it rather than assuming it holds.