Skip to content

Control Flow & Patterns

Flame provides a robust set of control flow structures designed for both safety and readability.


Standard conditional branching:

let score = 85
if score >= 90 {
print("Grade: A")
} else if score >= 80 {
print("Grade: B")
} else {
print("Grade: C")
}

In Flame, if is not only a statement—it is also a first-class expression that produces a value! This allows you to directly assign the outcome of conditionals to variables, pass them to functions, return them, or interpolate them without needing mutable temporary variables:

let b = 0
let a = if b == 0 { 10 } else { 20 }
let connected = true
let status = if connected { "connected" } else { "offline" }

Blocks in Flame evaluate each statement sequentially, and the trailing expression yields the value of that branch:

let score = 85
let grade = if score >= 90 {
"A"
} else if score >= 80 {
"B"
} else {
"C"
}
// Blocks can contain local statements before returning the final expression
let config_timeout = if is_production {
let base_seconds = 30
let buffer = 10
base_seconds + buffer
} else {
5
}

Because if is an expression, it can be passed anywhere expressions are valid:

println(if is_admin { "Welcome Admin" } else { "Access Denied" })
let message = $"Status: { if ready { 100 } else { 0 } }%"

The Flame typechecker statically enforces two critical safety guarantees:

  1. Branch Type Compatibility: Every branch must produce compatible types. If branches yield conflicting types (for example, Int in the if branch and String in the else branch), Flame raises a compile error:

    // ❌ Compile Error: incompatible branch types in if expression: expected 'Int', found 'String'
    let x = if ready { 10 } else { "not ready" }
  2. Exhaustive Branches: When if is used as an expression to produce a value, an else branch is strictly required so that every execution path yields a valid typed value:

    // ❌ Compile Error: if expression missing 'else' branch
    let x = if ready { 10 }

Stored Boolean Conditions & One-Line Checks

Section titled “Stored Boolean Conditions & One-Line Checks”

In Flame, conditional expressions are first-class and evaluate directly to Bool (true or false). You can capture one-line boolean checks directly into variables and store them for reuse across branching logic.

This is a clean, recommended idiom for hardware register bitmasking, sensor telemetry, and complex validation:

const FLAG_OVERTEMP: Int = 1 << 0 // 0x01 (Bit 0)
const FLAG_OVERVOLTAGE: Int = 1 << 1 // 0x02 (Bit 1)
let register_value: Int = 0x03 // Both flags active
// One-line checks that evaluate to Bool and store in variables
let is_overtemp = (register_value & FLAG_OVERTEMP) != 0
let is_overvoltage = (register_value & FLAG_OVERVOLTAGE) != 0
// Combine cleanly using keyword operators (and, or, not)
if is_overtemp and is_overvoltage {
print("[CRITICAL] Dual hardware thermal and electrical fault detected!")
} else if is_overtemp {
print("[WARNING] High temperature detected on thermal sensor.")
} else if is_overvoltage {
print("[WARNING] Electrical over-voltage detected.")
}
Section titled “Why Stored Boolean Checks Are Recommended:”
  • Self-Documenting Intent: Naming the condition (such as is_overtemp) clarifies business and hardware logic without needing inline comments.
  • Single Evaluation: The bitwise or arithmetic expression is computed once and can be safely referenced across multiple branches.
  • Direct Reusability: Stored boolean variables can be passed straight to assertions, logged in diagnostics, or returned from helper functions:
@Test
fn main() {
let payload = "Hello, World!"
let checksum = &payload
let expected = "Hello, World!"
let is_valid = payload.len() > 0 and checksum == expected
println(assert(is_valid))
}

Flame features powerful, expressive pattern matching with match:

let status_code = 200
match status_code {
200 => print("OK"),
404 => print("Not Found"),
500 => print("Internal Server Error"),
_ => print("Unknown Status Code") // `_` acts as default fallback
}

You can unpack data embedded inside Enums (like Result and Option) using tuple destructuring (value) and paths in your match arms. If your logic requires multiple statements, you can use { ... } blocks directly as the body of your match arm!

let opt = Option.Some(42)
match opt {
Option.Some(value) => {
let doubled = value * 2
print($"Found value: {doubled}")
},
Option.None => print("No value found!")
}
let result: Result<Int, Error> = divide(10, 2)
match result {
Result.Ok(ans) => print($"Success: {ans}"),
Result.Err(err) => {
print("An error occurred!")
print(err.message)
}
}

Flame supports three distinct looping paradigms:

Executes as long as the condition evaluates to true:

let mut count = 0
while count < 5 {
print($"Count: {count}")
count = count + 1
}

Creates an infinite loop, ideal for servers, game loops, or event dispatchers:

let mut ticks = 0
loop {
ticks = ticks + 1
if ticks > 10 {
break // explicitly break out
}
}

Iterates over dynamic collections, ranges, or vectors:

let fruits = ["Apple", "Banana", "Orange"]
for fruit in fruits {
print($"Fruit: {fruit}")
}

The defer statement schedules a statement or block of code to run at the exact moment the enclosing function or scope exits.

import std.fs
fn process_log() {
let file = fs.read("app.log")
defer print("Finished processing file!") // Runs when process_log() returns
// ... parse log data ...
if file.len() == 0 {
return // defer still runs here!
}
print("Log processed successfully.")
}

  • break: Immediately terminates the innermost loop.
  • continue: Skips the remaining statements in the current iteration and begins the next iteration.
  • return: Exits the current function, optionally returning a value.