Failure Contexts: The "Performing If" and the ? Operator
Failure Contexts: The "Performing If" and the ? Operator
Newcomers to Verse hit one wall harder than any other: an if in Verse is not the if you know from other languages. In most languages if asks "is this boolean true?" In Verse, if asks "does this expression succeed?" That single difference explains the square brackets, the ? operator, the strange-looking conditions like if (X := Foo[]), and the rule that some functions "don't return true — they just succeed." Once it clicks, a huge amount of Verse stops being mysterious.
This lesson builds directly on Optionals. There, an empty option had to be checked before use. Here we name the general idea behind that check: failure, and the places it's allowed — failure contexts.
Two different worlds: logic vs. failure
<!-- section-art:two-different-worlds-logic-vs-failure -->

Logic vs Failure
Verse has an ordinary boolean type called logic, with values true and false. That is data — a yes/no value you can store in a variable.
IsNight : logic = true # a stored yes/no value
Separately, Verse has the idea of an expression that can succeed or fail. Failure is not a value — it's an outcome of running an expression. A map lookup Scores[Player] either succeeds (yielding the value) or fails (the key isn't there). A <decides> call either succeeds or fails. The ? on an option either succeeds (present) or fails (empty).
The crucial point that trips everyone: if in Verse runs on failure, not on logic. The condition of an if is an expression that is performed; if it succeeds, the then branch runs; if it fails, the else branch runs. This is why people call it a "performing if" — the condition does work, it isn't just a true/false flag.
# This if has NO boolean. `Scores[Player]` is a map lookup that can fail.
if (PlayerScore := Scores[Player]):
Print("Score is {PlayerScore}") # ran because the lookup SUCCEEDED
else:
Print("No score recorded") # ran because the lookup FAILED
So how does a plain logic work in an if? Verse provides the query operator ?: writing IsNight? succeeds if the logic is true and fails if it's false. That's the bridge from the data world to the failure world.
# `IsNight` is a logic. `IsNight?` turns it into a success/failure test.
if (IsNight?):
TurnOnStreetlights()
This is exactly the if (DebugMode?) / if (ActivatedOnStart?) pattern you see throughout real corpus code — those fields are logic (or ?logic), and ? is what makes them usable as an if condition.
Failure contexts: where failure is allowed
Failure can't happen just anywhere — if it could, your program would be full of silent dead ends. Verse permits failable expressions only inside a failure context: a place that is prepared to handle both outcomes. The everyday failure contexts are:
- The condition of an
if—if (failable): - The filter/binding part of a
for—for (X : Coll, failable): - Inside a
<decides>function body — the whole function is one big failable expression. - The right side of logical combinators inside those (
and,or).
If you write a failable expression outside a failure context, the compiler stops you: "this expression can fail, but failure is not allowed here." That error is not a nuisance — it's Verse forcing you to decide what happens on failure.
if as a failure context — combining conditions
Because the if condition is a performed expression, you can chain several failable steps with commas, and they run left-to-right as a single AND. If any step fails, the whole condition fails and else runs. Crucially, a binding from an earlier step is in scope for later steps — this is what makes Verse conditions so powerful.
# Each comma-separated clause must succeed, in order, for the body to run.
# Note PlayerData (bound in step 1) is used in step 2.
if (PlayerData := GetPlayerData[Player], PlayerData.Health > 0):
Revive(PlayerData)
else:
Print("No data, or already at zero health")
You can also write the multi-line form, which reads better for several clauses:
if:
FortChar := Player.GetFortCharacter[] # may fail
Health := FortChar.GetHealth() # uses the bound FortChar
Health > 50.0 # a comparison that can fail
then:
Print("Healthy")
else:
Print("Hurt, missing, or gone")
for as a failure context — filtering while you iterate
A for loop's head is also a failure context. Any clause after the iteration that fails simply skips that element — it's a filter, not a crash. This is one of Verse's most elegant features, and it's all over real code. Here is a real corpus pattern, lightly trimmed:
# Real shape from the codebase: iterate players, but only those who
# (a) have a character and (b) aren't the triggering character.
for (Player : CurrentPlayers,
Player.GetFortCharacter[] <> TriggeringCharacter,
CharacterToTeleport := Player.GetFortCharacter[]):
TeleportToSafeZone(CharacterToTeleport)
Read it as: for each player, if they have a character, and it isn't the trigger, bind it and run the body. Players who fail any clause are silently skipped. No manual if-inside-the-loop, no null checks — the failure context does it.
<decides>: a function that IS a failure test
The deepest version of "performing if" is the <decides> effect on a function. A <decides> function doesn't return a boolean — the entire function body is a failable expression. It either produces its result value (success) or fails. You call it with [], and you use it inside a failure context exactly like any other failable expression.
The classic beginner mistake comes straight from other languages: writing return true / return false from a <decides> function. You don't. You either produce the value (success) or hit a failing expression (failure). From our project's own conventions:
# WRONG — do not return booleans from a <decides> function.
HasEnoughGold(Player : agent, Cost : int)<decides> : void =
if (Gold := GetGold[Player], Gold >= Cost):
return true # NO
return false # NO
# RIGHT — the whole function is the test. Reach the end = success; fail = failure.
HasEnoughGold(Player : agent, Cost : int)<decides> : void =
Gold := GetGold[Player] # fails here if no gold record
Gold >= Cost # fails here if the comparison is false
# reaching this point with no failure = the function SUCCEEDS
Then you use it in a failure context — never call it and compare to true:
<decides> is contagious in the same way the next lesson's <transacts> is: a function that calls a <decides> function (outside a failure context) must itself be <decides>, because it too can now fail.
logic vs. failure — the rule of thumb
When do you use which?
- Use a
logicwhen you need to store or pass around a yes/no value — a setting, a flag in a struct, a parameter. - Use failure (a failable expression in an
if/for/<decides>) when you need to branch on whether something worked — a lookup, an unwrap, a guarded action. - The
?operator is the bridge:myLogic?converts a storedlogicinto a success/failure test so you can use it as a condition.
The pitfalls — what trips people up
<!-- section-art:the-pitfalls-what-trips-people-up -->

