Control Flow
Conditional Statements
Section titled “Conditional Statements”If-Else
Section titled “If-Else”use go:fmt
let temperature = 22
if temperature > 30 { fmt::Println("It's hot!")} else if temperature < 10 { fmt::Println("It's cold!")} else { fmt::Println("Nice weather!")}Conditions must be boolean expressions. There are no implicit truthy/falsy coercions. Comparison operators include ==, !=, <, <=, >, and >=; combine boolean expressions with and, or, and not.
Boolean Operator Grouping
Section titled “Boolean Operator Grouping”Comparisons bind more tightly than and, and and binds more tightly than or. Ard’s prefix not is intentionally broad: it negates the complete expression to its right, including comparisons and and or or expressions.
not value == expected // not (value == expected)not ready and enabled // not (ready and enabled)not ready or enabled // not (ready or enabled)To negate only one operand, group the not expression itself:
(not ready) and enabled(not ready) or enabledThe formatter may remove parentheses that only restate broad not grouping, so not (ready or enabled) can become not ready or enabled without changing its meaning. Parenthesize the not expression, as above, when you need narrow negation.
For Loops
Section titled “For Loops”Iterating Over Collections
Section titled “Iterating Over Collections”use go:fmt
let fruits = ["apple", "banana", "cherry"]for fruit, index in fruits { fmt::Println("{index}: {fruit}")}The index cursor can be omitted in list loops
use go:fmt
let fruits = ["apple", "banana", "cherry"]for fruit in fruits { fmt::Println(fruit)}Iterating Over Maps
Section titled “Iterating Over Maps”use go:fmt
let scores: [Str:Int] = ["Alice": 95, "Bob": 87, "Carol": 92]for name, score in scores { fmt::Println("{name} scored {score.to_str()}")}Numeric Ranges
Section titled “Numeric Ranges”use go:fmt
// Inclusive rangefor i in 1..10 { fmt::Println(i)}To iterate with a step other than 1, use a C-style loop:
use go:fmt
for mut i = 0; i <= 100; i =+ 10 { fmt::Println(i) // Prints 0, 10, 20, ..., 100}C-Style For Loop
Section titled “C-Style For Loop”use go:fmt
for mut i = 0; i <= 5; i =+ 1 { fmt::Println("Count: {i}")}While Loops
Section titled “While Loops”use go:fmt
mut count = 0while count < 10 { fmt::Println("Count is {count}") count =+ 1}Match Expressions
Section titled “Match Expressions”Match expressions are similar to switch expressions in most languages. They come in two forms: value matching (with a subject) and conditional matching (without a subject).
Conditional Matching
Section titled “Conditional Matching”Match expressions can be used without a subject to create clean conditional logic as an alternative to if-else chains:
let score = 85
let grade = match { score >= 90 => "A", score >= 80 => "B", score >= 70 => "C", score >= 60 => "D", _ => "F",}This is equivalent to, but more concise than, the if-else chain:
fn grade(score: Int) Str { if score >= 90 { "A" } else if score >= 80 { "B" } else if score >= 70 { "C" } else if score >= 60 { "D" } else { "F" }}Note that if blocks produce a value only as the final expression of a function body; they cannot be assigned directly with let x = if .... Conditional match is the expression form.
Conditional match expressions evaluate conditions in order and execute the first matching case. A catch-all case (_) is required to ensure the expression always returns a value.
Complex Conditions
Section titled “Complex Conditions”You can use any boolean expressions as conditions:
let age = 30let has_license = truelet has_insurance = true
let status = match { age < 16 => "Too young to drive", not has_license => "Need to get a license", not has_insurance => "Need insurance", age >= 65 => "Senior driver", _ => "Ready to drive",}
let temperature = 75let sunny = truelet weekend = false
let activity = match { temperature > 80 and sunny => "Go to the beach", temperature > 70 and weekend => "Have a picnic", temperature < 50 => "Stay inside and read", _ => "Go for a walk",}Integer Matching
Section titled “Integer Matching”When ranges overlap, the first match wins:
let score = 85
let grade = match score { 0 => "How?", 1..59 => "F", 60..69 => "D", 70..79 => "C", 80..89 => "B", 90..100 => "A", _ => "Invalid score",}Boolean Matching
Section titled “Boolean Matching”let is_valid = true
let response = match is_valid { true => "Proceed", false => "Error: invalid input",}Enum Matching
Section titled “Enum Matching”enum Status { active, inactive, pending,}
let user_status = Status::active
let message = match user_status { Status::active => "Welcome back!", Status::inactive => "Please reactivate account", Status::pending => "Account under review",}Matching on Type Unions
Section titled “Matching on Type Unions”Use match expressions to handle different types in a union:
use go:fmt
type Content = Str | Int | Bool
fn describe(value: Content) Str { match value { Str => "Text: {it}", Int => "Number: {it.to_str()}", Bool => "Flag: {it.to_str()}", }}
let items: [Content] = ["hello", 42, true]for item in items { fmt::Println(describe(item))}Pattern Matching Order
Section titled “Pattern Matching Order”Patterns are evaluated in the order they appear. More specific patterns should come before general ones.
When to Use Match vs If-Else
Section titled “When to Use Match vs If-Else”Use conditional match expressions when:
- You need to return a value based on conditions
- You want cleaner, more functional code
- The logic is pure (no side effects)
Use if statements when:
- You need to perform side effects (like
break,panic, or mutations) - You’re doing control flow within loops
- You’re executing statements rather than returning values
struct User {}
impl User { fn is_admin() Bool { true } fn is_member() Bool { true }}
fn should_skip(item: Int) Bool { false }fn process(item: Int) {}
let user = User{}let items = [1, 2, 3]
// Good: conditional match for valueslet message = match { user.is_admin() => "Admin access granted", user.is_member() => "Member access granted", _ => "Access denied",}
// Good: if statement for control flowfor item in items { // break is a side effect - can't use match here if should_skip(item) { break } process(item)}Loop Control
Section titled “Loop Control”Ard supports the break keyword for early termination of loops.
fn should_skip(item: Int) Bool { false }fn process(item: Int) {}
let items = [1, 2, 3]
for item in items { if should_skip(item) { break } process(item)}Deferred Cleanup
Section titled “Deferred Cleanup”Use defer to schedule cleanup work for the end of the current function, method, closure, or script. Deferred work runs in last-in-first-out order and still runs when try returns early.
fn read_file(path: Str) Str!Str { let file = try open_file(path) defer file.close()
let text = try file.read_all() Result::ok(text)}Both call and block forms are supported:
defer resource.close()
defer { match resource.close() { Result::err(e) => log("close failed: {e}"), Result::ok(_) => (), }}Unlike Go, Ard evaluates the deferred call later by lowering it as a zero-argument closure. If a deferred call captures a mut binding that is reassigned before the function exits, it sees the later value. Bind an explicit snapshot when needed:
let current = resourcedefer current.close()resource = next_resourcetry is not allowed inside deferred work. Handle cleanup results explicitly with match if they matter.