The Effect System and Transactional Rollback: no_rollback Decoded
"This invocation calls a function that has the no_rollback effect..." — the first cryptic red nearly every Verse newcomer slams into. This page takes it apart atom by atom: what the effect system is, why those "might need to undo everything" places work like a time machine, and the two correct moves for fixing your logic.
1. The Crime Scene
The scene usually looks like this: you casually call a function inside a Branch (if) check — maybe one of the engine's built-in device nodes (the classic forum case is a GetTags() call without transacts), maybe a little helper you threw together yourself — and Compile throws this at you:
This invocation calls a function that has the 'no_rollback' effect,
which is not allowed by its context.
Literally: "this call invokes a function carrying the no_rollback effect, and the place it sits in does not allow that." In plain words: you stuffed a function that "cannot take back what it does" into a place that "may need to repent and undo everything at any moment". To really read that sentence, you first need to meet Verse's effect system.
2. The Failure Context Is a Time Machine
Lesson 22 covered it: everything inside those "might not go through" places (a Branch check, a <decides> function body, and so on) is speculative execution — book it on the shadow graph first, act second; the moment any step declares "no pass", the ledger is torn up and every change is taken back as one, as if nothing ever happened. That is not a metaphor: Verse really builds "test-wire first, land it only if it all goes through, otherwise pretend it never happened" into the language (effectively a full set of auto-rollback transactions). Every write entering such a place is put on record; no pass reverts it, a pass commits it. The spotless "as if nothing happened" cleanup after a decides function dead-ends runs on exactly this machine.
But the time machine has one hard precondition: everything inside the capsule must be able to run backwards. Changing a variable (one Set)? Fine — Set it back. Building an object? Fine — throw it away. That is exactly the territory <transacts> promises — reading, changing, building are all things it can cover, all booked in the ledger. But printing a line to the screen with Print String? The player has already seen it; there is no taking it back. Having the engine play a sound, really open a door? The world has already changed. These "actions the outside can see" cannot run backwards — which is why operations like Print String cannot enter those "might repent" places.
The crux is the default: a function wearing no effect badge at all defaults to no_rollback. Compile cannot know whether it does anything "untakebackable" inside, so it assumes the worst — and bars the door across the board. And so the innocent little function you threw together flags red the moment it steps into a Branch check.
3. Two Correct Moves for Fixing It
Once you know the principle, the fixes follow naturally: either keep it out of the time machine, or prove to Compile that it can run backwards. One demo of each:
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
combo_device := class(creative_device):
var Hits:int = 0
# No effect annotation → defaults to no_rollback (it contains Print, which cannot roll back anyway)
AddHitAndReport():int =
set Hits += 1
Print("Hits: {Hits}")
Hits
# Only reads/writes internal state → can promise transacts
AddHit()<transacts>:int =
set Hits += 1
Hits
OnBegin<override>()<suspends>:void =
# if (AddHitAndReport() > 3): # compile error: no_rollback entered a failure context
Current := AddHitAndReport()
if (Current > 3):
Print("Move one: call it outside, store a constant, judge only the constant")
if (AddHit() > 3):
Print("Move two: a transacts function can enter a failure context directly")
Move one (take it outside): run the function call outside that "might repent" place first, store the result in a constant, and let the check compare only that constant. Reading a constant can of course run backwards, so Compile waves it through. This move is especially handy for the engine's built-in nodes that you cannot rewrite — they lack transacts, so you grab the value first, then judge with the value.
Move two (wear the badge): if the function is one you built yourself, and inside it only fiddles with variables and builds objects, just put <transacts> on it. Mind that this is a promise, not decoration: once it is on, the function body may no longer contain untakebackable actions like Print String, or Compile will stop you right where the function is defined.
One last look from the design angle: why did Epic set the bar this high? Because Verse's "might not go through" machinery (decides, Branch checks, might-not-pass expressions) is the bedrock of the entire language, and the sentence "no pass means nothing happened" only stands when every outside-facing action is under control. The effect system is the mechanism that writes "what will this function do to the world" right next to its name and hands it to Compile for line-by-line audit — every effect badge you see on a function's signature is a contract Compile will enforce.
Why can't Print String appear inside a "might repent" place?
A function with no effect badge at all — what is its default effect?
Compiled from the Epic developer forums and official documentation: Forum: This invocation calls a function that has the no_rollback effect ↗, Forum: no_rollback effect struggle ↗, Official docs: transacts ↗.