Nothing about the loop changes. What changes is what you hand it — and that one choice is the whole of pillar one.
In Ring, when you pass a list into a function, Ring hands over its address — instant, no matter how big the list is. When you pass a string, Ring copies the whole thing, every time, before the function's first line even runs. You've never had to think about this, because most strings are short enough that the copy is free. The moment a "string" is really a buffer — a document, a file you're building, a big block of bytes you read once and touch a thousand times — that copy stops being free, and nothing in the code you'd naturally write tells you so.
A helper function that reads one byte out of a 1 MB buffer, called 1,500 times — a parser, a validator, anything with a loop and a helper. The helper looks cheap: it reads one byte. The two versions below are otherwise identical.
func RawWay cData, nCalls, nSize # cData is a plain string. Ring copies the # whole megabyte onto the stack, on every call. nSum = 0 for i = 1 to nCalls nSum += ByteAtRaw(cData, (i * 7919) % (nSize - 1)) next return nSum
func RppWay oBuf, nCalls, nSize # oBuf is an OBJECT. Objects are lists under the # hood, and lists cross by address, not by copy. nSum = 0 for i = 1 to nCalls nSum += ByteAtRpp(oBuf, (i * 7919) % (nSize - 1)) next return nSum
Not asserted, checked: both versions must
produce the exact same answer before the timing is even printed — the
whole file is one of eight that hold both paths side by side. Try it:
ring examples/02-pass-a-large-value/example.ring.
An object crossing a function call is a reference, not a copy — that's not new, and it's not a Ring++ invention. It's how every class instance in Ring already behaves.
A way to hold a block of bytes — the kind of thing you'd normally reach for a string to hold — as an object instead, so it gets that same reference behaviour, plus safe, bounds-checked reads and writes into it.
RppBuffer and RppView are exactly that: a
buffer that crosses calls by reference and writes in place, and a
zero-copy window into part of one. No new syntax, no new runtime — a
class, like any other Ring class.
You don't need any of this to use RppBuffer — the
example above is the whole interface. But if you're curious why Ring behaves
this way at all: the answer is one line deep in Ring's C source,
RING_VM_STACK_PUSHCVAR — the exact spot where a string gets
copied onto Ring's internal stack before a function call. It is not a bug and
it is not an oversight; a copy is the simplest, safest thing to do with a
value whose lifetime you can't otherwise reason about, and Mahmoud Fayed built
Ring's whole object system on the other side of that choice — objects
are reference-counted, precisely so they don't need to be copied. Ring++
doesn't change that design. It just gives you a second way to say "this one, I
promise, wants to be held, not copied" — using the reference behaviour that
was already there for objects, on bytes instead of business logic. The fuller design story is its own page, alongside
the other two doors and the contract that keeps this working as Ring
evolves.
Below roughly 512 bytes, the machinery that makes
RppBuffer fast costs more than the copy it's avoiding — at that
size, plain Ring wins, measured. RppBuffer is for something you
hold and hit repeatedly, not for building one short string once. Three of
Ring++'s eight worked examples reach the same conclusion for their own shape:
plain Ring is the right answer. Saying so is part of the product, not a
footnote to it.