Skip to content

Commit 0427a91

Browse files
committed
forward-port: guard statement docs (PR #293, 08379ac)
Forward-ports Bastian Müller's PR #293 — adds a comprehensive 'Guard statement' section to /docs/language/control-flow with 6 subsections: basic boolean guard, comparison with if-statement (nested vs flat), optional binding (guard let / guard var with scope semantics), chaining guards (multiple preconditions), guard in loops (continue / break), and guard with resources (move operator interaction). Inserted between the existing 'Optional binding' and 'Switch' top-level sections, matching upstream placement. Pure markdown content with fenced code blocks; no MDX-specific conversion needed. Brings eval/pr285-cleanup back in sync with origin/main since the prior fetch (gained 4 commits: ae15a1a, c937619, ebe6298, 08379ac = PR #293 + 3 fixups). The fixup commits ('use string templates', 'Cadence type checker not compiler') are already incorporated in the final upstream text used as the source. Refs: pr285-evaluation/findings.md (Phase 7.1 rebase)
1 parent 3f0ada5 commit 0427a91

1 file changed

Lines changed: 164 additions & 0 deletions

File tree

content/docs/language/control-flow.mdx

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,170 @@ if let number = noNumber {
102102
}
103103
```
104104

105+
## Guard statement
106+
107+
The guard statement is an early-exit mechanism. It asserts that a condition must be true (or an optional must be non-nil) to continue execution. If the condition is false, the mandatory `else` block runs — and that block **must** exit the current scope via `return`, `break`, `continue`, or a function that never returns (e.g., `panic`).
108+
109+
The Cadence type checker enforces this: a guard whose `else` block can fall through is a type error.
110+
111+
### Basic boolean guard
112+
113+
```cadence
114+
fun divide(_ a: Int, by b: Int): Int {
115+
guard b != 0 else {
116+
return 0
117+
}
118+
return a / b
119+
}
120+
121+
divide(10, by: 2) // returns 5
122+
divide(10, by: 0) // returns 0
123+
```
124+
125+
### Comparison with if-statement
126+
127+
`guard` reads as "ensure this is true, otherwise bail out." The normal path stays at the outermost indentation level — there is no rightward drift for validation logic:
128+
129+
```cadence
130+
// Using if — validation logic nests deeper
131+
fun processAge(_ age: Int): String {
132+
if age >= 0 {
133+
if age <= 150 {
134+
return "Valid age: \(age)"
135+
} else {
136+
return "Too old"
137+
}
138+
} else {
139+
return "Negative age"
140+
}
141+
}
142+
143+
// Using guard — happy path stays flat
144+
fun processAge(_ age: Int): String {
145+
guard age >= 0 else {
146+
return "Negative age"
147+
}
148+
guard age <= 150 else {
149+
return "Too old"
150+
}
151+
return "Valid age: \(age)"
152+
}
153+
```
154+
155+
### Optional binding with guard
156+
157+
`guard let` and `guard var` unwrap an optional and — crucially — make the bound variable available **in the enclosing scope**, after the guard. This is the key difference from `if let`, where the bound variable only exists inside the `if` block.
158+
159+
```cadence
160+
fun greet(_ name: String?): String {
161+
guard let unwrappedName = name else {
162+
return "Hello, stranger"
163+
}
164+
// `unwrappedName` is available here, already unwrapped to `String`
165+
return "Hello, \(unwrappedName)"
166+
}
167+
168+
greet("Alice") // returns "Hello, Alice"
169+
greet(nil) // returns "Hello, stranger"
170+
```
171+
172+
Contrast with `if let`:
173+
174+
```cadence
175+
fun greet(_ name: String?): String {
176+
if let unwrappedName = name {
177+
// `unwrappedName` only exists inside this block
178+
return "Hello, \(unwrappedName)"
179+
}
180+
return "Hello, stranger"
181+
// `unwrappedName` is NOT available here
182+
}
183+
```
184+
185+
Use `guard var` when the unwrapped value needs to be mutated after the guard:
186+
187+
```cadence
188+
fun normalize(_ value: Int?): Int {
189+
guard var n = value else {
190+
return 0
191+
}
192+
if n < 0 {
193+
n = 0
194+
}
195+
return n
196+
}
197+
```
198+
199+
### Chaining guards
200+
201+
Multiple guards at the top of a function express preconditions clearly, without nesting:
202+
203+
```cadence
204+
struct User {
205+
let name: String
206+
let age: Int
207+
208+
init(name: String, age: Int) {
209+
self.name = name
210+
self.age = age
211+
}
212+
}
213+
214+
fun createUser(name: String?, age: Int?): User? {
215+
guard let validName = name else { return nil }
216+
guard let validAge = age else { return nil }
217+
guard validName.length > 0 else { return nil }
218+
guard validAge >= 0 else { return nil }
219+
220+
// All checks passed; both `validName` and `validAge` are in scope
221+
return User(name: validName, age: validAge)
222+
}
223+
```
224+
225+
### Guard in loops
226+
227+
Inside a loop, `guard` can use `continue` or `break` instead of `return`:
228+
229+
```cadence
230+
fun sumPositives(_ values: [Int?]): Int {
231+
var total = 0
232+
for item in values {
233+
guard let value = item else {
234+
continue // skip nil entries
235+
}
236+
guard value > 0 else {
237+
continue // skip non-positive entries
238+
}
239+
total = total + value
240+
}
241+
return total
242+
}
243+
244+
sumPositives([1, nil, -3, 4, nil, 2]) // returns 7
245+
```
246+
247+
### Guard with resources
248+
249+
`guard let` works with optional resources using the `<-` move operator. The `else` block must consume or destroy the original resource before exiting:
250+
251+
```cadence
252+
resource Vault {
253+
let balance: UFix64
254+
init(balance: UFix64) {
255+
self.balance = balance
256+
}
257+
}
258+
259+
fun withdraw(_ vault: @Vault?): UFix64 {
260+
guard let v <- vault else {
261+
return 0.0
262+
}
263+
let balance = v.balance
264+
destroy v
265+
return balance
266+
}
267+
```
268+
105269
## Switch
106270

107271
Switch-statements compare a value against several possible values of the same type, in order. When an equal value is found, the associated block of code is executed.

0 commit comments

Comments
 (0)