Verse Wiki — the Verse handbook for Blueprint authors
Chapter 4 · Lesson 12

if and Failure Contexts: Can This Succeed?

Blueprint’s Branch takes a Boolean and asks “true or false?”. Verse’s if asks “can this succeed?”. This lesson is the ace of the whole handbook: meet failable expressions, understand speculative execution and rollback, and master single-line if, block if, and “comma means and”. Climb this mountain, and the square brackets and options waiting beyond are all downhill scenery.

1. if Doesn’t Ask “True or False” — It Asks “Will This Work?”

Back to the tabletop. You tell the Game Master: “I push the boulder aside.” The GM doesn’t answer “true” or “false” — they have you roll a check: meet the difficulty and the check succeeds, the story takes branch A; fall short and it fails, the story takes branch B. Nobody flips the table, nobody throws an error, the adventure carries on.

Verse’s if is that Game Master. The official docs put it bluntly: an if condition doesn’t expect a Boolean — it expects an expression with the decides effect. In plain words: what goes between the parentheses isn’t a boolean value but a “failable expression” — something that might work, or might not. If it works, you take then; if it doesn’t, you take else.

In Blueprint terms: you already know the Branch node — it takes a Boolean, True goes down one wire, False down the other. Verse’s if looks like Branch, but it isn’t asking “is this Boolean true?” — it’s asking “can the thing in the parentheses go through?”: if it goes through, take then (the Branch node’s True pin); if it doesn’t, take else (the False pin). That’s the only difference, and it’s the foundation of this entire lesson.

This clears something up along the way: in Verse, “failure” isn’t an error and won’t crash your Blueprint — it’s just the other exit for the execution wire, like a Branch taking its False pin: the white wire quietly turns into else and the adventure carries on. The dice machine below runs two checks back to back — one succeeds, one fails. Hit “Run next step” and watch each branch go its own way.

dice_check_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

dice_check_device := class(creative_device):

    # The Strength stat on your character sheet
    var Strength:int = 14

    OnBegin<override>()<suspends>:void =
        # Check one: push the boulder, difficulty 10
        if (Strength >= 10):
            Print("Check passed, the boulder rolls aside!")
        else:
            Print("Check failed, the boulder doesn't budge.")
        # Check two: ram the castle gate, difficulty 18
        if (Strength >= 18):
            Print("Check passed, the gate crashes open!")
        else:
            Print("Check failed, you back off rubbing your shoulder.")
Output log

Click “Run next step” to watch the code execute line by line.

Look closely at the second check: 14 < 18, the check doesn’t go through, yet there isn’t a single “Error” in the log. In Verse’s worldview, “didn’t work” is every bit as respectable as “worked” — that’s unlike most languages you’ve used, and it’s the foundation everything in this lesson is built on.

2. Failable Expressions: What Exactly “Might Not Work”

If if takes “failable expressions”, then which expressions are failable? This table covers nine-tenths of everyday code:

Expression Succeeds when Fails when
Comparisons: A = B, A <> B, A < B, A <= B, A > B, A >= B The relation holds The relation doesn’t hold
Index access: Items[0], Scores["A"] The index / key exists Out of bounds, or the key is missing
Square-bracket call: FindItem[3] (a function with <decides>) The function body succeeds all the way through Any step inside the body fails
Query operator: Flag? (logic) The value is true The value is false
Query operator: MaybeItem? (option) It holds a value It’s empty
Type cast: my_subtype[Obj] Obj really is that type It isn’t
int division: X / Y Y is nonzero Division by zero

Run down this table with your Blueprint instincts: “comparisons” are your greater / less / equals checks; “index access” pulls one element out of an array by index or key name (counting as a failure if there’s nothing to pull); a “square-bracket call” invokes a “Blueprint function that might fail”; the query operator ? “translates” a logic or an option into can-this-go-through; and the type cast my_subtype[Obj] is the one you know best — it’s Blueprint’s Cast node: Cast succeeds down one wire, Cast fails down that red Cast Failed pin, exactly the success / failure split you see here.