Safe Passage vs
1. Expecting if to take a boolean. if (IsNight) is wrong; IsNight is data, not a test. Write if (IsNight?). The ? is doing essential work, not decoration.
2. return true / return false in <decides>. The function is the test. Produce the value or let an expression fail; never return booleans. This is the most common <decides> error by far.
3. Calling <decides> with () or comparing to true. Use [] and put the call in a failure context: if (HasEnoughGold[P, 100]), not if (HasEnoughGold(P, 100) = true).
4. Failable expression outside a failure context. Score := Scores[Player] on its own line (not in an if/for) is a compile error because the lookup can fail. Move it into a condition: if (Score := Scores[Player]):.
5. Forgetting that earlier bindings flow into later clauses. You don't need nested ifs. if (D := GetData[P], D.Health > 0) is one clean condition; the second clause sees D.
6. Thinking a skipped for element is an error. In a for head, a failing filter clause just skips that element. That's the intended behavior — it's a filter, not a fault.
Recap
- Verse's
ifis a "performing if": it runs on success/failure, not on a boolean. logic(true/false) is data; failure is an outcome of running an expression. The?operator bridges them (myLogic?succeeds ifftrue).- Failure contexts — the head of
if, the head offor, and<decides>bodies — are the only places failable expressions are allowed. - In an
if/forhead, comma-separated clauses run as an AND, and earlier bindings are in scope for later clauses. - A
<decides>function is a failure test: produce the value or fail — neverreturn true/false. Call it with[]inside a failure context.
References
- Verse Language Reference (Epic)
- Failure in Verse (Epic)
- Specifiers and Attributes / effects (Epic)
- Grounded in the Verse Cortex knowledge base:
Verse.digest.verse(GetFortCharacter,<decides>signatures), real corpusfor-filter andif (Field?)patterns, and project.cursor/rules(verse-transacts: Decides Usage, Parentheses vs Square Brackets, If Statement Requirements).
Get the complete code — free
You've read the full walkthrough. The complete, copy-paste-ready Verse solution is free for members — sign in to unlock it.
Free with your BrainDead.TV / BrainDeadGuild Discord account. The walkthrough above is always free.
Check your understanding
Test yourself with an interactive quiz and track your progress + earn XP — free for members.
Turn this into a guided course
Add Verse failure model — failable expressions, if/for as failure contexts, the ? query operator, logic vs. failure, and <decides> functions to your free study plan — we'll suggest related pages and stitch the lot into one compile-checked, self-guided lesson with worked examples and quizzes.
References
Original tutorial generated by Verse Island from the Verse/UEFN knowledge base, with references to the Epic Games sources above. Code is validated against the knowledge base.