Code Story

Chapter #4 – Control Flow

if/else, match/switch, loops, break/continue, pattern matching

4.0  Prologue

Control flow determines the order in which statements execute. Branching selects one path from several alternatives; looping repeats a block until a condition changes; early exit escapes from a loop or function before its natural end. Modern languages enrich these primitives with pattern matching, making branching both more expressive and safer.

4.1  if / else

The if statement is universal. In Rust and many functional-influenced languages it is also an expression that returns a value, removing the need for a ternary operator: // Rust (if is an expression) let label = if score >= 90 { "A" } else { "B" }; // C++ (statement only; ternary for inline) std::string label = (score >= 90) ? "A" : "B"; // C# (statement; ternary available) string label = score >= 90 ? "A" : "B"; // Python (inline conditional expression) label = "A" if score >= 90 else "B" All four languages support else if chains (Python spells it elif). When there are many discrete cases, match/switch is cleaner.

4.2  match / switch

A match or switch statement branches on the value of an expression. The key differences across languages are exhaustiveness and fall-through: // Rust (exhaustive, no fall-through, expression) let msg = match status { 200 => "OK", 404 => "Not Found", 500 => "Server Error", _ => "Unknown", }; // C++ (not exhaustive, fall-through unless break) switch (status) { case 200: msg = "OK"; break; case 404: msg = "Not Found"; break; default: msg = "Unknown"; } // C# (no implicit fall-through; goto case allowed) switch (status) { case 200: msg = "OK"; break; default: msg = "Unknown"; break; } // Python (match added in 3.10; structural pattern matching) match status: case 200: msg = "OK" case 404: msg = "Not Found" case _: msg = "Unknown" Rust’s compiler enforces exhaustiveness: every possible value must be covered or a wildcard _ must appear. This catches missing cases at compile time.

4.3  Loops

Every language provides at least while and for loops. // Rust: loop (infinite), while, for-in (iterator-based) loop { if done { break; } } while !done { /* ... */ } for x in 0..10 { /* x = 0,1,...,9 */ } for item in &collection { /* borrow each element */ } // C++: while, do-while, for, range-for while (!done) { /* ... */ } for (int i = 0; i < 10; ++i) { /* ... */ } for (auto& item : collection) { /* ... */ } // C#: while, do-while, for, foreach while (!done) { /* ... */ } for (int i = 0; i < 10; i++) { /* ... */ } foreach (var item in collection) { /* ... */ } // Python: while, for-in (always iterator-based) while not done: pass for i in range(10): pass for item in collection: pass Rust’s loop keyword creates an explicit infinite loop; the compiler knows it never falls through, enabling break with a value (see below). Range-based and iterator-based for loops are idiomatic in all modern languages.

4.4  break and continue

break exits the innermost enclosing loop. continue skips the remainder of the current iteration. Rust adds two notable extensions:
  • break with a value: loop { break expr; } returns expr from the loop, making the loop an expression.
  • labeled break/continue: labels ('outer: loop { ... break 'outer; }) allow targeting an outer loop rather than the innermost one. C# and Java support labeled break; C++ and Python do not.
// Rust: labeled break targeting outer loop 'outer: for i in 0..5 { for j in 0..5 { if i + j == 6 { break 'outer; } } }

4.5  Pattern Matching

Pattern matching extends matching to the structure of a value, not just its identity. It can destructure tuples, structs, and enums, bind sub-values to names, and apply guards. // Rust: destructuring in match match point { (0, 0) => println!("origin"), (x, 0) => println!("on x-axis at {x}"), (0, y) => println!("on y-axis at {y}"), (x, y) => println!("at ({x}, {y})"), } // Rust: matching on enum variants with data match msg { Message::Quit => quit(), Message::Move { x, y } => move_to(x, y), Message::Write(text) => print!("{text}"), } // Python 3.10+ structural pattern matching match command.split(): case ["go", direction]: move(direction) case ["pick", item]: pick_up(item) case _: print("unknown") Guards add a boolean condition to a pattern arm: Some(x) if x > 0 => positive(x). C#’s switch expression (C# 8+) provides similar power through when clauses.

4.6  Epilogue

Pattern matching turns match/switch from a simple integer branch into a structural decomposition tool. Languages that enforce exhaustiveness shift a class of runtime errors to compile time. The next chapter covers functions - the primary unit of code reuse.

4.7  References

Rust match Control Flow - The Book
C++ switch Statement - cppreference
C# Selection Statements - Microsoft
PEP 634 - Structural Pattern Matching