Rust - If Expressions
Overview
Estimated time: 30–40 minutes
Master conditional logic in Rust using if expressions. Learn the difference between expressions and statements, multiple conditions with else if, pattern matching with if let, and how to use if expressions for assignment and return values.
Learning Objectives
- Understand if expressions vs if statements in other languages.
- Learn basic if, else if, and else syntax and usage.
- Master using if expressions for variable assignment.
- Understand if let for pattern matching.
- Learn best practices for conditional logic in Rust.
Prerequisites
If Expressions vs Statements
In Rust, if
is an expression, not a statement. This means it returns a value that can be used in assignments and other contexts.
Basic If Expression
fn main() {
let number = 5;
if number > 0 {
println!("The number is positive");
}
let is_even = if number % 2 == 0 {
"even"
} else {
"odd"
};
println!("The number {} is {}", number, is_even);
}
Expected Output:
The number is positive
The number 5 is odd
If Expression Anatomy
fn main() {
let condition = true;
// If expression with explicit return values
let result = if condition {
"condition was true" // No semicolon - this is the return value
} else {
"condition was false" // No semicolon - this is the return value
};
println!("Result: {}", result);
// If expression returning numbers
let number = if condition { 1 } else { 0 };
println!("Number: {}", number);
}
Expected Output:
Result: condition was true
Number: 1
Basic If, Else If, Else
Simple If-Else
fn main() {
let temperature = 25;
if temperature > 30 {
println!("It's hot outside!");
} else {
println!("It's not that hot.");
}
// Using the expression result
let clothing = if temperature > 20 {
"t-shirt"
} else {
"jacket"
};
println!("You should wear a {}", clothing);
}
Expected Output:
It's not that hot.
You should wear a t-shirt
Multiple Conditions with Else If
fn main() {
let score = 85;
let grade = if score >= 90 {
'A'
} else if score >= 80 {
'B'
} else if score >= 70 {
'C'
} else if score >= 60 {
'D'
} else {
'F'
};
println!("Score: {}, Grade: {}", score, grade);
// More complex example
let age = 25;
let has_license = true;
let has_car = false;
if age >= 18 && has_license {
if has_car {
println!("You can drive your own car!");
} else {
println!("You can drive, but you need to rent or borrow a car.");
}
} else if age >= 16 {
println!("You can get a learner's permit.");
} else {
println!("You're too young to drive.");
}
}
Expected Output:
Score: 85, Grade: B
You can drive, but you need to rent or borrow a car.
Logical Operators in Conditions
AND, OR, NOT Operators
fn main() {
let age = 20;
let has_id = true;
let is_member = false;
// AND operator (&&)
if age >= 18 && has_id {
println!("You can enter the venue");
}
// OR operator (||)
if age >= 21 || is_member {
println!("You can get the discount");
} else {
println!("No discount available");
}
// NOT operator (!)
if !is_member {
println!("Consider becoming a member for exclusive benefits");
}
// Complex conditions
let username = "admin";
let password = "secret123";
let is_authenticated = false;
if (username == "admin" && password == "secret123") || is_authenticated {
println!("Access granted");
} else {
println!("Access denied");
}
}
Expected Output:
You can enter the venue
No discount available
Consider becoming a member for exclusive benefits
Access granted
Short-Circuit Evaluation
fn main() {
let x = 5;
let y = 0;
// AND short-circuits: if first is false, second isn't evaluated
if y != 0 && x / y > 2 { // y != 0 is false, so x / y is never evaluated
println!("This won't print");
} else {
println!("Short-circuit prevented division by zero");
}
// OR short-circuits: if first is true, second isn't evaluated
let result = true || expensive_computation();
println!("Result: {}", result);
}
fn expensive_computation() -> bool {
println!("This expensive computation won't run");
false
}
Expected Output:
Short-circuit prevented division by zero
Result: true
If Let Pattern Matching
Basic If Let
if let
allows you to combine pattern matching with conditional logic:
fn main() {
let some_number = Some(42);
let no_number: Option = None;
// Traditional match approach
match some_number {
Some(value) => println!("Got a value: {}", value),
None => println!("Got nothing"),
}
// if let approach (more concise for single pattern)
if let Some(value) = some_number {
println!("if let: Got a value: {}", value);
} else {
println!("if let: Got nothing");
}
// Another example with None
if let Some(value) = no_number {
println!("This won't print: {}", value);
} else {
println!("no_number was None");
}
}
Expected Output:
Got a value: 42
if let: Got a value: 42
no_number was None
If Let with Different Types
fn main() {
let message = Ok("Success!");
let error_message: Result<&str, &str> = Err("Something went wrong");
// if let with Result
if let Ok(msg) = message {
println!("Success message: {}", msg);
} else {
println!("Got an error");
}
if let Err(error) = error_message {
println!("Error occurred: {}", error);
} else {
println!("Operation succeeded");
}
// if let with tuples
let pair = (3, 5);
if let (x, y) = pair {
println!("Pair values: x={}, y={}", x, y);
}
// if let with custom enums
enum Color {
Red,
Green,
Blue,
Rgb(u8, u8, u8),
}
let color = Color::Rgb(255, 0, 128);
if let Color::Rgb(r, g, b) = color {
println!("RGB color: red={}, green={}, blue={}", r, g, b);
} else {
println!("Not an RGB color");
}
}
Expected Output:
Success message: Success!
Error occurred: Something went wrong
Pair values: x=3, y=5
RGB color: red=255, green=0, blue=128
Using If in Different Contexts
If in Variable Assignment
fn main() {
let weather = "sunny";
// Assigning result of if expression
let activity = if weather == "sunny" {
"go to the beach"
} else if weather == "rainy" {
"stay inside and read"
} else {
"go for a walk"
};
println!("Since it's {}, let's {}", weather, activity);
// More complex assignment
let x = 10;
let y = 20;
let max = if x > y { x } else { y };
let relationship = if x == y {
"equal"
} else if x > y {
"x is greater"
} else {
"y is greater"
};
println!("x: {}, y: {}, max: {}, relationship: {}", x, y, max, relationship);
}
Expected Output:
Since it's sunny, let's go to the beach
x: 10, y: 20, max: 20, relationship: y is greater
If in Function Returns
fn main() {
let temperature = 25;
let humidity = 60;
let comfort_level = assess_comfort(temperature, humidity);
println!("Comfort level: {}", comfort_level);
let number = -5;
let abs_value = absolute_value(number);
println!("Absolute value of {}: {}", number, abs_value);
}
fn assess_comfort(temp: i32, humidity: i32) -> &'static str {
if temp >= 20 && temp <= 26 && humidity >= 40 && humidity <= 60 {
"comfortable"
} else if temp > 30 || humidity > 70 {
"too hot/humid"
} else if temp < 15 || humidity < 30 {
"too cold/dry"
} else {
"acceptable"
}
}
fn absolute_value(n: i32) -> i32 {
if n < 0 { -n } else { n }
}
Expected Output:
Comfort level: comfortable
Absolute value of -5: 5
Nested If Expressions
Simple Nesting
fn main() {
let user_type = "premium";
let subscription_active = true;
let payment_overdue = false;
if user_type == "premium" {
if subscription_active {
if !payment_overdue {
println!("Full access granted");
} else {
println!("Payment required for full access");
}
} else {
println!("Please renew your subscription");
}
} else {
println!("Limited access - consider upgrading to premium");
}
// Same logic with combined conditions (often cleaner)
if user_type == "premium" && subscription_active && !payment_overdue {
println!("Alternative: Full access granted");
} else if user_type == "premium" && subscription_active {
println!("Alternative: Payment required for full access");
} else if user_type == "premium" {
println!("Alternative: Please renew your subscription");
} else {
println!("Alternative: Limited access - consider upgrading to premium");
}
}
Expected Output:
Full access granted
Alternative: Full access granted
Complex Nested Logic
fn main() {
let age = 25;
let country = "US";
let has_passport = true;
let criminal_record = false;
let travel_status = if age >= 18 {
if has_passport {
if country == "US" {
if !criminal_record {
"Can travel internationally"
} else {
"May face travel restrictions"
}
} else {
"Check visa requirements"
}
} else {
"Need to get a passport first"
}
} else {
"Need parental consent for passport"
};
println!("Travel status: {}", travel_status);
}
Expected Output:
Travel status: Can travel internationally
Type Consistency in If Expressions
All Branches Must Return Same Type
fn main() {
let condition = true;
// This works - both branches return i32
let number = if condition { 42 } else { 0 };
println!("Number: {}", number);
// This would cause a compile error:
// let mixed = if condition { 42 } else { "hello" }; // Error: type mismatch
// Correct way to handle different types
enum Value {
Number(i32),
Text(String),
}
let mixed_value = if condition {
Value::Number(42)
} else {
Value::Text(String::from("hello"))
};
match mixed_value {
Value::Number(n) => println!("Got number: {}", n),
Value::Text(s) => println!("Got text: {}", s),
}
}
Expected Output:
Number: 42
Got number: 42
Unit Type in If Expressions
fn main() {
let print_debug = true;
// If used for side effects (returns unit type)
if print_debug {
println!("Debug: Starting application");
println!("Debug: Loading configuration");
}
// Explicit unit type return
let result = if print_debug {
println!("This branch returns ()");
() // Explicit unit type
} else {
println!("This branch also returns ()");
() // Explicit unit type
};
println!("Result type is unit: {:?}", result);
}
Expected Output:
Debug: Starting application
Debug: Loading configuration
This branch returns ()
Result type is unit: ()
Practical Examples
Input Validation
fn main() {
let username = "admin";
let password = "secure123";
let age = 25;
let validation_result = validate_user(username, password, age);
println!("{}", validation_result);
}
fn validate_user(username: &str, password: &str, age: u8) -> String {
if username.is_empty() {
"Username cannot be empty".to_string()
} else if username.len() < 3 {
"Username must be at least 3 characters".to_string()
} else if password.len() < 8 {
"Password must be at least 8 characters".to_string()
} else if age < 13 {
"Must be at least 13 years old".to_string()
} else if age < 18 {
"Account created with parental controls".to_string()
} else {
"Account created successfully".to_string()
}
}
Expected Output:
Account created successfully
Configuration Logic
fn main() {
let environment = "production";
let debug_mode = false;
let log_level = "info";
configure_application(environment, debug_mode, log_level);
}
fn configure_application(env: &str, debug: bool, log_level: &str) {
let port = if env == "production" { 80 } else { 3000 };
let database_url = if env == "production" {
"postgresql://prod-db:5432/app"
} else if env == "staging" {
"postgresql://staging-db:5432/app"
} else {
"postgresql://localhost:5432/app_dev"
};
if debug {
println!("🔧 Debug mode enabled");
println!("🔧 Environment: {}", env);
println!("🔧 Port: {}", port);
println!("🔧 Database: {}", database_url);
}
if log_level == "debug" || debug {
println!("📝 Verbose logging enabled");
} else if log_level == "info" {
println!("📝 Standard logging enabled");
} else {
println!("📝 Minimal logging enabled");
}
println!("🚀 Application configured for {} environment on port {}", env, port);
}
Expected Output:
📝 Standard logging enabled
🚀 Application configured for production environment on port 80
Best Practices
Readability Guidelines
- Use if expressions for simple conditional assignments
- Consider using match for complex pattern matching instead of many else if
- Keep conditions simple and readable
- Use descriptive variable names for boolean conditions
fn main() {
let temperature = 25;
let is_raining = false;
let is_weekend = true;
// Good: descriptive boolean variables
let is_warm = temperature > 20;
let is_nice_weather = is_warm && !is_raining;
let can_go_outside = is_nice_weather || is_weekend;
if can_go_outside {
println!("It's a good day to go outside!");
}
// Good: clear conditional logic
let activity = if is_nice_weather && is_weekend {
"perfect day for a picnic"
} else if is_nice_weather {
"good day for a walk after work"
} else if is_weekend {
"indoor activities"
} else {
"focus on work"
};
println!("Today is a {}", activity);
}
Expected Output:
It's a good day to go outside!
Today is a perfect day for a picnic
Performance Considerations
- If expressions compile to efficient branch instructions
- Short-circuit evaluation prevents unnecessary computations
- Avoid deeply nested conditions - consider using match instead
Common Pitfalls
- Forgetting else branch: If expressions used for assignment must have else
- Type mismatches: All branches must return the same type
- Complex nested conditions: Consider using match or separate functions
- Missing parentheses: Use parentheses to clarify complex boolean expressions
Checks for Understanding
- What's the difference between an if expression and an if statement?
- What happens if you try to assign an if expression where branches return different types?
- When should you use
if let
instead ofmatch
? - What operators can you use in if conditions?
- Do all if expressions need an else branch?
Answers
- If expressions return values that can be used in assignments; if statements in other languages don't return values
- Compile error - all branches must return the same type
- Use
if let
when you only care about one pattern; usematch
for multiple patterns &&
(AND),||
(OR),!
(NOT), comparison operators (==
,!=
,<
,>
, etc.)- Only if you're using the if expression for assignment or return values