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");
}
}
}