Skip to content

Rust - Decision Making

Decision-making constructs allow a program to execute different blocks of code based on certain conditions. Rust provides powerful and safe tools for controlling the flow of your program. The primary constructs are if/else expressions and the match statement.

Here’s an overview of Rust’s control flow statements:

StatementDescription
if expressionExecutes code if a condition is true.
if-else expressionExecutes one block of code if the condition is true, and another if it’s false.
if-else if-elseAllows for checking multiple conditions in a sequence.
match statementA powerful control flow operator that compares a value against a series of patterns and executes code based on which pattern matches.

A key feature in Rust is that if is an expression, not just a statement. This means it evaluates to a value, which you can assign to a variable. This allows for very concise and readable code.

fn main() {
let number = 6;
// Traditional if-else statement
if number % 4 == 0 {
println!("Number is divisible by 4");
} else if number % 3 == 0 {
println!("Number is divisible by 3");
} else {
println!("Number is not divisible by 4 or 3");
}
// Using `if` as an expression
let is_even = if number % 2 == 0 {
true // This is the value returned if the condition is true
} else {
false // This is the value returned if the condition is false
};
println!("Is the number even? {}", is_even);
// This is even more concise
let description = if is_even { "even" } else { "odd" };
println!("The number is {}.", description);
}

Important: When using if as an expression, the values returned from each block must be of the same type. The compiler will enforce this to ensure type safety.

The match statement is one of Rust’s most beloved features. It allows you to compare a value against a series of patterns and execute code based on the first matching pattern. Think of it as a switch statement on steroids.

A crucial feature of match is exhaustiveness checking. The compiler ensures that you have handled every possible case, which prevents bugs from forgotten conditions. The _ placeholder is a catch-all pattern that matches any value not already handled.

fn main() {
let country_code = "US";
let country = match country_code {
"US" => "United States",
"CA" => "Canada",
"MX" => "Mexico",
"GB" | "UK" => "United Kingdom", // Match multiple patterns
_ => "Unknown", // Default case
};
println!("The country is: {}", country);
}

match can do much more than just compare simple values. It can destructure enums, tuples, and structs, and even use guards.

fn main() {
let some_number = Some(5);
let no_number: Option<i32> = None;
let x = 10;
match some_number {
Some(50) => println!("Got 50"),
// You can add a condition, called a match guard
Some(n) if n == x => println!("Matched a number equal to x"),
// This pattern binds the inner value to `n`
Some(n) => println!("Matched some other number: {}", n),
// This handles the None case
None => println!("Matched None"),
}
// Matching on ranges (inclusive)
let age = 30;
match age {
0 => println!("Newborn"),
1..=12 => println!("Child"),
13..=19 => println!("Teenager"),
_ => println!("Adult"),
}
}

Sometimes, you have a match expression where you only care about one of the cases and want to ignore all the others. For this situation, Rust provides if let, which is syntactic sugar for a match that runs code for one pattern and does nothing for all others.

fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
// Using `if let` is more concise than a full match here.
if let Some(color) = favorite_color {
println!("Using your favorite color, {}, as the background", color);
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background");
} else {
println!("Using orange as the background");
}
} else {
println!("Using blue as the background");
}
}