The flip side: a “failable expression” can’t go just anywhere either — it’s only allowed to live in a handful of “failure contexts”: the parentheses of an if condition, the value and filter clauses of a for / ForEach loop, inside a not, on the left side of an or, inside a failable Blueprint function (the kind with <decides>), and the initializer block of option{...}. Drop a bare Items[0] anywhere outside those spots, hit Compile, and it lights up red — stopped at the door.

This “forced pairing” is one of Verse’s smartest safety designs. Array out-of-bounds is a regular cause of runtime crashes in other languages; in Verse, that crash can’t even be written — you must put the index access inside a failure context, which means you must answer “what if it fails?” on the spot. The GM won’t let you declare “I pick the lock” and refuse to accept the roll.

3. The Many Faces of if: Single-Line, Block Form, Comma-Means-and

if itself wears several faces, all built on the same works / doesn’t-work semantics. The three you’ll use most:

if_shapes.verse
# Face one: single-line if, one expression each for then / else
Bigger(A:int, B:int):int = if (A > B) then A else B

# Face two: block form, colon + indentation, chains with else if
# if is itself an expression: whichever branch runs, the function returns that branch's value
RateScore(Score:int):string =
    if (Score >= 90):
        "S"
    else if (Score >= 60):
        "A"
    else:
        "C"

Read the snippet above: Bigger is a Blueprint function whose body uses if to “compute” a value and hand it out — just like a Select node (pick one of two values based on a condition) wired straight into the function’s Return pin. When you use if to “produce a value”, the else must be wired up too (both the success and failure sides need to supply a value), and the then / else sides must supply the same kind of value; if you only wire up the success half and skip else, the if produces no value at all and can only sit there as a “do something” execution node. RateScore below chains several checks head to tail: first see if the score clears 90, if not see if it clears 60 — like a relay of Branch nodes filtering tier by tier, handing out whichever letter grade the flow lands on.

The third face is this lesson’s signature move: comma means and. Stack several failable expressions in the condition, separated by commas, and Verse evaluates them left to right — all must succeed to enter then; the moment any one fails, the whole condition fails:

door_check.verse
# Comma means and: key and ticket, miss either and you stay outside
if (HasKey?, Coins >= 10):
    Print("The great door swings slowly open.")

# Exactly equivalent to the version above
if (HasKey? and Coins >= 10):
    Print("The great door swings slowly open.")

Better still, the condition can “deal cards mid-hand”: use := to define a name inline, and earlier bindings are directly usable by later conditions and by the then branch. That merges “getting the value” and “testing it” into a single check:

loot_check_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

loot_check_device := class(creative_device):

    Items:[]int = array{7, 2, 9}

    OnBegin<override>()<suspends>:void =
        # Two-part check: grab the first element (may fail), then compare (may fail)
        if (First := Items[0], First > 5):
            Print("First piece of loot is worth {First}, check passed")
        else:
            # Note: First is not visible here - the binding belongs to later conditions and the then branch only
            Print("Either the backpack is empty, or the first item isn't valuable enough")

Read this one through: First := Items[0] does two things — it grabs element 0 from the array Items and stashes it in a temporary variable First on the way; if the array is empty and there’s nothing to grab, that step counts as a failure. Once grabbed, the following First > 5 runs a comparison check on it. Only when both steps go through do you enter then — where the freshly stored First is right there to use. “Getting the value” and “testing it” become one single check, sparing you the two-step ritual of fetching first and null-checking separately.

One rule to burn in: the temporary variable stored by := is visible only to the checks after it and the then wire; the else wire can’t see it — reaching else means the chain snapped at some link, and First may well never have been given a value. When there are too many checks to fit one line, there’s also a vertical way to write it: if: with each check listed on its own line, then: for the body, else: for the failure wire — the most common opening in UEFN device code, fully dissected in the extra page “Chained Failable Bindings”.

Hands-on time. The gate machine below has had two keywords carved out: one is the postfix operator that “translates” a logic variable into success / failure, the other is the failure branch’s keyword. Fill them back in and hit “Check answers”:

gate_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

gate_device := class(creative_device):

    var HasKey:logic = true
    var Coins:int = 30

    OnBegin<override>()<suspends>:void =
        # A logic can't be a condition on its own - it needs a postfix operator
        if (HasKey____, Coins >= 10):
            Print("Both checks passed, the great door swings open.")
        ____:
            Print("Conditions not met, the door doesn't budge.")

