3. Control Flow¶
So far our programs have run straight from top to bottom. Real programs need to decide
and repeat. If you've written if/else and loops in Python or Java, everything here
will feel familiar — the main surprises are small syntactic ones, like the parentheses
around conditions and the colon-and-indent block style.
Making decisions with if¶
An if tests a boolean condition. The condition goes in parentheses, the line ends in a
colon, and the body is indented. You can chain alternatives with elif (one word, like
Python) and finish with an else:
fn main() i32:
let i32 improbability = 42
if (improbability > 100):
println("Reality is optional.")
elif (improbability == 42):
println("That is the answer.")
else:
println("Carry on.")
let bool has_towel = true
if (has_towel):
println("You are a hoopy frood.")
else:
println("Don't forget your towel.")
return Result.Ok(0)
Output:
That is the answer.
You are a hoopy frood.
The condition must be a bool. A bool variable like has_towel can stand on its own —
you don't need to write if (has_towel == true). Comparisons (==, !=, <, <=, >,
>=) and the logical operators and, or, and not all produce booleans you can test.
Conditions are strict booleans
Unlike Python or C, Sushi won't treat 0, an empty string, or any non-boolean as a
truth value. if (count) is an error; write if (count > 0).
Repeating with while¶
A while loop runs its body over and over as long as its condition stays true. Remember
that rebinding a variable uses :=, which is how we make the loop eventually stop:
fn main() i32:
let i32 countdown = 5
println("Hyperspace countdown:")
while (countdown > 0):
println("T-minus {countdown}")
countdown := countdown - 1
println("Jump!")
return Result.Ok(0)
Output:
Hyperspace countdown:
T-minus 5
T-minus 4
T-minus 3
T-minus 2
T-minus 1
Jump!
Iterating with foreach and ranges¶
When you want to walk over a sequence of values, reach for foreach. The cleanest source
of values is a range. Ranges come in two flavours:
start..endis exclusive — it stops just beforeend(so0..5yields0,1,2,3,4).start..=endis inclusive — it includesend(so1..=3yields1,2,3).
If start is greater than end, the range counts down automatically. You can also
foreach over an array by calling .iter() on it:
fn main() i32:
println("Exclusive 0..5:")
foreach(i in 0..5):
println(" {i}")
println("Inclusive 1..=3:")
foreach(i in 1..=3):
println(" {i}")
println("Descending 3..0:")
foreach(i in 3..0):
println(" {i}")
println("Over a list:")
let string[] crew = from(["Arthur", "Ford", "Trillian"])
foreach(name in crew.iter()):
println(" {name}")
return Result.Ok(0)
Output:
Exclusive 0..5:
0
1
2
3
4
Inclusive 1..=3:
1
2
3
Descending 3..0:
3
2
1
Over a list:
Arthur
Ford
Trillian
Ranges compile down to plain counting loops — there's no iterator object allocated behind
the scenes, so they're free. (from([...]) builds an array literal; arrays get their own
chapter later.)
break and continue¶
Inside any loop, break exits the loop immediately, and continue skips to the next
iteration without running the rest of the body. They work the same as in Python, Java, and
C:
fn main() i32:
println("Numbers 1..10, skipping 5, stopping at 8:")
foreach(i in 1..=10):
if (i == 5):
continue
if (i == 8):
break
println(" {i}")
return Result.Ok(0)
Output:
Numbers 1..10, skipping 5, stopping at 8:
1
2
3
4
6
7
5 is missing because continue skipped its println, and the loop halts before 8
because break fired. Using one of these outside a loop is a compile error — the compiler
won't let a stray break slip through.
What you learned¶
if/elif/elsechoose between branches; conditions go in parentheses and must be realboolvalues.while (condition):repeats while the condition holds; rebind with:=to make progress.foreach(x in source):iterates; ranges give youstart..end(exclusive),start..=end(inclusive), and automatic descending order..iter()letsforeachwalk an array.breakleaves a loop early;continuejumps to the next iteration.
We've been calling println and from without thinking about it. Time to write our own
functions. On to Functions.