Introduction to Conditional Statements in Rust
Conditional statements are fundamental control flow structures that allow programs to make decisions and execute different code paths based on certain conditions. Rust's approach to conditionals emphasizes safety, expressiveness, and the ability to use conditions as expressions. Understanding conditional statements is crucial for writing flexible and robust Rust programs.
Key Concepts
- Conditions must be boolean: No automatic conversion from other types
- Expressions, not statements:
ifconstructs can return values - Exhaustive checking:
if letandmatchensure all cases handled - Pattern matching integration: Seamless work with enums and destructuring
- Zero-cost abstractions: Conditionals compile to efficient branch instructions
1. Basic If-Else Syntax
Simple If Statement
fn main() { let number = 7; // Basic if statement if number < 5 { println!("Number is less than 5"); } // Note: Condition must be boolean // if number { // Error: expected bool, found integer // println!("This won't compile"); // } // Works with boolean expressions let condition = number > 10; if condition { println!("Number is greater than 10"); } } If-Else Statement
fn main() { let number = 7; if number < 5 { println!("Number is less than 5"); } else { println!("Number is greater than or equal to 5"); } // Real-world example let temperature = 22; if temperature > 30 { println!("It's hot outside! 🌞"); } else { println!("It's pleasant weather! 😊"); } } Else If Ladder
fn main() { let number = 7; 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"); } // Grade classification example let score = 85; if score >= 90 { println!("Grade: A"); } else if score >= 80 { println!("Grade: B"); } else if score >= 70 { println!("Grade: C"); } else if score >= 60 { println!("Grade: D"); } else { println!("Grade: F"); } // Note: Conditions are evaluated in order // First matching block executes, rest are skipped } 2. If as an Expression
Returning Values from If
fn main() { // If expressions can return values let condition = true; let number = if condition { 5 } else { 6 }; println!("Number is: {}", number); // All arms must return the same type let x = if condition { 5 } else { 6 // Both arms return i32 }; // This won't compile: // let y = if condition { // 5 // } else { // "six" // Error: incompatible types // }; // Practical example: conditional assignment let is_even = if number % 2 == 0 { true } else { false }; println!("Is even: {}", is_even); // Or more concisely: let is_even = number % 2 == 0; println!("Is even: {}", is_even); } Complex Expressions in If
fn main() { let x = 10; let y = 20; // Complex condition with logical operators let result = if x > 5 && y < 30 || x == y { "Complex condition true" } else { "Complex condition false" }; println!("{}", result); // Nested if expressions let value = if x > 0 { if x < 10 { "x is between 0 and 10" } else { "x is greater than or equal to 10" } } else { "x is less than or equal to 0" }; println!("{}", value); } Using If in Function Returns
fn absolute_value(x: i32) -> i32 { if x >= 0 { x // Return x directly } else { -x // Return negated x } } fn describe_number(x: i32) -> &'static str { if x == 0 { "zero" } else if x > 0 { "positive" } else { "negative" } } fn main() { println!("Absolute of -5: {}", absolute_value(-5)); println!("Absolute of 5: {}", absolute_value(5)); println!("-3 is {}", describe_number(-3)); println!("0 is {}", describe_number(0)); println!("7 is {}", describe_number(7)); } 3. If in Variable Declarations
Conditional Initialization
fn main() { // Initialize based on condition let x = if some_condition() { 10 } else { 20 }; // More complex initialization let data = if has_permission() { load_secure_data() } else { load_public_data() }; // Conditional struct initialization struct Config { timeout: u32, retries: u32, } let is_production = true; let config = if is_production { Config { timeout: 30, retries: 5, } } else { Config { timeout: 5, retries: 1, } }; println!("Config: timeout={}, retries={}", config.timeout, config.retries); } fn some_condition() -> bool { true } fn has_permission() -> bool { false } fn load_secure_data() -> &'static str { "secure data" } fn load_public_data() -> &'static str { "public data" } Conditional Vector/Array Creation
fn main() { let use_large_buffer = true; // Conditional array size (must be constant) // This won't work: let arr = if use_large_buffer { [0; 1000] } else { [0; 100] }; // But for Vec it works fine let buffer: Vec<u8> = if use_large_buffer { vec![0; 1000] } else { vec![0; 100] }; println!("Buffer size: {}", buffer.len()); // Conditional collection initialization let items = if has_items() { vec!["apple", "banana", "orange"] } else { Vec::new() }; println!("Items: {:?}", items); } fn has_items() -> bool { false } 4. Nested If Statements
Basic Nesting
fn main() { let x = 10; let y = 5; if x > 0 { println!("x is positive"); if y > 0 { println!("y is also positive"); if x > y { println!("x is greater than y"); } } } // Nested if-else if x > 0 { if y > 0 { println!("Both positive"); } else { println!("x positive, y non-positive"); } } else { if y > 0 { println!("x non-positive, y positive"); } else { println!("Both non-positive"); } } } Avoiding Deep Nesting
fn main() { // Deep nesting (hard to read) let user = Some("Alice"); let age = Some(30); let verified = true; // Bad: Deep nesting if let Some(name) = user { if let Some(age_val) = age { if verified { if age_val >= 18 { println!("{} is an adult", name); } } } } // Good: Early returns with ? fn process_user() -> Option<()> { let name = user?; let age_val = age?; if !verified { return None; } if age_val >= 18 { println!("{} is an adult", name); } Some(()) } process_user(); // Good: Combine conditions with && if let (Some(name), Some(age_val)) = (user, age) { if verified && age_val >= 18 { println!("{} is an adult", name); } } } 5. If Let Syntax
Basic If Let
fn main() { // Instead of match for single pattern let optional = Some(5); // Match version match optional { Some(x) => println!("Value: {}", x), None => (), } // If let version (more concise) if let Some(x) = optional { println!("Value: {}", x); } // With else if let Some(x) = optional { println!("Value: {}", x); } else { println!("No value"); } // With Option::None let none: Option<i32> = None; if let Some(x) = none { println!("Value: {}", x); } else { println!("No value"); } } If Let with Enums
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg = Message::Move { x: 10, y: 20 }; // Match on specific variant if let Message::Move { x, y } = msg { println!("Moving to ({}, {})", x, y); } let msg2 = Message::Write(String::from("hello")); if let Message::Write(text) = msg2 { println!("Writing: {}", text); } else { println!("Not a Write message"); } // Multiple patterns (with else if) let msg3 = Message::ChangeColor(255, 0, 0); if let Message::Quit = msg3 { println!("Quitting"); } else if let Message::Move { x, y } = msg3 { println!("Moving to ({}, {})", x, y); } else if let Message::Write(text) = msg3 { println!("Writing: {}", text); } else if let Message::ChangeColor(r, g, b) = msg3 { println!("Changing color to RGB({}, {}, {})", r, g, b); } } If Let with Destructuring
fn main() { // Destructuring tuples let pair = (10, 20); if let (10, y) = pair { println!("First is 10, second is {}", y); } // Destructuring structs struct Point { x: i32, y: i32, } let point = Point { x: 5, y: 10 }; if let Point { x: 5, y } = point { println!("x is 5, y is {}", y); } // Destructuring with ignored fields if let Point { y, .. } = point { println!("y coordinate is {}", y); } // Destructuring enums with data enum Result { Success(i32), Failure { code: u32, message: String }, } let result = Result::Failure { code: 404, message: String::from("Not Found"), }; if let Result::Failure { code, message } = result { println!("Error {}: {}", code, message); } } 6. While Let
Basic While Let
fn main() { let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); stack.push(4); // While let with Option while let Some(top) = stack.pop() { println!("Popped: {}", top); } println!("Stack is now empty: {:?}", stack); // While let with Iterator let mut iter = (0..5).into_iter(); while let Some(value) = iter.next() { println!("Iterator value: {}", value); } // While let with custom condition let mut x = Some(0); while let Some(i) = x { if i > 5 { println!("Breaking at {}", i); x = None; } else { println!("i = {}", i); x = Some(i + 1); } } } While Let with Mutable References
fn main() { let mut data = Some(vec![1, 2, 3, 4, 5]); while let Some(vec) = data.as_mut() { if let Some(value) = vec.pop() { println!("Popped: {}", value); } else { data = None; } } // More idiomatic way let mut data2 = vec![1, 2, 3, 4, 5]; while let Some(value) = data2.pop() { println!("Value: {}", value); } // Processing a queue use std::collections::VecDeque; let mut queue = VecDeque::new(); queue.push_back(1); queue.push_back(2); queue.push_back(3); while let Some(front) = queue.pop_front() { println!("Processing: {}", front); } } 7. Match vs If Let
When to Use Each
fn main() { let optional = Some(5); let number = 42; // If let - when interested in one pattern if let Some(x) = optional { println!("Got a value: {}", x); } // Match - when need to handle multiple patterns match optional { Some(x) if x > 10 => println!("Large value: {}", x), Some(x) => println!("Value: {}", x), None => println!("No value"), } // If let with complex patterns #[derive(Debug)] enum ComplexEnum { A(i32), B { x: i32, y: i32 }, C(String), } let value = ComplexEnum::B { x: 10, y: 20 }; // If let is perfect for single variant check if let ComplexEnum::B { x, y } = value { println!("Got B with x={}, y={}", x, y); } // Match for comprehensive handling match value { ComplexEnum::A(x) => println!("A: {}", x), ComplexEnum::B { x, y } => println!("B: ({}, {})", x, y), ComplexEnum::C(s) => println!("C: {}", s), } } If Let with Guards
fn main() { let optional = Some(15); // If let with guard if let Some(x) = optional { if x > 10 { println!("Value {} is greater than 10", x); } } // More elegantly with match match optional { Some(x) if x > 10 => println!("Value {} is greater than 10", x), Some(x) => println!("Value {} is not greater than 10", x), None => println!("No value"), } // But if let can't have guards directly // This doesn't work: // if let Some(x) = optional && x > 10 { // Not valid // println!("Value is >10"); // } // Workaround with nested if if let Some(x) = optional { if x > 10 { println!("Value is >10"); } } }