Each issue below is a small Ring program, run on official Ring 1.27 and on Ring++ (16 October 2026). You read the
program first, then what each runtime printed. Nothing here is a promise: it is what the two printed.
Programs that die
An object taken from a list ends the program, with no message
Ring 1.27 Adding two objects through their operator method works -- unless the right one comes from a list: then the program stops, exit code 1, no error printed, and no try can catch it.
Ring++ The operator is called, and the program goes on.
# Two prices: one in a variable, one inside a list
oPrice = new Money { nAmount = 10 }
aPrices = [ new Money { nAmount = 5 } ]
# Add them: the class tells Ring how to add two Money objects
oTotal = oPrice + aPrices[1]
? oTotal.nAmount
#--> Ring 1.27: nothing at all -- the program ends here, exit code 1,# with no error message, and no try/catch can stop it#--> Ring++: 15
? "done"
class Money
nAmount = 0
func operator cOp, oOther
oSum = new Money
oSum.nAmount = nAmount + oOther.nAmount
return oSum
Ring 1.27 printed
(nothing)
(the program stops, exit code 1)
Ring++ printed
15
done
Empty catch blocks fill the stack until the program dies
Ring 1.27 Each empty catch leaves one slot on the VM's stack. A loop that expects failures dies with Stack Overflow.
Ring++ Nothing is left behind; the loop runs to the end.
# Try something that fails, 3,000 times, and ignore the failure
for nTry = 1 to 3000
try
raise("a failure we expect")
catch
# nothing to do: the failure is expected
done
next
? "3,000 failures handled"
#--> Ring 1.27: Error (R4) : Stack Overflow -- every empty catch# leaves one slot behind, until there is no room left#--> Ring++: 3,000 failures handled
Ring 1.27 printed
Line 2 Error (R4) : Stack Overflow
(the program stops, exit code 1)
Ring 1.27 Asking a hash list for a key it does not have inserts that key. A read changes the data it reads.
Ring++ A read never writes.
# A hash list with two entries
aHash = [ :a = 1, :b = 2 ]
# Read an entry that does not exist
nValue = aHash[:c]
# How many entries are there now?
? len(aHash)
#--> Ring 1.27: 3 -- reading :c silently ADDED it to the list#--> Ring++: 2 -- a read never writes
Ring 1.27 A method that assigns a name the program also uses writes the program's variable, silently. Softanza spells its own variables _name_ at 175,797 places to avoid it.
Ring++ A method's variables are its own. A global is written $name.
# A variable of the program
cName = "Mansour"
# An object whose method uses a variable with the same name
oGreeter = new Greeter
oGreeter.Greet()
# Is the program's variable unchanged?
? cName
#--> Ring 1.27: a visitor -- the method silently overwrote it#--> Ring++: Mansour -- a method's variables are its own
class Greeter
def Greet()
cName = "a visitor"
Ring 1.27 printed
a visitor
Ring++ printed
Mansour
switch ignores the second value of an on
Solved
Ring 1.27on 6, 7 is accepted without a word, but only 6 is ever compared.
Ring++on 6, 7 matches either value.
nDay = 7
switch nDay
on 6, 7
? "weekend"
other
? "weekday"
off
#--> Ring 1.27: weekday -- `on 6, 7` is accepted,# but only its first value is ever compared#--> Ring++: weekend
Ring 1.27 printed
weekday
Ring++ printed
weekend
A range of two strings answers a string
Solved
Ring 1.27"1":"12" is neither a list nor an error: it is a string, and the program goes on with it.
Ring++ An operator that cannot do what it is asked says so.
# A range of numbers is a list
aNumbers = 1:3
? type(aNumbers)
#--> LIST, on both# A range of two strings that are not single letters
xRange = "1":"12"
? type(xRange)
#--> Ring 1.27: STRING -- no list, and no error#--> Ring++: Bad parameter type! -- the operator says it cannot do this
Ring 1.27 printed
LIST
STRING
Ring++ printed
LIST
Line 7 Bad parameter type!
(the program stops, exit code 1)
The language changed under you
TRUE can be reassigned
Refused
Ring 1.27 After TRUE = 0, every if TRUE in the program takes the other branch.
Ring++ Assigning a constant of the language is refused: the line is not compiled, and the program stops there and says why.
# TRUE is a constant of the language... or is it?
TRUE = 0
if TRUE
? "TRUE is true"
else
? "TRUE is false now"
ok
#--> Ring 1.27: TRUE is false now#--> Ring++: refuses the first line, before anything runs
Ring 1.27 printed
TRUE is false now
Ring++ printed
Line 2 is refused: TRUE = 0
(the line is not compiled; the program stops there)
Ring 1.27ChangeRingKeyword renames a keyword from that line on, silently, for whoever reads the file next.
Ring++ It is refused, by name: a reader of the file sees the language it was written in.
# Rename the keyword `see` to `show`, from this line on
ChangeRingKeyword see show
show "the keyword see is now spelt show"
#--> Ring 1.27: accepts it, silently, for the rest of the file#--> Ring++: refuses it, by name
Ring 1.27 printed
the keyword see is now spelt show
Ring++ printed
Line 2 is refused: ChangeRingKeyword see show
(the line is not compiled; the program stops there)
A loop variable keeps a value the loop never gave it
Ring 1.27 After for nIndex = 1 to 5, nIndex is 6. Four kinds of loop leave four kinds of value behind.
Ring++ The variable belongs to its loop, and reading it afterwards is refused. Run over Softanza, the rule found two real bugs.
func Main
for nIndex = 1 to 5
# the loop's work
next
# What is nIndex once the loop is over?
nAfter = nIndex
? nAfter
#--> Ring 1.27: 6 -- a value the loop never took #--> Ring++: refuses the read -- the variable belongs to its loop
Ring 1.27 printed
6
Ring++ printed
Line 7 is refused: loop variable read after its loop: nIndex
(the line is not compiled; the program stops there)
Found only when the line runs
A missing function is found only when its line runs
Ring 1.27 The program runs up to the call, then fails -- in production, on the day that line is reached.
Ring++rnxc check reports it, with its line, before anything runs.
? "Starting the payroll"
SendPayslips()
#--> Ring 1.27: prints the first line, then fails on the second#--> Ring++: `rnxc check` reports it before anything runs
Ring 1.27 printed
Starting the payroll
Line 2 Error (R3) : Calling Function without definition: sendpayslips
(the program stops, exit code 1)
Ring++ printed
rnxc check p.ring, before running it:
line 2: SendPayslips() is not defined anywhere this file can reach — Error (R3) the moment this line runs
Speed
Three choices in Ring's design cost it the most: strings copied at every call, lists that are
linked, and methods found by name at every call. Ring++ replaces all three. The same programs, on the same machine, minima
of three runs, every answer checked equal:
the program
Ring 1.27
Ring++
why
A 1 MB string, passed to a function 2,000 times
1,348 ms
below 1 ms
Ring copies a string at every call; Ring++ passes it by reference.
Binary search: 150,000 lookups in 200,000 numbers
188,741 ms
93 ms
A Ring list is linked, so reaching item n walks to it; a Ring++ list is an array.
Merge sort of 200,000 numbers
5,222 ms
242 ms
Lists, appends and calls.
Matrix multiplication, 220 by 220
1,790 ms
245 ms
Nested lists and arithmetic.
Sieve of Eratosthenes up to 4,000,000
1,335 ms
237 ms
Integer arithmetic over a list.
Recursive Fibonacci of 31
433 ms
100 ms
Function calls.
The first measurements of the VM, and what flatters them, are recorded in NOTES.md.
What Ring++ keeps from Ring, for now
Some of Ring's behaviours are kept on purpose, so that existing programs keep their meaning: a list printed one item per
line, a named argument that arrives as a two-element list, decimals() as a setting for the whole program, a
string measured in bytes. They are not solved, so they are not listed above. They are decisions, explained below, and the
next language, Haro, is where they change.
Every decision, explained
Where Ring++ answers differently from Ring, it is never by accident. Each difference is a decision, taken for one defect
at a time and written down with what it costs. There are four kinds.
Keep Ring's answer, so existing programs keep their meaning, even where Ring's choice is questionable.
decimals(2) still changes every number the program prints from then on. A bare name at the top level is still
a global. A load that names a missing file still stops the program before it starts, exactly as on Ring.
Fix it silently, where no correct program can notice. Reading aHash[:key] no longer inserts the key.
An object taken from a list no longer ends the program when it meets an operator. Leaving a try through
return also leaves its handler, from a function as from a method.
Refuse, with a rule, where Ring's behaviour is a trap. The program is refused before it runs, and
rnxc check names every place the rule applies: a loop variable read after its loop, a numeric
for whose bound changes while it runs.
Drop it, and say so, for what only Ring's own programs use and Softanza never does: renaming keywords, packages,
raw memory pointers. Each is refused by name rather than half-built.
All 31 decisions are on record, each with its reasons and its cost, in
the corrections register.
Every finding, explained
The findings are what Ring's VM was measured to do: 127 of them, each from a program that ran. They fall into four
families.
Costs the source does not show. A string passed to a function is copied, every time. A loop written
for i = 1 to len(s) measures the string again at every turn, which makes it quadratic. How fast a list is read
depends on how it was built and on where it was read last. On identical programs, Ring measured 9 to 16 times behind Lua.
Traps.N and n are the same variable. Every func written after the first
class of a file becomes a method of that class. A string has no escapes: "\n" is two characters.
char(300) is char(44), silently.
Rules that Ring++ had to learn, to run Ring's programs as they are.:Name = value is a two-element
list. Ring calls Main() by itself after the top level. Every operator on an object calls its
operator() method. eval runs in the scope of the function that calls it.
Ring's own failures.memcpy() ending the process on a zero byte; empty catch blocks filling the stack;
an object from a list ending a program without a message; the Linux build crashing as it exits.
All 127, each with the program that shows it, are in the findings.