Why Out-of-Bounds Doesn't Throw: Failure Contexts and Transactional Rollback
Lesson 16 said "reaching too far = the line doesn't go through, no crash". This page digs one shovel deeper: how does "doesn't go through" actually work? Why can Verse promise that after a line fails, the world is still untouched — as if nothing was ever moved? The answer hides in two things — how Verse stamps "might not succeed" onto a function's nameplate, and its dry-run-first, commit-after machinery.
1. Exceptions vs. Failure: Two Worldviews
First, how "reaching too far" usually ends elsewhere: either an instant crash (or worse, silently corrupted data), or an error pops out and you have to write a dedicated catch-the-error section to mop it up — except nobody forces you to write it, and if you forget, the game blows up mid-run right in front of your players. The shared flaw: "this can go wrong" is not written on the function's nameplate — it lives in documentation and your own memory.
Verse swaps in a different approach: carve "might go wrong" straight into the function's nameplate. Grabbing a slot carries a <decides> mark meaning "this step might not succeed"; and any action with that mark is only allowed to sit somewhere with a way out — a Branch (if) condition, a ForEach filter condition, option{...}, or the body of another function that also carries the <decides> mark. Forget the way out? One press of Compile lights up red — it never gives you the chance to put it in front of players. Errors elsewhere are a jump-scare while the game is running; Verse's "doesn't go through" is a contract signed at Compile time — that's the first layer of the principle behind Lesson 16's "reaching too far doesn't crash".
2. Layer Two: A Failure Context Is a Speculative Run
The better part comes next. The official "Failure in Verse" documentation says it outright: the string of actions inside a place with a way out is first dry-run on a shadow graph (officially: speculative execution) — Verse "tries" the whole condition from the top, and it only counts if every step goes through; if any step fails along the way, every change already made in that stretch gets undone wholesale — even sets already wired to variables — as if none of it ever happened.
Don't take my word for it — here's a minimal example:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
rollback_device := class(creative_device):
var Numbers:[]int = array{1, 2, 3}
OnBegin<override>()<suspends>:void =
# Comma = and: both expressions share one failure context
if (set Numbers[0] = 99, Numbers[5] > 0):
Print("Never gets here")
else:
Print("Failed as a whole, the set was rolled back")
# Inspect the goods: what is slot 0 now?
if (First := Numbers[0]):
Print("Numbers[0] = {First}")
Read the snippet and walk it through step by step: set Numbers[0] = 99 targets a legal slot 0, so that step succeeds; immediately after, Numbers[5] reaches too far and doesn't go through. Intuition says slot 0 should be 99 by now — yet the final print shows Numbers[0] = 1. Because the two steps sit in the same place with a way out (the comma straps them into one group), the later step failing uproots the earlier step's already-wired set. They stand together, they fall together: the whole stretch either lands in full or is voided in full. Incidentally, the other mark you keep seeing on function nameplates, <transacts>, takes its name from exactly this — "can be undone", the precondition for rollback.
3. Why This Deal Is Worth It
The gift this undo machinery hands you: a half-modified world simply never exists. In many languages, the most grinding part of an error is working out, piece by piece, "which things changed and which didn't"; Verse erases that half-changed middle state at the root — the moment a line fails to go through, the world is exactly as it was before it started, not a hair different.
The price is a shift in thinking: pack "check first, change after" into the same place with a way out whenever possible, and let the language guarantee all-or-nothing. Say "deduct 100 gold from the vault, put it into bag slot 0" — write it as one condition group like if (set Bag[0] = 100, Vault[3] > 0): and no link in the chain can fail while leaving half an operation behind. It also explains why Verse insists on marking even writing into a slot (set Items[0] = X) as might-not-succeed — only by folding everything into this undoable model does rollback stay on the table.
4. Pop Quiz
After the rollback_device example above finishes, what is the value of Numbers[0]?
At which stage does Verse mainly manage the fact that grabbing a slot might reach too far?
Sources & Further Reading
This page is distilled from Epic's official documentation: "Failure in Verse" (dev.epicgames.com ↗) and "Array in Verse" (dev.epicgames.com ↗). The former gives the complete definition of speculative execution and rollback — worth a close read.