4. Speculative Execution and Rollback: A Timeline You Can Undo

Now for the ace of aces: “speculative execution”. Whatever happens inside a failure context, Verse first dry-runs on a “shadow graph” — including the changes set nodes make to variables — and only if the whole line goes through does it commit those changes to your real Blueprint; the moment the overall condition fails, the shadow graph is scrapped and every change along the way is wiped clean in one stroke, as if those nodes had never been wired up. The official wording: if an expression fails, its effects are rolled back, as if it never happened.

rollback_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

rollback_device := class(creative_device):

    var X:int = 5

    OnBegin<override>()<suspends>:void =
        # Speculative execution: boldly bump X by 1 first, then see if it clears 10
        if (set X += 1, X > 10):
            Print("X made it past 10")
        else:
            Print("Check failed, X is still {X}")

Trace this code: set X += 1 pushes X to 6, then X > 10 fails — so the whole condition is declared failed, that increment is undone on the spot, and the else branch prints Check failed, X is still 5. The timeline is wound back to before the dice roll, as if nothing ever happened. This “undo potion” is a guarantee from the Verse language itself, not something conjured up by a plugin or a function library.

Rollback is powered by the <transacts> effect, so the language lays down a law: any function marked <decides> (may fail) must also carry <transacts> (can roll back) — if it can fail, it must be able to take it back. From this follows a discipline: inside a failure context (that “shadow graph”), you can’t call nodes that “can’t take it back” — Print / Print String, Delay with its little clock icon or any <suspends> function, most built-in device actions: all off-limits. Wire one in anyway and Compile lights up red with This invocation calls a function that has effects that are not allowed by its context. Actions that genuinely change the game world belong on the official then / else execution wires — those don’t count as failure contexts.

score_gate_device.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }

score_gate_device := class(creative_device):

    # <decides> must come paired with <transacts>: if it can fail, it must be able to roll back
    PassingScore(Score:int)<transacts><decides>:int =
        Score >= 60
        Score

    OnBegin<override>()<suspends>:void =
        # Failable functions are called with square brackets, and the call must live in a failure context
        if (S := PassingScore[75]):
            Print("Check passed, got score {S}")
        else:
            Print("Below the passing line")

Look inside the Blueprint function PassingScore: the first line, Score >= 60, is itself a check — fail it and the whole function counts as failed; pass it and execution continues to the last line, wiring Score straight to the function’s Return pin (that’s “implicit return”: the last node’s output simply becomes the return value, no dedicated Return node needed to announce it). Calling it uses square brackets, PassingScore[75] — that bracket pair is a big bright signpost: “this function might fail, remember to catch it with else”. As for how rollback works down in the engine, and why these failable functions won’t even let you write an early return — the extra pages dig deep.

5. Common Pitfalls: The Compiler’s Favorite Things to Reject

Failure contexts are a high-yield field for beginner error messages. Work through this incident comparison table and find your own entry:

What you wrote The compiler’s reaction The right move
if (X == 10) == isn’t a Verse operator Equality is a single =: if (X = 10)
if (IsOpen) A logic can’t be a condition by itself if (IsOpen?) — the ? turns it into a failable expression
Calling Print(...) inside the condition Effect not allowed: effects that are not allowed by its context Put side effects in the then / else branches
Writing <decides> without <transacts> Compile error: failable must be rollback-able Annotate the two effects as a pair
Calling a failable function with parentheses: PassingScore(75) Bracket shape is bound to effects — instant error Failable functions always take square brackets: PassingScore[75]
Referencing a name bound with := in the condition from inside else No such name in this scope The binding only covers later conditions and the then branch
Writing return inside a <decides> function body Compile error: explicit return is forbidden in failure contexts Collect the result with an option (see the extra page)

Every row of this table is a classic reason Compile lights up red. A few to remember with Blueprint instincts: a logic (your Boolean) won’t turn itself into “can this go through” — it needs a ? after it; actions that change the game world (like Print String) can’t be stuffed into the check’s parentheses — they belong on the then / else execution wires; failable functions must be called with square brackets [] — parentheses () are an instant error; and that temporary name stored with := is “nowhere to be found” once you’re in else — that wire never went through, so the name never got a value.

