You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/guards.md
+39-27Lines changed: 39 additions & 27 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,8 @@
2
2
3
3
Guards let you choose between multiple transitions for the same trigger based on state, data, or trigger properties. They encode business rules directly in your transitions.
4
4
5
+
> **🎯 First-Match Semantics**: Transitions are evaluated **in order**. The **first matching transition wins** and executes immediately. Subsequent transitions for the same trigger are never checked.
6
+
5
7
## Table of Contents
6
8
7
9
1.[Why Use Guards](#why-use-guards)
@@ -62,50 +64,53 @@ var (newState, newData, commands) = machine.Fire(
62
64
**How it works:**
63
65
- Guard evaluates the predicate `data.CreditScore >= 700`
64
66
- If **true**, the transition executes
65
-
- If **false**, this transition is skipped
67
+
- If **false**, this transition is skipped and the next transition for the same trigger is checked
66
68
67
-
**What happens if the guard fails?** If no other transition handles this trigger, it's **unhandled** and throws an exception.
69
+
**What happens if the guard fails?** If no other transition handles this trigger, it's **unhandled** and throws an exception (unless you've configured `.OnUnhandled()`).
68
70
69
71
---
70
72
71
73
## Multiple Guarded Transitions
72
74
73
-
Handle different cases by defining multiple transitions for the same trigger:
75
+
Handle different cases by defining multiple transitions for the same trigger with **first-match semantics**:
.Guard((data, trigger) =>true) // If we got here, all checks passed
286
+
// Guard 3: Successful withdrawal (no guard = catch-all)
287
+
.On<ATMTrigger.ConfirmAmount>() // No guard - if we got here, all checks passed
280
288
.ModifyData((data, trigger) =>datawith
281
289
{
282
290
Balance=data.Balance-trigger.Amount,
@@ -337,29 +345,33 @@ var (state3, _, commands3) = machine.Fire(
337
345
```
338
346
339
347
**What's happening:**
340
-
1. Three guards check conditions in order: daily limit, insufficient funds, success
341
-
2. Each guard routes to a different state based on the condition
342
-
3. The success guard modifies data and emits commands
343
-
4. All guards use the same trigger but produce different outcomes
348
+
1. ⚠️ **First-match semantics**: Transitions are evaluated in order
349
+
2. First guard checks daily limit, if it passes → go to DailyLimitReached, STOP
350
+
3. Second guard checks insufficient funds, if it passes → go to InsufficientFunds, STOP
351
+
4. Third transition has no guard (catch-all) → always executes if we reach it
352
+
5. The catch-all modifies data, emits commands, and transitions to Dispensing
344
353
345
354
---
346
355
347
356
## Best Practices
348
357
358
+
✅ **⚠️ Remember first-match semantics**
359
+
Only the **first matching transition executes**. Order matters!
360
+
349
361
✅ **Order guards from most specific to most general**
350
-
Put stricter conditions first, catch-all guards last.
362
+
Put stricter conditions first, catch-all (no guard) last.
351
363
352
-
✅ **Use a catch-all guard for completeness**
353
-
`Guard(data => true)`ensures all cases are handled.
364
+
✅ **Omit the guard for catch-all cases**
365
+
Use no guard instead of `Guard(data => true)`for the final "else" case. It's clearer and more idiomatic.
354
366
355
367
✅ **Keep guards pure**
356
368
Don't perform I/O or side effects in guard predicates. Only inspect data.
357
369
358
370
✅ **Consider If/ElseIf/Else for single-state variations**
359
371
Use guards when you need to go to different states. Use If/Else when you stay in the same state.
360
372
361
-
❌ **Avoid overlapping guards without intention**
362
-
If two guards can both be true, only the first will execute.
373
+
❌ **Avoid unguarded transitions before other transitions**
374
+
An unguarded transition matches everything, making subsequent transitions for the same trigger unreachable. The build-time analyzer will detect this error.
0 commit comments