tripon[github]

Traps and no-UB

Undefined behaviour is what happens when the specification declines to say what a program does, and lets the optimizer decide instead. Tripon has no such category. Every edge case in the table below has one defined outcome, emitted by both backends, and no build flag turns that off.

the classicin Triponoutcome
integer overflowtraps, exit 101, E9001 — or wraps with -fastdefined
division and modulo by zerotraps, E9003defined
index out of boundsbounds-checked on every access, traps E9002defined
an out-of-range float-to-integer castrange-guarded, traps E9004defined
read of an uninitialized locallocals are zero-initializeddefined

What a trap looks like

A trap prints one line to standard error and exits 101. No unwinding, no stack trace, no core dump — a code, a name and a line:

E9003 — division by zero
ralt run trap.tp
tripon trap E9003 division-by-zero-trap at trap.tp:5
echo $?
101
E9001 — integer overflow
ralt run ovf.tp
tripon trap E9001 integer-overflow-trap at ovf.tp:5
echo $?
101
E9002 — an index past the end of an array
ralt run oob.tp
tripon trap E9002 index-out-of-bounds at oob.tp:6
echo $?
101
E9004 — a float that does not fit the target integer
ralt run f2i.tp
tripon trap E9004 cast-overflow-trap at f2i.tp:5
echo $?
101

Each of those lines is a complete bug report: the code has a page in the diagnostic table, the name says what happened, and the file:line says where. A trap is deterministic — the same input traps on the same line, on gcc and on clang, on both backends.

-fast changes one thing, explicitly

The default build traps on signed overflow, because a wrong number that silently continues is harder to find than a program that stops at the line that produced it. Where you want the wrap instead — a checksum, a hash, a deliberate modular accumulator — you ask for it at the command line:

the same program, wrapped deliberately
ralt build wrap.tp -fast -o wrap
./wrap
-2147483648

-fast flips +, - and * on the integer types to two's-complement wrap. It does not touch bounds checks, it does not touch division by zero, and it does not reintroduce UB: the wrap is a defined result and the rest of the contract still holds.

Zero-initialized, always

A local that has not been assigned yet reads as zero of its type — 0 for integers, 0.0 for floats, false for bool. Not "whatever was on the stack", which is what makes an uninitialized read a security bug in C:

an int and a bool, never assigned
ralt run zero.tp
0
zero

Exit statuses

A trap is not a failure of the command line and not a failed build, so it has a status of its own:

statusmeaning
0clean, or warnings only
1at least one diagnostic with severity error
101the program trapped at runtime
2the command line itself was wrong

A test harness can therefore tell "the program is wrong" (1) from "the program ran and hit a defined edge" (101) without parsing anything. ralt explain prints the same hint the compiler would, for any code:

ralt explain E9003
E9003
severity: error
name: division-by-zero-trap
hint: div and mod by zero are defined traps; guard the divisor

Why an optimizer likes this

Every optimization a compiler performs is a bet that the program's meaning does not change. Where the meaning is undefined, the bet cannot be checked; where every edge case is defined, it can. Bounds elimination, constant folding and the niche packing that makes result<T, E> free all rest on the guarantee above, which is why the contract is enforced in the code generator rather than promised in the documentation.