Verse Wiki — the Verse handbook for Blueprint authors
Bonus · EXTRA

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:

rollback_device.verse
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.