One last trap, a psychological one: many people’s first reaction is “if it fails, will it crash? Do I need a safety net?” — it won’t, and you don’t. Failure in Verse is a designed, normal exit that silently takes else. What actually deserves care is forgetting that failure rolls back: no modification made inside the condition survives a failure; modifications inside not are stricter still — rolled back whether it succeeds or fails. If you want changes to stick, do the deed inside the branches.

Blueprint Cross-Reference

Every concept in this lesson has a counterpart in Blueprint — you have wired Branch a few hundred times already. The differences column is the point: it explains why the same white wire grew an extra exit in Verse.

What you do in Blueprint How you write it in Verse Difference
A Branch node with a Boolean on its condition pin if (Strength >= 10): Branch asks “is this Boolean true?”; Verse’s if asks “can the thing in the parentheses go through?” — it doesn’t take a boolean at all, it takes a failable expression
Branch’s False execution pin else: Same slot, same job — but what sends you there changes from “the boolean was false” to “that check didn’t pass”
An Is Valid node (or the Is Valid pin on a Validated Get) Put the fetch itself in a failure context: if (First := Items[0]): In Blueprint the null check is optional — skip it and you’ve left a landmine; in Verse an index access can only live in a failure context, so code that doesn’t handle “nothing there” won’t compile
Touching a null reference → an Accessed None warning at runtime That class of runtime accident doesn’t exist Blueprint defers the problem until the game runs; Verse forces you to deal with it the moment you hit Compile — the cost moves from “a player sees a bug” to “you write one more else”
The red Cast Failed pin on a Cast To … node The else of if (P := player[Agent]): One unified spelling: casting, grabbing an array element, comparing numbers, unwrapping an option — all of them are the same if / else in Verse, instead of a differently shaped node each time
Two Branch nodes in series, or an AND boolean node feeding one if (HasKey?, Coins >= 10): Comma means and, evaluated left to right — and a name bound with := earlier in the list is usable by the later checks and the then branch, which Blueprint has no equivalent for
Set nodes wired in front of a Branch (once set, it stays set) set X += 1 inside the condition is rolled back when the condition fails No Blueprint counterpart: Verse’s failure contexts come with speculative execution and rollback, so a failed check wipes every change made along the way

All of these differences come from one place. Blueprint’s Branch computes a Boolean first, then picks a wire: testing and fetching are two separate acts, and the gap between them is exactly where Accessed None breeds. Verse merges “fetch the value” and “test it” into a single check — nothing to fetch means the check didn’t pass. Once merged, failure needs somewhere to go, so else becomes a fixed exit; and since a check can now fail halfway through, whatever it already changed has to be recoverable, so rollback follows. Pull the chain and the whole design serves one goal: move errors that used to blow up at runtime forward into compile time.

The price is real too: a casual “get element 0” that you toss into a Blueprint graph now needs an else beside it. That isn’t Verse being difficult — it’s Verse turning the care you maintained by habit, by experience, and by scattering Is Valid nodes, into a hard rule the compiler keeps for you.

Level Challenge

Three checks; answer right to earn a ★. Wrong answers cost nothing, and you can keep re-rolling — after all, you just learned: failure is merely the other branch.

You have var IsOpen:logic = true and want to use it as an if condition. Which version compiles?

var X:int = 5. After if (set X += 1, X > 10) takes the else branch, what is X?

About the comma in if (Cond1, Cond2): — which statement is true?

Further Reading

Level Up · EXTRA

Why return Is Banned in decides Functions

A real case from the official forums: code copied straight from the docs still failed to compile, and an Epic employee personally explained the feud between the confiscated return and lenient evaluation.

Enter the extra →

Level Up · EXTRA

Verse’s Theoretical Roots: The Verse Calculus

A language calculus co-designed by one of Haskell’s fathers: how failure and choice became first-class citizens, and where “if tests success” really comes from.

Enter the extra →

Technique · EXTRA

Chained Failable Bindings: The Standard Opening for Device Code

agent → player → fort_character — how one string of square brackets spares you the null-check pyramid you’d build in other languages.

Enter the extra →