Skip to content

Commit 08379ac

Browse files
authored
Merge pull request #293 from onflow/docs/guard-statement
docs: document the guard statement in control-flow
2 parents 4c04cab + ebe6298 commit 08379ac

1 file changed

Lines changed: 164 additions & 0 deletions

File tree

docs/language/control-flow.md

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

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

108272
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)