Guide
Control Flow

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 truetrue or falsefalse. It represents whether a certain truth is true. For example, 5 > 05 > 0 is a condition that asks "is 5 greater than 0?". If the answer is yes, the condition is truetrue. If the answer is no, the condition is falsefalse.

The if statement

The if-statement is the most basic conditional statement. It executes a block of code if a specified condition is truetrue. 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 > 0x > 0. If this condition is true, the code inside the block will be executed. In this case, xx is 55, which is greater than 00, so the code inside the block will be executed.

The else block

You can optionally attach an elseelse-block to an if-statement to make it execute a different block of code if the condition is falsefalse. 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 xx is -5-5, which is not greater than 00.

The else if block

You can optionally attach an else if-blocks in an if-statement. An else ifelse if statement to specify additional conditions to be checked if the previous conditions in the if statement are falsefalse. 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 xx is 2020, which is greater than 55.

Logical Operators

Logical operators allow you to combine the logic of multiple conditions. There are three logical operators:

  • left && rightleft && right: The and operator returns truetrue if both leftleft and rightright are truetrue.
  • left || rightleft || right: The or operator returns truetrue if either leftleft or rightright is truetrue.
  • !condition!condition: The not operator returns truetrue if conditioncondition is falsefalse.

And remember, if a boolean isn't truetrue, it's falsefalse.

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 xx and yy are greater than 00.

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 truetrue. This is because if the left side is falsefalse, the entire expression will be falsefalse no matter what the right side is. This means that if the left side is falsefalse, 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 falsefalse.

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 boolbool.

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_eqassert_eq call above checked if x == y == 5x == y == 5. assert_eqassert_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 DivergentDivergent 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 DivergentDivergent 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 DivergentDivergent, 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 looploop keyword, which will execute a block of code indefinitely:

loop {
    // code to execute indefinitely
    println("looping...");
}

The break statement

You can use the breakbreak 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 breakbreak 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, xx increments to 55 and the loop breaks.

The continue statement

You can use the continuecontinue 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 continuecontinue statement skips printing when xx 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 truetrue. 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 xx will increment (via the x += 1x += 1) until it reaches 55, when the condition is falsefalse. 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; }
}