Core Concepts: Control Flow and Functions
Every useful program needs to be able to make decisions and repeat actions. This chapter introduces you to Rust’s control flow constructs (if expressions, loop, while, for) and how to write reusable blocks of code using functions.
Control Flow
Control flow determines the order in which statements are executed in a program.
if Expressions
The if expression allows you to execute code conditionally. The condition must always be a bool (boolean) type. Rust does not implicitly convert non-boolean types to booleans.
fn main() {
let number = 7;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
// This would be a compile error:
// if number { ... }
}
You can have multiple else if conditions:
fn main() {
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}
if in a let Statement (Expressions vs. Statements)
In Rust, if is an expression, meaning it can return a value. This allows you to use if in let statements. The types of the values returned by each arm of the if expression must be compatible.
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {}", number); // Output: 5
// This would be a compile error because the types don't match:
// let number_error = if condition { 5 } else { "six" };
}
This is a powerful feature that makes Rust code more concise and idiomatic.
Repetition with Loops
Rust provides three kinds of loops: loop, while, and for.
loop
The loop keyword executes a block of code repeatedly until you explicitly tell it to stop using break. You can also return a value from a loop.
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // Return this value from the loop
}
};
println!("The result is {}", result); // Output: 20
}
Loop Labels
If you have nested loops and want to break or continue from an outer loop, you can use loop labels.
fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {}", count);
let mut remaining = 10;
loop {
println!("remaining = {}", remaining);
if remaining == 9 {
break; // Breaks out of the inner loop
}
if count == 2 {
break 'counting_up; // Breaks out of the 'counting_up' loop
}
remaining -= 1;
}
count += 1;
}
println!("End count = {}", count); // Output: End count = 2
}
while
The while loop executes a block of code as long as a condition is true.
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}
for
The for loop is used to iterate over elements of a collection (like arrays, vectors, or ranges) or anything that implements the IntoIterator trait. This is the most commonly used loop in Rust because it’s concise and safe.
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() { // .iter() provides an iterator over references
println!("The value is: {}", element);
}
// Iterating over a range
for number in (1..4).rev() { // (1..4) is a range from 1 up to (but not including) 4
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
Using for loops with collections is safer than while loops with manual indexing because you don’t have to worry about out-of-bounds access.
Functions
Functions are blocks of code that perform a specific task and can be called by other parts of the program.
Defining Functions
Functions are declared using the fn keyword.
fn main() {
println!("Hello from main!");
another_function(); // Call the function
function_with_param(5);
print_labeled_measurement(5, 'h');
}
fn another_function() {
println!("Hello from another function!");
}
fn function_with_param(x: i32) {
println!("The value passed to function_with_param is: {}", x);
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {}{}", value, unit_label);
}
Parameters
Functions can take parameters, which are special variables that are part of the function’s signature. You must declare the type of each parameter.
Return Values
Functions can return values. The type of the return value must be declared after an arrow ->. In Rust, the return value is the value of the final expression in the function body, or you can use the return keyword to exit early.
Rust is an expression-based language. A statement performs an action but doesn’t return a value. An expression evaluates to a value. Function bodies are a series of statements ending in an optional expression.
fn five() -> i32 {
5 // This is an expression; no semicolon
}
fn plus_one(x: i32) -> i32 {
x + 1 // This is an expression; no semicolon
}
fn main() {
let x = five();
println!("The value of x is: {}", x); // Output: 5
let y = plus_one(x);
println!("The value of y is: {}", y); // Output: 6
// Using `return` for early exit:
let z = plus_ten(7);
println!("The value of z is: {}", z); // Output: 17
}
fn plus_ten(a: i32) -> i32 {
if a > 100 {
return a; // Explicit return
}
a + 10 // Implicit return (expression)
}
Notice the absence of semicolons after 5 and x + 1 when they are the final expressions of a function. Adding a semicolon would turn them into statements, causing a compile error because the function would then return () (unit type) instead of i32.
Exercises / Mini-Challenges
Exercise 4.1: FizzBuzz Function
Write a function called fizz_buzz that takes an integer n as input.
- If
nis divisible by both 3 and 5, print “FizzBuzz”. - If
nis divisible by 3, print “Fizz”. - If
nis divisible by 5, print “Buzz”. - Otherwise, print the number
n.
Then, use a for loop to call fizz_buzz for numbers from 1 to 15.
Instructions:
- Define the
fizz_buzzfunction that takes onei32argument. - Implement the conditional logic inside
fizz_buzz. - In
main, use aforloop to iterate through a range and callfizz_buzzfor each number.
// Solution Hint:
/*
fn fizz_buzz(n: i32) {
if n % 15 == 0 { // Check for both first!
println!("FizzBuzz");
} else if n % 3 == 0 {
println!("Fizz");
} else if n % 5 == 0 {
println!("Buzz");
} else {
println!("{}", n);
}
}
fn main() {
for i in 1..=15 { // Range includes 15
fizz_buzz(i);
}
}
*/
Exercise 4.2: Factorial with while loop
Write a function calculate_factorial that takes a non-negative integer n and returns its factorial. Use a while loop for the calculation.
Instructions:
- Define
calculate_factorial(n: u32) -> u32. - Inside the function, initialize a
resultvariable to 1 and acurrent_numvariable ton. - Use a
whileloop that continues as long ascurrent_numis greater than 0. - Inside the loop, multiply
resultbycurrent_numand then decrementcurrent_num. - Return
result. - In
main, callcalculate_factorialfor a few numbers (e.g., 0, 1, 5, 7) and print the results.
Example Output:
Factorial of 5 is: 120
// Solution Hint:
/*
fn calculate_factorial(n: u32) -> u32 {
if n == 0 {
return 1;
}
let mut result = 1;
let mut current_num = n;
while current_num > 0 {
result *= current_num;
current_num -= 1;
}
result
}
fn main() {
println!("Factorial of 0 is: {}", calculate_factorial(0));
println!("Factorial of 1 is: {}", calculate_factorial(1));
println!("Factorial of 5 is: {}", calculate_factorial(5));
println!("Factorial of 7 is: {}", calculate_factorial(7));
}
*/
Exercise 4.3: Celsius to Fahrenheit Converter
Write two functions:
celsius_to_fahrenheit(celsius: f64) -> f64fahrenheit_to_celsius(fahrenheit: f64) -> f64
Implement the conversion logic and call these functions in main to test them.
Formulas:
- Fahrenheit = (Celsius * 1.8) + 32
- Celsius = (Fahrenheit - 32) / 1.8
Instructions:
- Define both functions with the specified signatures.
- Implement the arithmetic conversions.
- In
main, test with a few values and print the original and converted temperatures.
// Solution Hint:
/*
fn celsius_to_fahrenheit(celsius: f64) -> f64 {
(celsius * 1.8) + 32.0
}
fn fahrenheit_to_celsius(fahrenheit: f64) -> f64 {
(fahrenheit - 32.0) / 1.8
}
fn main() {
let temp_c = 25.0;
let temp_f = celsius_to_fahrenheit(temp_c);
println!("{}°C is {}°F", temp_c, temp_f);
let temp_f2 = 77.0;
let temp_c2 = fahrenheit_to_celsius(temp_f2);
println!("{}°F is {}°C", temp_f2, temp_c2);
}
*/