Control Flow
Control flow is a term used to describe features and constructs that allow you to control the flow of execution of your code. The most common types of control flow constructs are conditionals and loops.
Conditionals
Conditionals allow you to execute code based on the result of a boolean expression — to run code based on whether a certain condition is met.
What are conditions?
A condition is a boolean expression that evaluates to either true
true
or false
false
. It represents whether a
certain truth is true. For example, 5 > 0
5 > 0
is a condition that asks "is 5 greater than 0?". If the answer is
yes, the condition is true
true
. If the answer is no, the condition is false
false
.
The if
statement
The if-statement is the most basic conditional statement. It executes a block of code if a specified condition is
true
true
. The general syntax of an if statement is:
if CONDITION {
// code to execute if the condition is true
}
Here is an example that demonstrates the use of an if statement:
let x = 5;
if x > 0 {
println("x is positive");
}
In this example, the condition is x > 0
x > 0
. If this condition is true, the code inside the block will be executed.
In this case, x
x
is 5
5
, which is greater than 0
0
, so the code inside the block will be executed.
The else
block
You can optionally attach an else
else
-block to an if-statement to make it execute a different block of code if the
condition is false
false
. The else statement does not have a condition to check.
The general syntax of an if statement with an else block is:
if CONDITION {
// code to execute if the condition is true
} else {
// code to execute if the condition is false
}
Here is an example that demonstrates the use of an if statement with an else block:
let x = -5;
if x > 0 {
println("x is positive");
} else {
println("x is negative");
}
This example would print x is negative
because x
x
is -5
-5
, which is not greater than 0
0
.
The else if
block
You can optionally attach an else if
-blocks in an if-statement. An else if
else if
statement to specify additional
conditions to be checked if the previous conditions in the if statement are false
false
.
It allows you to chain multiple conditions together. The general syntax of an else if statement is:
if FIRST_CONDITION {
// code to execute if the first condition is true
} else if SECOND_CONDITION {
// code to execute if the second condition is true and the first condition is false
} else {
// code to execute if all conditions are false
}
Here is the previous example, but with an else if
block:
let x = 20;
if x < 5 {
println("x is less than 5");
} else if x > 5 {
println("x is greater than 5");
} else {
println("x is equal to 5");
}
This example would print x is greater than 5
because x
x
is 20
20
, which is greater than 5
5
.
Logical Operators
Logical operators allow you to combine the logic of multiple conditions. There are three logical operators:
left && right
left && right
: The and operator returnstrue
true
if bothleft
left
andright
right
aretrue
true
.left || right
left || right
: The or operator returnstrue
true
if eitherleft
left
orright
right
istrue
true
.!condition
!condition
: The not operator returnstrue
true
ifcondition
condition
isfalse
false
.
And remember, if a boolean isn't true
true
, it's false
false
.
Here is an example that demonstrates the use of logical operators:
let x = 5;
let y = 10;
// is both x and y greater than 0?
if x > 0 && y > 0 {
println("x and y are positive");
}
This example would print x and y are positive
because both x
x
and y
y
are greater than 0
0
.
De Morgan's Laws
De Morgan's Laws are a set of rules that allow you to simplify compound boolean expressions. They include:
!(left && right)
!(left && right)
is equivalent to!left || !right
!left || !right
!(left || right)
!(left || right)
is equivalent to!left && !right
!left && !right
Applying these rules to complex boolean expressions can make them easier to understand.
Short-Circuit Evaluation
Short-circuit evaluation is a feature of logical operators that allows you to write more efficient code. It allows you to write code that only evaluates the minimum number of conditions necessary to determine the result of a boolean expression.
For example, if you have an &&
&&
expression, the right side of the expression will only be evaluated if the left
side is true
true
. This is because if the left side is false
false
, the entire expression will be false
false
no
matter what the right side is. This means that if the left side is false
false
, the right side will not be evaluated.
Similarly, if you have an ||
||
expression, the right side of the expression will only be evaluated if the left
side is false
false
.
So:
true && println("Hello, world!"); // prints "Hello, world!"
false && println("Hello, world!"); // does not print "Hello, world!"
Booleans vs. Truthy values
After reading the previous example, you might be surprised to see that println("Hello, world!")
println("Hello, world!")
is valid in
a logical operator. Whereas conditional statements like if-statements take booleans only as conditions,
logical operators take a truthy value, which include all values that support an explicit type-cast to bool
bool
.
If-statements as expressions
Remember the "scoping" syntax from the Variables section? They are called blocks and evaluate to the last expression in the block:
let x = { 5 };
assert_eq(x, 5);
let y = { 5; 10; 15; };
assert_eq(y, 15);
In fact, anything that enters a scope like this is an expression, including if-statements:
let x = if 1 + 1 == 2 {
5
} else {
10
};
let y = if 1 + 1 == 2 then 5 else 10; // this ternary expression also works
assert_eq(x, y, 5);
The assert_eq
assert_eq
call above checked if x == y == 5
x == y == 5
. assert_eq
assert_eq
can accept multiple arguments and
checks if all of them are equal to each other.
Type convergence
In Terbium, every expression must have a type. When all branches of an if-expression evaluate to the same type, its types converge to that type. For example:
let x = if true { 5 /* int32 */ } else { 10 /* int32 */ };
assert(x is int32);
However, what if your if-expression has branches that evaluates to different types? In that case, the type of the
if-expression is divergent and is represented by the Divergent
Divergent
type:
let x = if true { 5 /* int32 */ } else { "hello" /* string */ };
assert(x is Divergent<int32, string>);
// Also note:
let x = if true { 5 } else if false { "hello" } else { 10 };
assert(x is Divergent<int32, string, int32>);
assert(x is Divergent<int32, Divergent<string, int32>>)
assert(x is Divergent<int32, string>);
The Divergent
Divergent
type is a special type that represents a type that can be one of multiple types, also known as a
sum type or union type. It is only one of the many union types in Terbium, however we'll cover them, as well as
Divergent
Divergent
, in more detail in the Sum Types section.
Loops
Loops allow you to execute a block of code multiple times. The most basic type of loop can be written using the
loop
loop
keyword, which will execute a block of code indefinitely:
loop {
// code to execute indefinitely
println("looping...");
}
The break
statement
You can use the break
break
statement to exit a loop early. Since the loop will execute indefinitely, it would be
useful to have a way to exit the loop eventually. The break
break
statement will exit the loop immediately when it
is executed:
let mut x = 0;
loop {
println(x);
x += 1;
if x == 5 {
break;
}
}
assert_eq(x, 5);
This example would print:
0
1
2
3
4
...and after such, x
x
increments to 5
5
and the loop breaks.
The continue
statement
You can use the continue
continue
statement to skip the rest of the current iteration of a loop and start the next
iteration:
let mut x = 0;
loop {
x += 1;
if x.is_even() {
continue;
}
println(x);
if x == 5 {
break;
}
}
This example would print:
1
3
5
...because the continue
continue
statement skips printing when x
x
is even.
While-loops
Another type of loop in Terbium is the while-loop, which continuously executes a block of code based on a condition.
Specifically, a while-loop executes a block of code as long as a specified condition is true
true
. The general
syntax of a while-loop is:
while CONDITION {
// code to execute while the condition is true
}
Here is an example that demonstrates the use of a while-loop:
let mut x = 0;
while x < 5 {
println(x);
x += 1;
}
The variable x
x
will increment (via the x += 1
x += 1
) until it reaches 5
5
, when the condition is false
false
.
This example would print:
0
1
2
3
4
Fundamentally, a while-loop is just a more specific form of a loop that checks a condition before each iteration:
while CONDITION { BODY }
// Expands to:
loop {
if CONDITION { BODY }
else { break; }
}