Skip to main content

Rust

Welcome to Rust

Rust is a modern systems programming language that focuses on performance, reliability, and productivity. Developed by Mozilla Research, Rust provides memory safety without garbage collection through its innovative ownership system.

Known for its "fearless concurrency" and zero-cost abstractions, Rust enables developers to write efficient, safe code while preventing common programming errors like null pointer dereferences, buffer overflows, and data races. This makes Rust ideal for systems programming, web assembly, embedded systems, and performance-critical applications.

Introduction to Rust

How to set up a Rust development environment and write your first "Hello, World!" program.

Rust is designed to be safe, concurrent, and practical. Setting up a Rust development environment is straightforward with the official Rust toolchain.

Step 1: Install Rust

  1. All Platforms
    Use rustup, the Rust toolchain installer. Open your terminal and run:

    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

    Or on Windows, download and run the rustup-init.exe from rustup.rs.

Step 2: Verify Installation

Open a terminal and type:

rustc --version

This should display the installed Rust compiler version.

Step 3: Create and Run Your First Rust Program

  1. Create a new Rust project using Cargo:

    cargo new hello_world
    cd hello_world
  2. Examine the generated src/main.rs file:

    fn main() {
        println!("Hello, world!");
    }
  3. Run the program:

    cargo run

    You should see the output: Hello, world!

Congratulations! You have successfully set up a Rust environment and run your first program. 🎉

Rust Syntax Basics

Rust syntax is designed to be expressive and safe. Understanding the basic syntax is crucial for writing correct Rust programs.

1. Basic Structure of a Rust Program

Every Rust program has a specific structure:

fn main() {           // Main function - program entry point
    // Program statements go here
    println!("Hello, Rust!");
}

2. Semicolons and Blocks

Rust uses semicolons to terminate statements and braces {} to define code blocks:

let x = 5;        // Statement ends with semicolon

if x > 3 {        // Braces define the if block
    println!("x is greater than 3");
}                 // Closing brace

3. Comments

Rust supports single-line and documentation comments:

// This is a single-line comment

/// This is a documentation comment
/// It supports Markdown and is used by rustdoc
fn my_function() {
    /* 
     This is a multi-line comment
     It can span multiple lines
    */
}

4. Case Sensitivity

Rust is case-sensitive, meaning it distinguishes between uppercase and lowercase letters:

let my_var = 5;    // Different from myvar
let MyVar = 10;   // Different from myVar

println!("{}", my_var);  // Outputs: 5
println!("{}", MyVar);   // Outputs: 10

5. Variables and Type Inference

Rust has strong, static typing with excellent type inference:

let x = 10;           // Compiler infers i32
let y = 3.14;         // Compiler infers f64
let z = 'A';          // Character
let name = "Rust";    // String slice &str

// Explicit type annotation
let a: i32 = 10;
let b: f64 = 3.14;
let c: char = 'A';
let d: &str = "Rust";

Conclusion

Understanding Rust syntax is essential for writing correct and efficient programs. Key takeaways include:

  • Rust programs start execution from the main() function
  • Statements end with semicolons
  • Code blocks are defined with braces {}
  • Rust is case-sensitive
  • Rust has strong static typing with excellent type inference

Output with println!

The println! macro in Rust is used to display output on the screen. It's one of the most commonly used features for basic output and debugging.

1. Basic println! Usage

The simplest way to use println! is with string literals:

fn main() {
    println!("Hello, World!");  // Outputs: Hello, World!
    println!("{}", 42);         // Outputs: 42
}

2. Formatting with Placeholders

You can use placeholders to format output:

println!("Hello {}!", "Rust");  // Outputs: Hello Rust!
println!("{} + {} = {}", 5, 3, 8);  // Outputs: 5 + 3 = 8

3. Positional and Named Arguments

Rust supports positional and named arguments in formatting:

// Positional arguments
println!("{0} {1} {0}", "foo", "bar");  // Outputs: foo bar foo

// Named arguments
println!("{name} is {age} years old", name = "Alice", age = 25);

4. Formatting Specifiers

Rust provides various formatting specifiers for different data types:

let number = 42;
let float = 3.14159;

println!("Decimal: {}", number);        // 42
println!("Binary: {:b}", number);       // 101010
println!("Hex: {:x}", number);          // 2a
println!("Octal: {:o}", number);        // 52
println!("Float: {:.2}", float);        // 3.14
println!("Scientific: {:e}", float);    // 3.14159e0

5. Printing Variables and Expressions

You can print variables and expressions:

let name = "Alice";
let age = 25;
println!("Name: {}, Age: {}", name, age);

// Expressions work too
println!("5 + 3 = {}", 5 + 3);

6. Debug and Display Traits

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

let point = Point { x: 10, y: 20 };

// Debug formatting
println!("{:?}", point);   // Outputs: Point { x: 10, y: 20 }
println!("{:#?}", point);  // Pretty-print:
// Point {
//     x: 10,
//     y: 20,
// }

Conclusion

The println! macro is a fundamental tool for output in Rust. Key points:

  • Use {} as placeholders for values
  • Supports positional and named arguments
  • Rich formatting options for different data types
  • Can print debug information with {:?}

Arithmetic Operators in Rust

Rust provides standard arithmetic operators for mathematical calculations. These operators work with numeric data types like integers and floating-point numbers.

Basic Arithmetic Operators

fn main() {
    let a = 15;
    let b = 4;
    
    println!("a + b = {}", a + b);  // Addition: 19
    println!("a - b = {}", a - b);  // Subtraction: 11
    println!("a * b = {}", a * b);  // Multiplication: 60
    println!("a / b = {}", a / b);  // Division: 3 (integer division)
    println!("a % b = {}", a % b);  // Modulus: 3
    
    let x = 15.0;
    let y = 4.0;
    println!("x / y = {}", x / y);  // Division: 3.75 (float division)
}

Type Considerations

// Rust requires explicit type conversion
let a: i32 = 10;
let b: f64 = 3.14;

// This would cause a compile error:
// let result = a + b;

// Correct approach with explicit conversion
let result = a as f64 + b;
println!("Result: {}", result);  // 13.14

Overflow Handling

let max_u8 = 255u8;

// This would panic in debug mode or wrap in release mode
// let overflow = max_u8 + 1;

// Safe arithmetic operations
let checked = max_u8.checked_add(1);  // Returns None
let wrapping = max_u8.wrapping_add(1); // Returns 0
let saturating = max_u8.saturating_add(1); // Returns 255

println!("Checked: {:?}", checked);      // None
println!("Wrapping: {}", wrapping);      // 0
println!("Saturating: {}", saturating);  // 255

Common Pitfalls

  • Integer division truncates the fractional part: 5 / 2 equals 2, not 2.5
  • Mixing different numeric types requires explicit conversion
  • Division by zero causes a panic at runtime
  • Integer overflow behavior depends on compilation mode

Comparison Operators in Rust

Comparison operators compare two values and return a boolean result (true or false). These are essential for conditional statements and loops.

Comparison Operators

fn main() {
    let x = 7;
    let y = 10;
    
    println!("x == y: {}", x == y);  // Equal to: false
    println!("x != y: {}", x != y);  // Not equal to: true
    println!("x > y: {}", x > y);    // Greater than: false
    println!("x < y: {}", x < y);    // Less than: true
    println!("x >= 7: {}", x >= 7);  // Greater than or equal: true
    println!("y <= 7: {}", y <= 7);  // Less than or equal: false
    
    // Comparing different types requires conversion
    let int_val: i32 = 5;
    let float_val: f64 = 5.0;
    
    // This works because we convert to the same type
    println!("Equal: {}", int_val as f64 == float_val);  // true
}

Using Comparisons in Conditions

let age = 18;
if age >= 18 {
    println!("You are an adult");
} else {
    println!("You are a minor");
}

// Pattern matching with comparisons
match age {
    0..=17 => println!("Minor"),
    18..=64 => println!("Adult"),
    _ => println!("Senior"),
}

Comparing Custom Types

#[derive(PartialEq, PartialOrd)]
struct Point {
    x: i32,
    y: i32,
}

let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 3, y: 4 };

// Now we can compare Point instances
println!("p1 == p2: {}", p1 == p2);  // false
// println!("p1 < p2: {}", p1 < p2);  // This would need FullOrd derivation

Common Pitfalls

  • Comparing different numeric types requires explicit conversion
  • Custom types need to derive comparison traits to use operators
  • Floating-point comparisons for exact equality can be problematic due to precision issues
  • NaN values always compare as not equal, even to themselves

Logical Operators in Rust

Logical operators combine boolean expressions and return true or false. Rust provides three logical operators: && (AND), || (OR), and ! (NOT).

Logical Operators

fn main() {
    let is_sunny = true;
    let is_warm = false;
    
    println!("is_sunny && is_warm: {}", is_sunny && is_warm);  // AND: false
    println!("is_sunny || is_warm: {}", is_sunny || is_warm);  // OR: true
    println!("!is_warm: {}", !is_warm);                      // NOT: true
    
    // Complex conditions
    let age = 25;
    let has_license = true;
    
    if age >= 18 && has_license {
        println!("You can drive");
    }
}

Short-Circuit Evaluation

// In AND (&&), if first condition is false, second isn't evaluated
// In OR (||), if first condition is true, second isn't evaluated

fn expensive_check() -> bool {
    println!("Expensive check performed!");
    true
}

let x = false;
if x && expensive_check() {  // expensive_check() not called
    println!("This won't execute");
}

let y = true;
if y || expensive_check() {  // expensive_check() not called
    println!("This will execute without calling expensive_check");
}

Boolean Operations on Other Types

// Option and Result types have boolean-like operations
let some_value: Option<i32> = Some(5);
let none_value: Option<i32> = None;

// Using and_then, or_else for chaining operations
let result = some_value.and_then(|x| Some(x * 2));  // Some(10)
let result2 = none_value.and_then(|x| Some(x * 2)); // None

println!("Result: {:?}", result);
println!("Result2: {:?}", result2);

Common Pitfalls

  • Using bitwise operators (&, |) instead of logical operators (&&, ||)
  • Forgetting that logical operators only work with boolean types
  • Not leveraging short-circuit evaluation for performance

Bitwise Operators in Rust

Bitwise operators perform operations on individual bits of integer types. They are used in low-level programming, embedded systems, and performance optimization.

Bitwise Operators

fn main() {
    let a: u8 = 0b0110;  // binary: 0110 (6)
    let b: u8 = 0b0011;  // binary: 0011 (3)
    
    println!("a = {:08b} ({})", a, a);
    println!("b = {:08b} ({})", b, b);
    
    println!("a & b = {:08b} ({})", a & b, a & b);  // AND: 0010 (2)
    println!("a | b = {:08b} ({})", a | b, a | b);  // OR: 0111 (7)
    println!("a ^ b = {:08b} ({})", a ^ b, a ^ b);  // XOR: 0101 (5)
    println!("!a = {:08b} ({})", !a, !a);           // NOT: 11111001 (249)
    println!("a << 1 = {:08b} ({})", a << 1, a << 1); // Left shift: 1100 (12)
    println!("b >> 1 = {:08b} ({})", b >> 1, b >> 1); // Right shift: 0001 (1)
}

Practical Applications

// Setting and checking flags
const FLAG_A: u8 = 1 << 0;  // 0001
const FLAG_B: u8 = 1 << 1;  // 0010  
const FLAG_C: u8 = 1 << 2;  // 0100

let mut settings = 0;
settings |= FLAG_A;  // Set flag A
settings |= FLAG_C;  // Set flag C

// Check if flag B is set
if settings & FLAG_B != 0 {
    println!("Flag B is set");
} else {
    println!("Flag B is not set");
}

// Count set bits (population count)
let value: u32 = 0b10101100;
let count = value.count_ones();
println!("Number of set bits in {}: {}", value, count);

Bit Manipulation Methods

let num: u8 = 0b10101010;

// Various bit manipulation methods
println!("Leading zeros: {}", num.leading_zeros());
println!("Trailing zeros: {}", num.trailing_zeros());
println!("Count ones: {}", num.count_ones());
println!("Rotate left: {:08b}", num.rotate_left(2));
println!("Swap bytes: {:08b}", num.swap_bytes());

// Check power of two
let power_of_two = 16u32;
println!("{} is power of two: {}", power_of_two, power_of_two.is_power_of_two());

Common Pitfalls

  • Confusing bitwise AND & with logical AND &&
  • Shift operations on signed integers maintain sign (arithmetic shift)
  • Shifting beyond the bit width causes panic in debug mode
  • Forgetting that bitwise NOT on signed integers also flips the sign bit

Assignment Operators in Rust

Assignment operators assign values to variables. Rust provides compound assignment operators that combine assignment with arithmetic or bitwise operations.

Assignment Operators

fn main() {
    let mut x = 10;        // Simple assignment
    println!("x = {}", x);
    
    x += 5;                // Equivalent to x = x + 5
    println!("x += 5: {}", x);  // 15
    
    x -= 3;                // Equivalent to x = x - 3
    println!("x -= 3: {}", x);  // 12
    
    x *= 2;                // Equivalent to x = x * 2
    println!("x *= 2: {}", x);  // 24
    
    x /= 4;                // Equivalent to x = x / 4
    println!("x /= 4: {}", x);  // 6
    
    x %= 4;                // Equivalent to x = x % 4
    println!("x %= 4: {}", x);  // 2
    
    // Bitwise assignment operators
    let mut y: u8 = 5;
    y &= 3;                // AND assignment: y = y & 3
    y |= 8;                // OR assignment: y = y | 8  
    y ^= 4;                // XOR assignment: y = y ^ 4
    
    println!("Final y: {}", y);
}

Multiple Assignment and Destructuring

// Multiple assignment (destructuring)
let (a, b, c) = (10, 20, 30);
println!("a={}, b={}, c={}", a, b, c);

// Swap values
let mut x = 5;
let mut y = 10;
println!("Before: x={}, y={}", x, y);
std::mem::swap(&mut x, &mut y);
println!("After: x={}, y={}", x, y);

// Compound assignment in loops
let mut sum = 0;
for i in 1..=5 {
    sum += i;  // Add i to sum
}
println!("Sum: {}", sum);  // 15

Assignment with Different Types

// Type conversion during assignment
let int_val: i32 = 42;
let float_val: f64 = int_val as f64;  // Explicit conversion

// Assignment from function returns
let result = {
    let x = 5;
    let y = 10;
    x + y  // No semicolon - this expression is returned
};
println!("Result: {}", result);  // 15

// Assignment with pattern matching
let (success, value) = if true {
    (true, 42)
} else {
    (false, 0)
};
println!("Success: {}, Value: {}", success, value);

Common Pitfalls

  • Forgetting mut keyword when variable needs to be mutable
  • Trying to assign to immutable variables
  • Mixing types in compound assignments without explicit conversion
  • Shadowing variables instead of mutating them

Integer Data Types in Rust

Rust provides several integer types with explicit sizes and signed/unsigned variants. Choosing the right integer type depends on the required range and memory constraints.

Basic Integer Types

fn main() {
    // Signed integers (can represent negative numbers)
    let i8_val: i8 = 100;        // 8-bit signed (-128 to 127)
    let i16_val: i16 = 1000;     // 16-bit signed
    let i32_val: i32 = 100000;   // 32-bit signed (default)
    let i64_val: i64 = 100000;   // 64-bit signed
    let i128_val: i128 = 100000; // 128-bit signed
    let isize_val: isize = 100000; // Architecture-dependent signed
    
    // Unsigned integers (only non-negative numbers)
    let u8_val: u8 = 100;        // 8-bit unsigned (0 to 255)
    let u16_val: u16 = 1000;     // 16-bit unsigned
    let u32_val: u32 = 100000;   // 32-bit unsigned
    let u64_val: u64 = 100000;   // 64-bit unsigned  
    let u128_val: u128 = 100000; // 128-bit unsigned
    let usize_val: usize = 100000; // Architecture-dependent unsigned
    
    // Type inference
    let inferred = 42;           // Compiler infers i32
    let explicit: u64 = 42;      // Explicit type
    
    println!("i8 range: {} to {}", i8::MIN, i8::MAX);
    println!("u8 range: {} to {}", u8::MIN, u8::MAX);
    println!("usize size: {} bytes", std::mem::size_of::<usize>());
}

Integer Operations

let a = 10;
let b = 3;
println!("a + b = {}", a + b);  // 13
println!("a - b = {}", a - b);  // 7
println!("a * b = {}", a * b);  // 30
println!("a / b = {}", a / b);  // 3 (integer division)
println!("a % b = {}", a % b);  // 1 (modulus)

// Checked operations (avoid panics)
match a.checked_add(b) {
    Some(result) => println!("Safe addition: {}", result),
    None => println!("Addition would overflow"),
}

// Wrapping operations
let wrapped = a.wrapping_add(b);
println!("Wrapped addition: {}", wrapped);

Type Conversion and Casting

// Safe casting between integer types
let small: u8 = 255;
let large: u16 = small as u16;  // 255

// This would lose data but is allowed
let large_num: i32 = 300;
let small_num: u8 = large_num as u8;  // 44 (300 - 256)

// String to integer
let str_val = "123";
let int_val: i32 = str_val.parse().unwrap();  // 123
let int_val2: i32 = "123".parse().expect("Not a number!");

// Different parsing approaches
let num1 = "42".parse::<i32>();        // Ok(42)
let num2 = "42abc".parse::<i32>();     // Err

Common Pitfalls

  • Integer overflow panics in debug mode, wraps in release mode
  • Integer division truncates fractional parts
  • Casting between types can silently lose data
  • Parsing strings can fail - always handle Result types

Floating-Point Data Types in Rust

Floating-point types represent real numbers with fractional parts. Rust provides two floating-point types with different precision levels.

Floating-Point Types

fn main() {
    let f32_val: f32 = 3.14159;         // Single precision (32 bits)
    let f64_val: f64 = 3.14159265358979; // Double precision (64 bits, default)
    
    // Type inference
    let inferred_float = 3.14;  // Compiler infers f64
    
    println!("f32: {:.10}", f32_val);
    println!("f64: {:.10}", f64_val);
    
    // Special values
    println!("sqrt(-1): {:?}", (-1.0f64).sqrt());  // NaN (Not a Number)
    println!("1.0/0.0: {:?}", 1.0 / 0.0);          // inf (Infinity)
    println!("-1.0/0.0: {:?}", -1.0 / 0.0);        // -inf
    
    // Checking special values
    let nan = f64::NAN;
    println!("Is NaN: {}", nan.is_nan());
    println!("Is finite: {}", f64_val.is_finite());
    println!("Is infinite: {}", f64::INFINITY.is_infinite());
}

Floating-Point Operations

let a = 2.5;
let b = 1.5;
println!("a + b = {}", a + b);  // 4.0
println!("a - b = {}", a - b);  // 1.0
println!("a * b = {}", a * b);  // 3.75
println!("a / b = {}", a / b);  // 1.6666666666666667

// Mathematical functions
println!("sqrt(a) = {}", a.sqrt());
println!("powf(a, b) = {}", a.powf(b));
println!("sin(pi/2) = {}", (std::f64::consts::PI / 2.0).sin());

// Rounding operations
let num = 3.75;
println!("floor: {}", num.floor());    // 3.0
println!("ceil: {}", num.ceil());      // 4.0
println!("round: {}", num.round());    // 4.0
println!("trunc: {}", num.trunc());    // 3.0
println!("fract: {}", num.fract());    // 0.75

Type Conversion and Precision

// String to floating-point
let str_val = "3.14159";
let float_val: f64 = str_val.parse().unwrap();

// Floating-point to integer (truncation)
let f = 3.99;
let i = f as i32;  // i becomes 3

// Integer to floating-point
let x = 5;
let y = x as f64;  // y becomes 5.0

// Be careful with precision
let precise: f64 = 0.1 + 0.2;
println!("0.1 + 0.2 = {}", precise);  // 0.30000000000000004
println!("Equal to 0.3: {}", precise == 0.3);  // false

// Better approach for comparisons
let tolerance = 1e-10;
println!("Close to 0.3: {}", (precise - 0.3).abs() < tolerance);  // true

Common Pitfalls

  • Floating-point numbers have limited precision and rounding errors
  • Comparing floating-point numbers for exact equality is often problematic
  • Operations with very large or very small numbers can cause overflow/underflow
  • NaN values propagate through calculations and break comparisons

Strings in Rust

Rust has two main string types: String (owned, growable) and &str (string slice, borrowed). Understanding both is crucial for effective Rust programming.

Creating and Using Strings

fn main() {
    // Different ways to create strings
    let s1 = String::from("Hello");
    let s2 = "World".to_string();
    let s3 = String::new();
    let s4 = "String slice";  // This is &str, not String
    
    // Concatenation
    let s5 = s1 + " " + &s2;  // Note: s1 is moved here
    println!("s5: {}", s5);
    
    // Format macro for complex concatenation
    let s6 = format!("{} {}!", "Hello", "Rust");
    println!("s6: {}", s6);
    
    // String slices (&str)
    let slice = "Hello World";
    let hello = &slice[0..5];  // "Hello"
    let world = &slice[6..11]; // "World"
    println!("hello: {}, world: {}", hello, world);
    
    // String length and capacity
    let mut text = String::from("Hello");
    println!("Length: {}", text.len());        // 5
    println!("Capacity: {}", text.capacity()); // 5
    println!("Is empty: {}", text.is_empty()); // false
    
    text.push_str(" Rust!");
    println!("After push_str: {}", text);      // Hello Rust!
}

String Comparison and Searching

let str1 = "apple";
let str2 = "banana";
    
// Comparison
if str1 == str2 {
    println!("Strings are equal");
} else if str1 < str2 {
    println!("{} comes before {}", str1, str2);
} else {
    println!("{} comes after {}", str1, str2);
}

// Searching
let text = "Hello World";
if let Some(pos) = text.find("World") {
    println!("Found 'World' at position {}", pos);
}

// Pattern matching with contains
if text.contains("Hello") {
    println!("Text contains 'Hello'");
}

// Starts with / ends with
println!("Starts with 'Hello': {}", text.starts_with("Hello"));
println!("Ends with 'World': {}", text.ends_with("World"));

String Modification

let mut text = String::from("Hello");
    
// Append characters and strings
text.push('!');           // Add single character
text.push_str(" Rust");   // Add string slice
    
// Insert at position
text.insert(5, ',');      // Insert character at index 5
text.insert_str(6, " dear"); // Insert string at index 6
    
// Replace
let replaced = text.replace("dear", "wonderful");
    
// Remove characters
let mut remove_example = String::from("Hello");
let removed_char = remove_example.remove(4);  // Removes 'o' at index 4
remove_example.pop();     // Remove last character
    
println!("Modified: {}", text);
println!("Replaced: {}", replaced);
println!("After removal: {}", remove_example);

Iterating Over Strings

let text = "Hello 世界";  // Mix of ASCII and non-ASCII
    
// Iterate by characters (Unicode scalar values)
println!("Characters:");
for c in text.chars() {
    print!("{} ", c);
}
println!();
    
// Iterate by bytes
println!("Bytes:");
for b in text.bytes() {
    print!("{} ", b);
}
println!();
    
// Iterate with indices
for (i, c) in text.char_indices() {
    println!("Character at {}: {}", i, c);
}

Common Pitfalls

  • Indexing strings with [] is not allowed - use slicing with caution
  • String slices must occur on valid UTF-8 boundaries
  • Confusing String (owned) with &str (borrowed)
  • Forgetting that some string operations move ownership

String Functions in Rust

Rust's String type provides many useful methods for string manipulation, including trimming, case conversion, splitting, and pattern matching.

String Manipulation Methods

fn main() {
    let text = "   Hello World   ";
    
    // Trimming
    let trimmed = text.trim();
    println!("Trimmed: '{}'", trimmed);  // 'Hello World'
    
    let left_trimmed = text.trim_start();
    let right_trimmed = text.trim_end();
    println!("Left trimmed: '{}'", left_trimmed);
    println!("Right trimmed: '{}'", right_trimmed);
    
    // Case conversion
    let mixed = "Hello World";
    let uppercase = mixed.to_uppercase();
    let lowercase = mixed.to_lowercase();
    println!("Uppercase: {}", uppercase);  // HELLO WORLD
    println!("Lowercase: {}", lowercase);  // hello world
    
    // Repeat strings
    let repeated = "abc".repeat(3);
    println!("Repeated: {}", repeated);  // abcabcabc
}

String Splitting and Joining

// Splitting a string
let data = "apple,banana,cherry";
let fruits: Vec<&str> = data.split(',').collect();
println!("Fruits: {:?}", fruits);  // ["apple", "banana", "cherry"]

// Splitting with multiple delimiters
let text = "apple, banana; cherry";
let items: Vec<&str> = text.split([',', ';']).map(|s| s.trim()).collect();
println!("Items: {:?}", items);  // ["apple", "banana", "cherry"]

// Splitting into lines
let multiline = "Line 1\nLine 2\nLine 3";
for line in multiline.lines() {
    println!("Line: {}", line);
}

// Joining strings
let joined = fruits.join("-");
println!("Joined: {}", joined);  // apple-banana-cherry

// Using collect to join
let collected: String = fruits.into_iter().collect();
println!("Collected: {}", collected);  // applebananacherry

Pattern Matching with Strings

let text = "The quick brown fox";
    
// Using patterns with string methods
let words: Vec<&str> = text.split_whitespace().collect();
println!("Words: {:?}", words);
    
// Matching with patterns
match text {
    "hello" => println!("It's hello!"),
    s if s.starts_with("The") => println!("Starts with 'The'"),
    s if s.contains("fox") => println!("Contains 'fox'"),
    _ => println!("Something else"),
}
    
// Using matches! macro
if text.matches("o").count() == 3 {
    println!("Text contains exactly three 'o' characters");
}

String Validation and Inspection

let text = "Hello123";
    
// Checking string properties
println!("Is ASCII: {}", text.is_ascii());
println!("Is empty: {}", text.is_empty());
println!("Length: {}", text.len());
println!("Number of chars: {}", text.chars().count());
    
// Checking character categories
println!("All alphabetic: {}", text.chars().all(|c| c.is_alphabetic()));
println!("Any digit: {}", text.chars().any(|c| c.is_numeric()));
    
// Finding positions
if let Some(pos) = text.find('1') {
    println!("First digit at position: {}", pos);
}
    
// RFind (reverse find)
if let Some(pos) = text.rfind('l') {
    println!("Last 'l' at position: {}", pos);
}

Common Pitfalls

  • String indexing is not allowed - use chars() or pattern matching
  • Slicing can panic if not on character boundaries
  • split() returns an iterator, need to collect() to get Vec
  • Some operations consume the string, others work on references

String Formatting in Rust

Rust provides powerful string formatting through the format! macro and related formatting traits. This allows for flexible and type-safe string creation.

Basic Formatting with format!

fn main() {
    let name = "Alice";
    let age = 25;
    let score = 95.5;
    
    // Basic formatting
    let message = format!("Name: {}, Age: {}, Score: {}", name, age, score);
    println!("{}", message);
    
    // Positional arguments
    let pos_message = format!("{0} {1} {0}", "foo", "bar");
    println!("{}", pos_message);  // foo bar foo
    
    // Named arguments
    let named_message = format!("{name} is {age} years old", name = "Bob", age = 30);
    println!("{}", named_message);
}

Formatting Specifiers

let number = 42;
let float = 3.14159;
    
// Number formatting
println!("Decimal: {}", number);           // 42
println!("Binary: {:b}", number);          // 101010
println!("Hex: {:x}", number);             // 2a
println!("Hex uppercase: {:X}", number);   // 2A
println!("Octal: {:o}", number);           // 52
println!("Pointer: {:p}", &number);        // memory address
    
// Float formatting
println!("Float: {}", float);              // 3.14159
println!("Float 2 decimal: {:.2}", float); // 3.14
println!("Scientific: {:e}", float);       // 3.14159e0
println!("Scientific upper: {:E}", float); // 3.14159E0
    
// Padding and alignment
println!("Right aligned: {:>10}", number);  // "        42"
println!("Left aligned: {:<10}", number);    // "42        "
println!("Center aligned: {:^10}", number);  // "    42    "
println!("Zero padded: {:010}", number);     // "0000000042"

Custom Formatting with Traits

use std::fmt;
    
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}
    
// Implement Display trait for custom formatting
impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}
    
fn main() {
    let point = Point { x: 10, y: 20 };
    
    // Using Display trait (user-facing)
    println!("Point: {}", point);        // Point: (10, 20)
    
    // Using Debug trait (developer-facing)
    println!("Point: {:?}", point);      // Point: Point { x: 10, y: 20 }
    println!("Point: {:#?}", point);     // Pretty-printed:
    // Point {
    //     x: 10,
    //     y: 20,
    // }
}

Advanced Formatting Options

let value = 255;
    
// Formatting with different bases
println!("Decimal: {}", value);      // 255
println!("Binary: {:b}", value);     // 11111111
println!("Hex: {:x}", value);        // ff
println!("Hex with prefix: {:#x}", value); // 0xff
println!("Octal: {:o}", value);      // 377
println!("Octal with prefix: {:#o}", value); // 0o377
    
// Formatting with sign
let positive = 42;
let negative = -42;
println!("Positive: {:+}", positive);  // +42
println!("Negative: {:+}", negative);  // -42
    
// Formatting with thousands separator (using crate)
// Typically you'd use a formatting crate for this
    
// Conditional formatting
let maybe_value = Some(42);
println!("Option: {:?}", maybe_value);  // Some(42)
println!("Option: {}", maybe_value.unwrap_or(0)); // 42

Formatting Collections

let numbers = vec![1, 2, 3, 4, 5];
let words = vec!["apple", "banana", "cherry"];
    
// Formatting vectors
println!("Numbers: {:?}", numbers);        // [1, 2, 3, 4, 5]
println!("Numbers: {:#?}", numbers);       // Pretty-printed
println!("Words: {}", words.join(", "));   // apple, banana, cherry
    
// Custom collection formatting
let formatted = format!("First: {}, Last: {}", numbers[0], numbers[numbers.len() - 1]);
println!("{}", formatted);  // First: 1, Last: 5
    
// Using iterators with format
let squares: Vec<String> = numbers.iter()
    .map(|&x| format!("{}²={}", x, x*x))
    .collect();
println!("Squares: {}", squares.join(", "));

Common Pitfalls

  • Forgetting that format! returns a String, doesn't print
  • Mixing up Display ({}) and Debug ({:?}) formatting
  • Format strings are checked at compile time - invalid formats cause errors
  • Custom types need to implement Display or Debug for formatting

Arrays in Rust

Arrays are fixed-size collections of elements of the same type stored in contiguous memory. Rust arrays have a compile-time fixed size and are stack-allocated.

Fixed-Size Arrays

fn main() {
    // Declaration and initialization
    let numbers: [i32; 5] = [1, 2, 3, 4, 5];  // Type annotation
    let inferred = [1, 2, 3, 4, 5];            // Type inferred as [i32; 5]
    let same_value = [0; 5];                   // [0, 0, 0, 0, 0]
    
    // Accessing elements
    println!("First element: {}", numbers[0]);  // 1
    println!("Last element: {}", numbers[4]);   // 5
    
    // Modifying elements (array must be mutable)
    let mut mutable_array = [1, 2, 3, 4, 5];
    mutable_array[2] = 100;
    println!("Modified: {:?}", mutable_array);
    
    // Array size
    println!("Array length: {}", numbers.len());
    
    // Iterating through array
    println!("Array elements:");
    for i in 0..numbers.len() {
        println!("numbers[{}] = {}", i, numbers[i]);
    }
    
    // Better: using iterators
    for element in &numbers {
        println!("Element: {}", element);
    }
    
    // With enumerate for index
    for (i, element) in numbers.iter().enumerate() {
        println!("numbers[{}] = {}", i, element);
    }
}

Multi-dimensional Arrays

// 2D array (matrix)
let matrix: [[i32; 3]; 3] = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

// Accessing elements
println!("Element at [1][2]: {}", matrix[1][2]);  // 6

// Nested loops for 2D array
for row in &matrix {
    for element in row {
        print!("{} ", element);
    }
    println!();
}

// 3D array
let cube: [[[i32; 2]; 2]; 2] = [
    [[1, 2], [3, 4]],
    [[5, 6], [7, 8]]
];
println!("Element at [1][0][1]: {}", cube[1][0][1]);  // 6

Array Methods and Operations

let arr = [1, 2, 3, 4, 5];

// Slicing arrays
let slice = &arr[1..4];  // [2, 3, 4]
println!("Slice: {:?}", slice);

// Array patterns in match
match arr {
    [1, 2, 3, 4, 5] => println!("Exact match!"),
    [first, .., last] => println!("First: {}, Last: {}", first, last),
}

// Converting to slices
let slice_ref: &[i32] = &arr;
println!("Slice length: {}", slice_ref.len());

// Array bounds checking
// This would panic at runtime:
// println!("{}", arr[10]);

// Safe access with get()
if let Some(element) = arr.get(2) {
    println!("Element at index 2: {}", element);  // 3
}

if let None = arr.get(10) {
    println!("Index 10 is out of bounds");
}

Array Limitations and Alternatives

// Arrays have fixed size determined at compile time
// This won't work:
// let size = 5;
// let arr = [0; size];  // Error: size must be constant

// Use Vec for dynamic arrays
use std::convert::TryInto;

// Converting between arrays and vectors
let vec = vec![1, 2, 3, 4, 5];
// Convert Vec to array (if you know the size)
let array: [i32; 5] = vec.try_into().unwrap_or_else(|v: Vec<i32>| panic!("Expected length 5, got {}", v.len()));

// Array from function
fn create_array() -> [i32; 3] {
    [1, 2, 3]  // Return type must match exactly
}

Common Pitfalls

  • Array indices start at 0, not 1
  • Array size must be known at compile time
  • Index out of bounds causes panic at runtime
  • Arrays are different types if they have different sizes
  • Cannot return arrays from functions that have different sizes

Array Indexing in Rust

Array indexing allows access to individual elements in an array using their position. Rust uses zero-based indexing and provides bounds checking for safety.

Basic Array Indexing

fn main() {
    let numbers = [10, 20, 30, 40, 50];
    
    // Accessing elements by index
    println!("numbers[0] = {}", numbers[0]);  // First element: 10
    println!("numbers[2] = {}", numbers[2]);  // Third element: 30
    println!("numbers[4] = {}", numbers[4]);  // Last element: 50
    
    // Modifying elements (array must be mutable)
    let mut mutable_numbers = [10, 20, 30, 40, 50];
    mutable_numbers[1] = 25;  // Change second element from 20 to 25
    mutable_numbers[3] = 45;  // Change fourth element from 40 to 45
    
    // Display modified array
    for (i, &value) in mutable_numbers.iter().enumerate() {
        println!("numbers[{}] = {}", i, value);
    }
}

Bounds Checking and Safe Access

let arr = [1, 2, 3, 4, 5];

// Unsafe indexing (panics if out of bounds)
// println!("{}", arr[10]);  // This would panic!

// Safe indexing with get()
match arr.get(2) {
    Some(&value) => println!("Element at index 2: {}", value),  // 3
    None => println!("Index 2 is out of bounds"),
}

match arr.get(10) {
    Some(&value) => println!("Element at index 10: {}", value),
    None => println!("Index 10 is out of bounds"),  // This executes
}

// Using get() with if let
if let Some(&value) = arr.get(1) {
    println!("Second element: {}", value);  // 2
}

// Using get_mut() for mutable access
let mut mutable_arr = [1, 2, 3];
if let Some(element) = mutable_arr.get_mut(1) {
    *element = 100;  // Modify through mutable reference
}
println!("Modified: {:?}", mutable_arr);  // [1, 100, 3]

Array Slicing

let numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

// Creating slices
let full_slice = &numbers[..];        // Entire array
let start_slice = &numbers[3..];      // From index 3 to end: [3, 4, 5, 6, 7, 8, 9]
let end_slice = &numbers[..5];        // From start to index 4: [0, 1, 2, 3, 4]
let middle_slice = &numbers[2..7];    // From index 2 to 6: [2, 3, 4, 5, 6]
let exclusive_end = &numbers[2..=6];  // From index 2 to 6 inclusive: [2, 3, 4, 5, 6]

println!("Full: {:?}", full_slice);
println!("Start from 3: {:?}", start_slice);
println!("Up to 5: {:?}", end_slice);
println!("Middle 2..7: {:?}", middle_slice);

// Slices are references and don't own the data
// This allows multiple read-only accesses
let slice1 = &numbers[0..3];
let slice2 = &numbers[3..6];
println!("Slice1: {:?}, Slice2: {:?}", slice1, slice2);

Pattern Matching with Arrays

let point = [10, 20];

// Destructuring arrays
let [x, y] = point;
println!("x: {}, y: {}", x, y);  // x: 10, y: 20

// Pattern matching in match statements
match point {
    [0, 0] => println!("Origin"),
    [x, 0] => println!("On x-axis at {}", x),
    [0, y] => println!("On y-axis at {}", y),
    [x, y] => println!("At ({}, {})", x, y),
}

// Using .. to ignore parts of the array
let larger = [1, 2, 3, 4, 5];
match larger {
    [first, .., last] => println!("First: {}, Last: {}", first, last),  // 1, 5
    _ => println!("Other pattern"),
}

// Matching specific patterns
let coords = [[1, 2], [3, 4], [5, 6]];
for &[x, y] in &coords {
    println!("Coordinate: ({}, {})", x, y);
}

Common Pitfalls

  • Accessing out-of-bounds indices causes panic at runtime
  • Array indices start at 0, not 1
  • Slicing beyond array bounds causes panic
  • Forgetting that slices are references, not owned data

Array Functions and Methods in Rust

While Rust arrays have a fixed size and limited built-in methods, there are many ways to work with arrays using standard library functions, iterators, and conversions.

Array Utilities and Conversions

fn main() {
    let numbers = [5, 2, 8, 1, 9, 3];
    
    // Array length
    println!("Array length: {}", numbers.len());
    
    // Check if empty (arrays are never empty if size > 0)
    println!("Is empty: {}", numbers.is_empty());  // false
    
    // First and last elements
    println!("First: {:?}", numbers.first());      // Some(5)
    println!("Last: {:?}", numbers.last());        // Some(3)
    
    // Converting to slices (most array methods work on slices)
    let slice: &[i32] = &numbers;
    println!("Slice length: {}", slice.len());
}

Working with Array Iterators

let numbers = [1, 2, 3, 4, 5];

// Basic iteration
println!("Elements:");
for num in &numbers {
    println!("{}", num);
}

// Using iterator methods
let sum: i32 = numbers.iter().sum();
println!("Sum: {}", sum);  // 15

let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
println!("Doubled: {:?}", doubled);  // [2, 4, 6, 8, 10]

// Filtering
let evens: Vec<&i32> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
println!("Evens: {:?}", evens);  // [2, 4]

// Finding elements
if let Some(&value) = numbers.iter().find(|&&x| x == 3) {
    println!("Found 3 at some position");
}

// Position of element
if let Some(pos) = numbers.iter().position(|&x| x == 4) {
    println!("4 found at position: {}", pos);  // 3
}

Array Manipulation Techniques

// Since arrays are fixed-size, we often work with slices or convert to Vec

// Sorting (this consumes the array and returns a new one)
let mut unsorted = [5, 2, 8, 1, 9];
unsorted.sort();
println!("Sorted: {:?}", unsorted);  // [1, 2, 5, 8, 9]

// Reverse
unsorted.reverse();
println!("Reversed: {:?}", unsorted);  // [9, 8, 5, 2, 1]

// Fill array with value
let mut arr = [0; 5];
arr.fill(42);
println!("Filled: {:?}", arr);  // [42, 42, 42, 42, 42]

// Windows and chunks (working with slices)
let data = [1, 2, 3, 4, 5, 6];
for window in data.windows(3) {
    println!("Window: {:?}", window);  // [1,2,3], [2,3,4], [3,4,5], [4,5,6]
}

for chunk in data.chunks(2) {
    println!("Chunk: {:?}", chunk);  // [1,2], [3,4], [5,6]
}

Array Conversions

use std::convert::TryInto;

// Converting between arrays and other types
let vec = vec![1, 2, 3, 4, 5];

// Vec to array (must know exact size)
let array: [i32; 5] = match vec.try_into() {
    Ok(arr) => arr,
    Err(_) => panic!("Cannot convert Vec to array - wrong size"),
};

// Array to Vec
let back_to_vec = array.to_vec();
println!("Back to Vec: {:?}", back_to_vec);

// TryFrom conversion (more idiomatic)
let another_array: [i32; 5] = vec.try_into().expect("Wrong size");

// Converting between different array sizes
let small = [1, 2, 3];
// This doesn't work directly:
// let large: [i32; 5] = small;  // Error

// Manual conversion
let mut large = [0; 5];
large[..3].copy_from_slice(&small);
println!("Extended: {:?}", large);  // [1, 2, 3, 0, 0]

Multi-dimensional Array Operations

let matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

// Flattening 2D array
let flattened: Vec<i32> = matrix.iter().flatten().cloned().collect();
println!("Flattened: {:?}", flattened);  // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// Transpose (manual implementation)
let mut transpose = [[0; 3]; 3];
for i in 0..3 {
    for j in 0..3 {
        transpose[j][i] = matrix[i][j];
    }
}
println!("Transpose: {:?}", transpose);  // [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

// Working with rows and columns
for (i, row) in matrix.iter().enumerate() {
    println!("Row {}: {:?}", i, row);
}

// Column iteration (more complex)
for col in 0..3 {
    let column: Vec<i32> = matrix.iter().map(|row| row[col]).collect();
    println!("Column {}: {:?}", col, column);
}

Common Pitfalls

  • Arrays don't have many built-in methods - use slices or iterators
  • Cannot resize arrays - use Vec for dynamic sizing
  • Array conversions require exact size matching
  • Multi-dimensional array operations often require manual loops

Vectors in Rust

Vectors (Vec<T>) are growable arrays that can dynamically resize. They are heap-allocated and one of the most commonly used data structures in Rust.

Creating and Using Vectors

fn main() {
    // Different ways to create vectors
    let numbers: Vec<i32> = Vec::new();           // Empty vector
    let mut primes = vec![2, 3, 5, 7, 11];        // Initialized with macro
    let zeros = vec![0; 5];                       // 5 elements, all 0
    
    // Adding elements
    primes.push(13);                              // Add to end
    primes.insert(2, 4);                          // Insert at index 2
    
    // Accessing elements
    println!("First: {}", primes[0]);             // 2
    println!("Second: {}", primes.get(1).unwrap()); // 3 (safe access)
    
    // Size and capacity
    println!("Length: {}", primes.len());          // 7
    println!("Capacity: {}", primes.capacity());   // Current capacity
    println!("Is empty: {}", primes.is_empty());   // false
    
    // Reserve capacity
    primes.reserve(10);
    println!("New capacity: {}", primes.capacity());
    
    // Remove elements
    let removed = primes.remove(2);               // Remove element at index 2
    let popped = primes.pop();                    // Remove last element
    
    println!("Removed: {}, Popped: {:?}", removed, popped);
    println!("Final vector: {:?}", primes);
}

Vector Operations

let mut vec = vec![1, 2, 3, 4, 5];
    
// Iterating
println!("Elements:");
for i in 0..vec.len() {
    println!("vec[{}] = {}", i, vec[i]);
}
    
// Better: using iterators (borrowed)
println!("Using iterator:");
for element in &vec {
    println!("Element: {}", element);
}
    
// Mutable iteration
for element in &mut vec {
    *element *= 2;  // Double each element
}
println!("Doubled: {:?}", vec);  // [2, 4, 6, 8, 10]
    
// Range-based for loop with values (consumes vector)
// for element in vec { ... }  // This would move vec
    
// Useful vector methods
vec.clear();  // Remove all elements
vec.extend([1, 2, 3].iter().copied());  // Add multiple elements
vec.truncate(2);  // Truncate to first 2 elements
vec.shrink_to_fit();  // Reduce capacity to fit current size
    
println!("After operations: {:?}", vec);

Vector vs Array

// Arrays: Fixed size, stack allocation
let arr = [1, 2, 3, 4, 5];
// arr.push(6);  // Error - fixed size

// Vectors: Dynamic size, heap allocation
let mut vec = vec![1, 2, 3, 4, 5];
vec.push(6);  // OK - can grow
vec.pop();    // Remove last element

// Converting between arrays and vectors
let array = [1, 2, 3];
let vector = array.to_vec();  // Array to Vec
let from_vec: Vec<i32> = vec![4, 5, 6];

// Try to convert Vec to array (must be exact size)
if let Ok(array_back) = from_vec.try_into() {
    let arr: [i32; 3] = array_back;
    println!("Back to array: {:?}", arr);
}

Advanced Vector Usage

// Vector of different types using enums
#[derive(Debug)]
enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let row = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Float(10.12),
    SpreadsheetCell::Text(String::from("blue")),
];
println!("Row: {:?}", row);

// Splitting vectors
let mut numbers = vec![1, 2, 3, 4, 5, 6];
let second_half = numbers.split_off(3);  // numbers now has [1,2,3], second_half has [4,5,6]
println!("First: {:?}, Second: {:?}", numbers, second_half);

// Drain elements (remove range and get iterator)
let mut data = vec![1, 2, 3, 4, 5];
let drained: Vec<i32> = data.drain(1..4).collect();  // data becomes [1,5]
println!("Drained: {:?}, Remaining: {:?}", drained, data);

// Retain elements matching condition
let mut values = vec![1, 2, 3, 4, 5, 6];
values.retain(|&x| x % 2 == 0);  // Keep only even numbers
println!("Even values: {:?}", values);  // [2, 4, 6]

Vector Performance Considerations

// Pre-allocate capacity when size is known
let mut large_vec = Vec::with_capacity(1000);
for i in 0..1000 {
    large_vec.push(i);
}

// This avoids repeated reallocations
println!("Length: {}, Capacity: {}", large_vec.len(), large_vec.capacity());

// Collect from iterator with size hint
let collected: Vec<i32> = (0..1000).collect();
println!("Collected length: {}", collected.len());

// Using into_iter() vs iter()
let vec1 = vec![1, 2, 3];
// This consumes vec1:
// for element in vec1.into_iter() { ... }

// This borrows vec1:
for element in vec1.iter() {
    println!("{}", element);
}
// vec1 is still usable here

Common Pitfalls

  • Accessing elements with [] can panic if index is out of bounds
  • Use get() for bounds-checked access
  • Vectors can be less efficient than arrays for very small, fixed-size data
  • Inserting/erasing in the middle is O(n) operation
  • Forgetting that some operations consume the vector

References in Rust

References are non-owning pointers that allow you to access data without taking ownership. Rust's reference system is central to its memory safety guarantees.

Basic Reference Operations

fn main() {
    let number = 42;
    let number_ref = &number;  // Immutable reference
    
    println!("Value: {}", number);
    println!("Address: {:p}", number_ref);
    println!("Value through reference: {}", *number_ref);  // Dereferencing
    
    // Mutable references
    let mut mutable_number = 100;
    let mutable_ref = &mut mutable_number;
    
    *mutable_ref += 50;  // Modify through mutable reference
    println!("Modified value: {}", mutable_number);  // 150
    
    // References in functions
    let text = String::from("Hello");
    let length = calculate_length(&text);  // Pass reference, not ownership
    println!("Length of '{}': {}", text, length);  // text still usable here
}

fn calculate_length(s: &String) -> usize {
    s.len()
}  // s goes out of scope, but since it's a reference, nothing is dropped

Reference Rules and Limitations

let mut data = String::from("Hello");
    
// Multiple immutable references are allowed
let ref1 = &data;
let ref2 = &data;
println!("{}, {}", ref1, ref2);  // Both can be used
    
// But only one mutable reference at a time
let mut_ref1 = &mut data;
// let mut_ref2 = &mut data;  // This would cause compile error!
println!("{}", mut_ref1);
    
// Cannot mix mutable and immutable references
// let immutable_ref = &data;        // This would error
// let mutable_ref = &mut data;      // if this exists
    
// References must always be valid
fn dangling_reference_example() {
    // This function would not compile:
    // let reference = dangle();  // Error: returns reference to dropped value
}
    
// fn dangle() -> &String {
//     let s = String::from("hello");
//     &s  // s is dropped here, so reference would be invalid
// }

Working with References in Data Structures

// Struct with references require lifetimes
#[derive(Debug)]
struct Book<'a> {
    title: &'a str,
    author: &'a str,
}

fn main() {
    let title = "The Rust Programming Language";
    let author = "Steve Klabnik and Carol Nichols";
    
    let book = Book { title, author };
    println!("Book: {:?}", book);
    
    // References in collections
    let strings = vec!["hello", "world"];
    let string_refs: Vec<&str> = strings.iter().map(|s| *s).collect();
    println!("String refs: {:?}", string_refs);
    
    // Reference to slice
    let array = [1, 2, 3, 4, 5];
    let slice_ref = &array[1..4];  // Reference to part of array
    println!("Slice: {:?}", slice_ref);  // [2, 3, 4]
}

Reference Patterns and Destructuring

let point = (10, 20);
    
// Pattern matching with references
match &point {
    (x, y) => println!("Point at ({}, {})", x, y),  // x and y are &i32
}
    
// Destructuring references
let &(x, y) = &point;
println!("x: {}, y: {}", x, y);
    
// Reference patterns in function parameters
fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}
    
print_coordinates(&point);
    
// Using ref in patterns (less common in modern Rust)
let maybe_value = Some(42);
if let Some(ref value) = maybe_value {
    println!("Got a reference to: {}", value);  // value is &i32
}

Common Pitfalls

  • Trying to create multiple mutable references to the same data
  • Mixing mutable and immutable references
  • Returning references to local variables (dangling references)
  • Forgetting that references have lifetime constraints
  • Confusing &T with &mut T in function signatures

Borrowing in Rust

Borrowing is Rust's mechanism for allowing temporary access to data without transferring ownership. The borrow checker enforces rules at compile time to prevent data races and memory errors.

Borrowing Rules

fn main() {
    let mut s = String::from("hello");
    
    // Rule 1: Any number of immutable borrows
    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);
    // r1 and r2 are no longer used after this point
    
    // Rule 2: Only one mutable borrow in scope
    let r3 = &mut s;
    // println!("{}", r1);  // This would error - can't use r1 while r3 exists
    r3.push_str(", world");
    println!("{}", r3);
    
    // Rule 3: Cannot mix mutable and immutable borrows
    // let r4 = &s;        // This would error
    // let r5 = &mut s;    // if both exist
    
    demonstrate_scope();
}

fn demonstrate_scope() {
    let mut data = String::from("test");
    
    {
        let borrow1 = &data;      // First immutable borrow
        println!("First borrow: {}", borrow1);
    } // borrow1 goes out of scope here
    
    // Now we can borrow mutably because no other references exist
    let borrow2 = &mut data;
    borrow2.push_str(" modified");
    println!("After mutable borrow: {}", borrow2);
}

Function Parameters and Borrowing

// Functions that borrow vs take ownership
fn take_ownership(s: String) -> String {
    println!("I own: {}", s);
    s  // Return ownership
}

fn borrow_readonly(s: &String) {
    println!("I can read: {}", s);
    // s.push_str("!");  // Error - s is immutable reference
}

fn borrow_mutable(s: &mut String) {
    s.push_str("!");
    println!("I modified: {}", s);
}

fn main() {
    let mut text = String::from("Hello");
    
    borrow_readonly(&text);           // Immutable borrow
    borrow_mutable(&mut text);        // Mutable borrow
    println!("After functions: {}", text);  // text still owned here
    
    let returned = take_ownership(text);  // Ownership transferred
    // println!("{}", text);  // Error - text was moved
    println!("Returned: {}", returned);
}

Borrowing with Collections

fn main() {
    let mut vec = vec![1, 2, 3, 4, 5];
    
    // Borrowing elements from vector
    let first = &vec[0];
    // vec.push(6);  // This would error - can't mutate while borrowed
    println!("First element: {}", first);
    
    // After first is no longer used, we can modify
    vec.push(6);
    println!("Vector: {:?}", vec);
    
    // Iterators and borrowing
    for element in &vec {  // Immutable borrow
        println!("Element: {}", element);
    }
    
    // Mutable iteration
    for element in &mut vec {  // Mutable borrow
        *element *= 2;
    }
    println!("Doubled: {:?}", vec);
    
    // Using iter() vs into_iter()
    let doubled: Vec<i32> = vec.iter().map(|x| x * 2).collect();  // vec still usable
    // let consumed: Vec<i32> = vec.into_iter().collect();  // vec consumed
}

Lifetimes and Borrowing

// Simple case - compiler can infer lifetimes
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

// Explicit lifetime annotation needed for structs
struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    let i = ImportantExcerpt {
        part: first_sentence,
    };
    println!("Excerpt: {}", i.part);
}

Common Borrowing Patterns

// Returning references from functions
fn get_first_char(s: &String) -> Option<char> {
    s.chars().next()
}

// Working with Option<&T>
fn find_number(numbers: &[i32], target: i32) -> Option<&i32> {
    numbers.iter().find(|&&x| x == target)
}

// Borrowing in match expressions
fn process_option(opt: &Option<String>) {
    match opt {
        Some(s) => println!("Got: {}", s),  // s is &String
        None => println!("Got nothing"),
    }
}

// Using RefCell for interior mutability (advanced)
use std::cell::RefCell;

let data = RefCell::new(42);
{
    let mut borrow = data.borrow_mut();
    *borrow += 1;
}
println!("Data: {}", *data.borrow());

Common Pitfalls

  • Trying to mutate a borrowed value without a mutable reference
  • Creating dangling references by returning references to local data
  • Forgetting that borrowed data cannot be moved while borrowed
  • Mixing different kinds of borrows in the same scope
  • Not understanding lifetime elision rules

Variables in Rust

Variables in Rust are immutable by default and have move semantics. Understanding variable binding, mutability, and ownership is crucial for writing correct Rust code.

Variable Declaration and Mutability

fn main() {
    // Immutable by default
    let x = 5;
    // x = 6;  // Error: cannot assign twice to immutable variable
    
    // Mutable variables
    let mut y = 10;
    y = 15;  // This is allowed
    println!("y: {}", y);
    
    // Variable shadowing (different from mutability)
    let z = 5;
    let z = z + 1;      // New variable with same name
    let z = z * 2;      // Another new variable
    println!("z: {}", z);  // 12
    
    // Shadowing with different type
    let spaces = "   ";
    let spaces = spaces.len();  // This is allowed with shadowing
    println!("Spaces: {}", spaces);
    
    // Constants (must have type annotation)
    const MAX_POINTS: u32 = 100_000;
    println!("Max points: {}", MAX_POINTS);
}

Variable Scope and Ownership

fn main() {
    // Variable comes into scope
    let s = String::from("hello");  // s is valid from this point forward
    
    takes_ownership(s);             // s's value moves into the function...
                                    // ... and is no longer valid here
    
    let x = 5;                      // x comes into scope
    
    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it's okay to still
                                    // use x afterward
    
    // println!("{}", s);           // This would error - s was moved
    println!("x: {}", x);           // This works - x is Copy
    
} // Here, x goes out of scope, then s. But since s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

Pattern Matching and Destructuring

// Destructuring tuples
let point = (10, 20);
let (x, y) = point;
println!("x: {}, y: {}", x, y);

// Destructuring structs
struct Point {
    x: i32,
    y: i32,
}

let point = Point { x: 10, y: 20 };
let Point { x: a, y: b } = point;
println!("a: {}, b: {}", a, b);

// Shorthand destructuring (same variable names)
let Point { x, y } = point;
println!("x: {}, y: {}", x, y);

// Pattern matching in let
let some_option = Some(5);
if let Some(value) = some_option {
    println!("Got a value: {}", value);
}

// Multiple patterns
let number = 7;
match number {
    1 => println!("One"),
    2 | 3 | 5 | 7 => println!("Prime"),
    _ => println!("Other"),
}

Type Inference and Annotations

// Rust has excellent type inference
let x = 5;              // Compiler infers i32
let y = 3.14;           // Compiler infers f64
let z = "hello";        // Compiler infers &str

// Sometimes explicit types are needed
let collected: Vec<i32> = (0..5).collect();
let parsed: i32 = "42".parse().unwrap();

// Turbofish syntax for explicit types
let numbers = (0..5).collect::<Vec<i32>>();
let float_vec = vec![1.0, 2.0, 3.0f64];

// Type aliases
type Kilometers = i32;
let distance: Kilometers = 100;
println!("Distance: {} km", distance);

Advanced Variable Patterns

// Variables in different contexts
fn main() {
    // @ bindings in patterns
    let value = Some(42);
    match value {
        Some(x @ 1..=100) => println!("Got small number: {}", x),
        Some(x) => println!("Got large number: {}", x),
        None => println!("Got nothing"),
    }
    
    // Ignoring parts of patterns
    let (x, _, z) = (1, 2, 3);
    println!("x: {}, z: {}", x, z);
    
    // .. to ignore remaining parts
    let numbers = (1, 2, 3, 4, 5);
    match numbers {
        (first, .., last) => println!("First: {}, Last: {}", first, last),
    }
    
    // ref patterns (less common in modern Rust)
    let maybe_string = Some(String::from("hello"));
    if let Some(ref s) = maybe_string {
        println!("s is a reference to: {}", s);  // s is &String
    }
    // maybe_string is still usable here
    
    // ref mut for mutable references
    let mut maybe_num = Some(42);
    if let Some(ref mut n) = maybe_num {
        *n += 1;
    }
    println!("Maybe num: {:?}", maybe_num);  // Some(43)
}

Common Pitfalls

  • Forgetting that variables are immutable by default
  • Confusing variable shadowing with mutability
  • Trying to use moved values after ownership transfer
  • Not understanding the difference between Copy and Move types
  • Forgetting that pattern matching can move values

Conditional Statements: if, else if, else

Conditional statements allow your program to make decisions and execute different code blocks based on conditions. Rust's if expressions are more powerful than in many languages.

Basic if Statement

fn main() {
    let number = 7;
    
    // Simple if statement
    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
    
    // if as an expression
    let condition = true;
    let result = if condition { 5 } else { 6 };
    println!("The value of result is: {}", result);
    
    // Each arm must return the same type
    // This would error:
    // let invalid = if condition { 5 } else { "six" };
}

if-else if-else Chain

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

// Using if expressions for assignment
let grade = if score >= 90 {
    'A'
} else if score >= 80 {
    'B'
} else if score >= 70 {
    'C'
} else if score >= 60 {
    'D'
} else {
    'F'
};
println!("Your grade is: {}", grade);

Pattern Matching with if let

// if let for concise pattern matching
let some_value: Option<i32> = Some(42);
    
// Traditional match
match some_value {
    Some(42) => println!("The answer to everything!"),
    Some(x) => println!("Got some other value: {}", x),
    None => println!("Got nothing"),
}
    
// Equivalent with if let
if let Some(42) = some_value {
    println!("The answer to everything!");
} else if let Some(x) = some_value {
    println!("Got some other value: {}", x);
} else {
    println!("Got nothing");
}
    
// Combining conditions with patterns
let config_max = Some(3u8);
if let Some(max) = config_max && max > 5 {
    println!("The maximum is configured to be {}", max);
} else {
    println!("Maximum not configured or too small");
}

Complex Conditions

// Multiple conditions
let age = 25;
let has_license = true;
    
if age >= 18 && has_license {
    println!("You can drive");
} else if age >= 18 && !has_license {
    println!("You need to get a license first");
} else {
    println!("You are too young to drive");
}
    
// Nested if statements
let number = 15;
    
if number % 2 == 0 {
    println!("Number is even");
    if number % 4 == 0 {
        println!("Number is divisible by 4");
    } else {
        println!("Number is not divisible by 4");
    }
} else {
    println!("Number is odd");
    if number % 3 == 0 {
        println!("Number is divisible by 3");
    }
}
    
// Using boolean expressions directly
let is_even = number % 2 == 0;
if is_even {
    println!("The number is even");
}

Conditional with let-else

// let-else for pattern matching that must succeed
fn process_user_input(input: &str) {
    let Ok(number) = input.parse::<i32>() else {
        println!("Please input a valid number!");
        return;
    };
    
    println!("You entered: {}", number);
}
    
// This is equivalent to:
fn process_user_input_alt(input: &str) {
    let number = match input.parse::<i32>() {
        Ok(n) => n,
        Err(_) => {
            println!("Please input a valid number!");
            return;
        }
    };
    
    println!("You entered: {}", number);
}

Common Pitfalls

  • Forgetting that if conditions must be bool (no truthy/falsy values)
  • Mismatched types in if expression arms
  • Using assignment = instead of comparison == in conditions
  • Overusing nested if statements when match would be clearer
  • Forgetting that patterns in if let can move values

for Loop in Rust

The for loop is the most common loop in Rust, used to iterate over collections, ranges, and any type that implements the Iterator trait.

Basic for Loop

fn main() {
    // Iterate over a range
    for number in 1..=5 {
        println!("Count: {}", number);
    }
    
    // Iterate over array or vector
    let numbers = [10, 20, 30, 40, 50];
    for number in numbers {
        println!("Number: {}", number);
    }
    
    // Iterate with reference (doesn't consume collection)
    let names = vec!["Alice", "Bob", "Charlie"];
    for name in &names {
        println!("Name: {}", name);
    }
    // names is still usable here
    
    // Iterate with mutable reference
    let mut scores = vec![85, 92, 78];
    for score in &mut scores {
        *score += 5;  // Add 5 to each score
    }
    println!("Updated scores: {:?}", scores);
}

Advanced for Loop Usage

// Using enumerate to get index
let fruits = ["apple", "banana", "cherry"];
for (index, fruit) in fruits.iter().enumerate() {
    println!("Fruit {}: {}", index, fruit);
}

// Iterating over characters in a string
let text = "Hello";
for c in text.chars() {
    println!("Character: {}", c);
}

// Iterating over bytes in a string
for byte in text.bytes() {
    println!("Byte: {}", byte);
}

// Using ranges with steps
for i in (0..10).step_by(2) {
    println!("Even: {}", i);  // 0, 2, 4, 6, 8
}

// Reverse iteration
for i in (0..5).rev() {
    println!("Countdown: {}", i);  // 4, 3, 2, 1, 0
}

// Iterating over HashMap
use std::collections::HashMap;

let mut map = HashMap::new();
map.insert("red", 1);
map.insert("green", 2);
map.insert("blue", 3);

for (key, value) in &map {
    println!("{}: {}", key, value);
}

Nested for Loops

// Multiplication table
for i in 1..=5 {
    for j in 1..=5 {
        print!("{}\t", i * j);
    }
    println!();
}

// Pattern printing
for i in 1..=5 {
    for _ in 1..=i {
        print!("*");
    }
    println!();
}

// Iterating over 2D array
let matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

for row in matrix.iter() {
    for element in row.iter() {
        print!("{} ", element);
    }
    println!();
}

Custom Iterators with for

// Any type that implements IntoIterator can be used in for loops
struct Counter {
    count: u32,
    max: u32,
}

impl Counter {
    fn new(max: u32) -> Counter {
        Counter { count: 0, max }
    }
}

impl Iterator for Counter {
    type Item = u32;
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.count < self.max {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

// Now we can use Counter in for loops
for number in Counter::new(5) {
    println!("Counter: {}", number);  // 1, 2, 3, 4, 5
}

// Using iterator adapters
let result: Vec<i32> = (1..=10)
    .filter(|x| x % 2 == 0)
    .map(|x| x * x)
    .collect();

for value in result {
    println!("Processed: {}", value);  // 4, 16, 36, 64, 100
}

Common Pitfalls

  • Using for with types that don't implement Iterator
  • Accidentally consuming collections with into_iter()
  • Modifying a collection while iterating over it
  • Forgetting that range upper bound is exclusive: 1..5 gives 1,2,3,4
  • Using wrong iterator method (iter() vs into_iter() vs iter_mut())

while Loop in Rust

The while loop repeats a block of code as long as a condition is true. It's useful when you don't know in advance how many iterations are needed.

Basic while Loop

fn main() {
    // Count from 1 to 5
    let mut i = 1;
    while i <= 5 {
        println!("Count: {}", i);
        i += 1;
    }
    
    // User input validation
    let mut input = String::new();
    println!("Enter a positive number: ");
    
    // This is simplified - real input handling would be more complex
    while input.trim().parse::<i32>().unwrap_or(0) <= 0 {
        input.clear();
        println!("Invalid input. Enter a positive number: ");
        // In real code, you would read from stdin here
        input = "42".to_string(); // Simulating valid input
    }
    println!("Thank you! You entered: {}", input);
}

while let Pattern

// while let for pattern matching that might fail
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);

// Pop elements until empty
while let Some(top) = stack.pop() {
    println!("Popped: {}", top);
}

// Processing streams of data
let mut data = vec![Some(1), Some(2), None, Some(4)];
let mut iter = data.iter_mut();

while let Some(Some(value)) = iter.next() {
    *value *= 2;
}
println!("Processed: {:?}", data);  // [Some(2), Some(4), None, Some(8)]

// Reading until sentinel value (conceptual)
let mut values = vec![1, 2, 3, 0, 4, 5];
let mut sum = 0;
let mut index = 0;

while index < values.len() && values[index] != 0 {
    sum += values[index];
    index += 1;
}
println!("Sum until zero: {}", sum);  // 6

Practical while Loop Examples

// Game loop example
fn game_loop() {
    let mut game_over = false;
    let mut score = 0;
    
    while !game_over {
        // Simulate game round
        score += 10;
        println!("Score: {}", score);
        
        // Check win condition
        if score >= 100 {
            game_over = true;
            println!("You win!");
        }
        
        // In real game, this would have some condition
        if score > 50 {
            // Simulating random game over
            game_over = true;
            println!("Game over!");
        }
    }
}

// Processing until condition
fn find_first_negative(numbers: &[i32]) -> Option<usize> {
    let mut index = 0;
    while index < numbers.len() {
        if numbers[index] < 0 {
            return Some(index);
        }
        index += 1;
    }
    None
}

let data = [1, 2, -3, 4, 5];
if let Some(pos) = find_first_negative(&data) {
    println!("First negative at position: {}", pos);
}

// Infinite loop with break (often better than while true)
let mut counter = 0;
loop {
    counter += 1;
    if counter > 5 {
        break;
    }
    println!("Counter: {}", counter);
}

Loop Control in while

let mut x = 0;
    
// Using continue
while x < 10 {
    x += 1;
    if x % 2 == 0 {
        continue;  // Skip even numbers
    }
    println!("Odd: {}", x);  // 1, 3, 5, 7, 9
}
    
// Using break with value
let mut attempt = 0;
let result = loop {
    attempt += 1;
    if attempt > 3 {
        break "failed";
    }
    // Simulate some operation that might succeed
    if attempt == 2 {
        break "success";
    }
};
println!("Result: {}", result);  // success
    
// Nested loops with labels
let mut i = 0;
'outer: while i < 3 {
    let mut j = 0;
    while j < 3 {
        if i * j > 4 {
            println!("Breaking both loops at i={}, j={}", i, j);
            break 'outer;
        }
        println!("i={}, j={}", i, j);
        j += 1;
    }
    i += 1;
}

Common Pitfalls

  • Infinite loops when condition never becomes false
  • Forgetting to update the loop control variable
  • Using while true instead of loop for infinite loops
  • Modifying data being iterated over (can cause bugs or panics)
  • Off-by-one errors in loop conditions

Loop Control Statements

Rust provides loop control statements to alter the normal flow of loops: break, continue, and loop labels. These work with all loop types (loop, while, for).

break and continue

fn main() {
    // break exits the loop immediately
    for i in 1..=10 {
        if i == 5 {
            break;  // Exit loop when i reaches 5
        }
        println!("{}", i);
    }
    // Output: 1 2 3 4
    
    // continue skips the rest of current iteration
    for i in 1..=10 {
        if i % 2 == 0 {
            continue;  // Skip even numbers
        }
        println!("{}", i);  // Only odd numbers printed
    }
    // Output: 1 3 5 7 9
    
    // break with value (only in loop, not while or for)
    let result = loop {
        let number = 7;
        if number > 5 {
            break number * 2;  // Break with value
        }
    };
    println!("The result is {}", result);  // 14
}

Loop Labels

// Labeling loops for control
'outer: for i in 1..=3 {
    'inner: for j in 1..=3 {
        if i * j > 4 {
            println!("Breaking outer loop at i={}, j={}", i, j);
            break 'outer;  // Break both loops
        }
        println!("i={}, j={}", i, j);
    }
}
/* Output:
i=1, j=1
i=1, j=2
i=1, j=3
i=2, j=1
i=2, j=2
Breaking outer loop at i=2, j=3
*/

// Continue with labels
'outer: for i in 1..=3 {
    for j in 1..=3 {
        if i == 2 && j == 2 {
            println!("Skipping i=2, j=2");
            continue 'outer;  // Continue outer loop
        }
        println!("i={}, j={}", i, j);
    }
}
/* Output:
i=1, j=1
i=1, j=2
i=1, j=3
i=2, j=1
Skipping i=2, j=2
i=3, j=1
i=3, j=2
i=3, j=3
*/

Practical Loop Control Examples

// Search example with break
fn find_number(numbers: &[i32], target: i32) -> Option<usize> {
    for (index, &number) in numbers.iter().enumerate() {
        if number == target {
            return Some(index);  // Early return works like break
        }
    }
    None
}

let data = [10, 20, 30, 40, 50];
if let Some(pos) = find_number(&data, 30) {
    println!("Found at position: {}", pos);
}

// Input validation with continue
let mut valid_inputs = 0;
for attempt in 1..=5 {
    // Simulate input - some are invalid
    let input = if attempt % 2 == 0 { "42" } else { "invalid" };
    
    if input.parse::<i32>().is_err() {
        println!("Attempt {}: Invalid input, skipping...", attempt);
        continue;
    }
    
    valid_inputs += 1;
    println!("Attempt {}: Valid input received", attempt);
}
println!("Total valid inputs: {}", valid_inputs);

// Complex condition with break and continue
let mut count = 0;
for i in 1..=100 {
    if i % 3 == 0 && i % 5 == 0 {
        println!("FizzBuzz");
        continue;
    }
    if i % 3 == 0 {
        println!("Fizz");
        continue;
    }
    if i % 5 == 0 {
        println!("Buzz");
        continue;
    }
    
    count += 1;
    if count > 10 {
        println!("Reached limit of 10 numbers");
        break;
    }
    println!("{}", i);
}

Loop Control in Different Contexts

// In while loops
let mut x = 0;
while x < 10 {
    x += 1;
    if x == 5 {
        continue;  // Skip the rest when x is 5
    }
    if x == 8 {
        break;     // Exit when x is 8
    }
    println!("x = {}", x);
}
// Output: 1 2 3 4 6 7

// In nested loops without labels
for i in 1..=3 {
    for j in 1..=3 {
        if i * j > 4 {
            println!("Breaking inner loop at i={}, j={}", i, j);
            break;  // Only breaks inner loop
        }
        println!("i={}, j={}", i, j);
    }
}
/* Output:
i=1, j=1
i=1, j=2
i=1, j=3
i=2, j=1
i=2, j=2
Breaking inner loop at i=2, j=3
i=3, j=1
Breaking inner loop at i=3, j=2
*/

// Using return instead of break for function exit
fn process_data(data: &[i32]) -> Option<i32> {
    for &value in data {
        if value < 0 {
            println!("Negative value found, aborting");
            return None;  // Exit entire function
        }
        if value == 0 {
            println!("Zero found, skipping");
            continue;     // Continue to next iteration
        }
        println!("Processing: {}", value);
    }
    Some(42)  // Success case
}

Common Pitfalls

  • Using break when continue is more appropriate
  • Forgetting that break with value only works in loop
  • Overusing loop labels can make code harder to read
  • break without labels only exits the innermost loop
  • Placing code after continue that will never execute

Nested Loops in Rust

Nested loops are loops within loops. They are essential for working with multi-dimensional data, matrices, and complex patterns.

Basic Nested Loops

fn main() {
    // Simple nested loop
    for i in 1..=3 {
        for j in 1..=3 {
            println!("({}, {})", i, j);
        }
    }
    /* Output:
    (1,1) (1,2) (1,3) 
    (2,1) (2,2) (2,3) 
    (3,1) (3,2) (3,3) */
    
    // Multiplication table
    println!("\nMultiplication Table:");
    for i in 1..=5 {
        for j in 1..=5 {
            print!("{}\t", i * j);
        }
        println!();
    }
}

Pattern Printing with Nested Loops

let rows = 5;
    
// Right triangle
for i in 1..=rows {
    for _ in 1..=i {
        print!("*");
    }
    println!();
}
/* Output:
*
**
***
****
*****
*/
    
// Inverted triangle
for i in (1..=rows).rev() {
    for _ in 1..=i {
        print!("*");
    }
    println!();
}
/* Output:
*****
****
***
**
*
*/
    
// Pyramid
for i in 1..=rows {
    // Print spaces
    for _ in 1..=(rows - i) {
        print!(" ");
    }
    // Print stars
    for _ in 1..=(2 * i - 1) {
        print!("*");
    }
    println!();
}
/* Output:
    *
   ***
  *****
 *******
*********
*/

Working with 2D Arrays

// Matrix operations
const ROWS: usize = 3;
const COLS: usize = 3;
let matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

// Print matrix
println!("Matrix:");
for i in 0..ROWS {
    for j in 0..COLS {
        print!("{} ", matrix[i][j]);
    }
    println!();
}

// Sum of all elements
let mut sum = 0;
for i in 0..ROWS {
    for j in 0..COLS {
        sum += matrix[i][j];
    }
}
println!("Sum of all elements: {}", sum);

// Transpose matrix
println!("Transpose:");
for j in 0..COLS {
    for i in 0..ROWS {
        print!("{} ", matrix[i][j]);
    }
    println!();
}

// Element-wise operations
let mut result = [[0; COLS]; ROWS];
for i in 0..ROWS {
    for j in 0..COLS {
        result[i][j] = matrix[i][j] * 2;
    }
}
println!("Doubled matrix: {:?}", result);

Mixed Loop Types

// while inside for
for i in 1..=3 {
    let mut j = 1;
    while j <= 3 {
        println!("i={}, j={}", i, j);
        j += 1;
    }
}

// loop inside for
for i in 1..=2 {
    let mut j = 1;
    loop {
        println!("i={}, j={}", i, j);
        j += 1;
        if j > 2 {
            break;
        }
    }
}

// Using different loop controls in nested loops
'outer: for i in 1..=3 {
    for j in 1..=3 {
        if i == 2 && j == 2 {
            println!("Skipping i=2, j=2");
            continue 'outer;  // Continue to next i
        }
        if i * j > 6 {
            println!("Product too large at i={}, j={}", i, j);
            break 'outer;  // Break both loops
        }
        println!("i={}, j={}, product={}", i, j, i * j);
    }
}

Performance Considerations

// Cache-friendly iteration (row-major order)
let mut matrix = [[0; 1000]; 1000];
    
// Good: Iterate row by row (cache-friendly)
for i in 0..1000 {
    for j in 0..1000 {
        matrix[i][j] = i + j;
    }
}
    
// Bad: Iterate column by column (cache-unfriendly)
// for j in 0..1000 {
//     for i in 0..1000 {
//         matrix[i][j] = i + j;
//     }
// }
    
// Using iterators for nested loops (often more efficient)
let data = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
    
let flat: Vec<i32> = data.iter().flatten().cloned().collect();
println!("Flattened: {:?}", flat);
    
let sum: i32 = data.iter().flatten().sum();
println!("Total sum: {}", sum);
    
// Using enumerate for indices in nested loops
for (i, row) in data.iter().enumerate() {
    for (j, &value) in row.iter().enumerate() {
        println!("data[{}][{}] = {}", i, j, value);
    }
}

Common Pitfalls

  • O(n²) time complexity can be inefficient for large data
  • Using wrong loop variables in inner vs outer loops
  • Cache-unfriendly access patterns in multi-dimensional arrays
  • Excessive nesting reduces readability (try to keep to 2-3 levels)
  • Forgetting to break/continue the correct loop when using labels

Functions in Rust

Functions are reusable blocks of code that perform specific tasks. Rust functions are first-class citizens and support features like pattern matching, multiple return values, and closures.

Function Definition and Usage

// Function declaration
fn greet() {
    println!("Hello, world!");
}

// Function with parameters and return type
fn add_numbers(a: i32, b: i32) -> i32 {
    a + b  // No semicolon - this is an expression, not a statement
}

// Function with explicit return
fn subtract_numbers(a: i32, b: i32) -> i32 {
    return a - b;  // Explicit return with semicolon
}

// Function with multiple parameters
fn print_info(name: &str, age: u32) {
    println!("{} is {} years old", name, age);
}

fn main() {
    greet();
    
    let sum = add_numbers(5, 3);
    println!("5 + 3 = {}", sum);
    
    let difference = subtract_numbers(10, 4);
    println!("10 - 4 = {}", difference);
    
    print_info("Alice", 30);
}

Function Parameters and Ownership

// Pass by value (takes ownership)
fn take_ownership(s: String) {
    println!("I own: {}", s);
} // s is dropped here

// Pass by immutable reference (borrows)
fn borrow_immutable(s: &String) -> usize {
    s.len()
} // s is not dropped - it's just a reference

// Pass by mutable reference (mutably borrows)
fn borrow_mutable(s: &mut String) {
    s.push_str(" world");
}

fn main() {
    let s1 = String::from("hello");
    
    // take_ownership(s1);  // s1 is moved, can't use it after
    // println!("{}", s1);  // This would error
    
    let len = borrow_immutable(&s1);  // s1 is still owned here
    println!("Length: {}, s1: {}", len, s1);
    
    let mut s2 = String::from("hello");
    borrow_mutable(&mut s2);
    println!("After mutation: {}", s2);
}

Returning Values from Functions

// Returning multiple values using tuples
fn calculate_stats(numbers: &[i32]) -> (i32, i32, f64) {
    let sum: i32 = numbers.iter().sum();
    let count = numbers.len() as i32;
    let average = sum as f64 / count as f64;
    
    (sum, count, average)  // Return tuple
}

// Returning Option for functions that might fail
fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

// Returning Result for error handling
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse()
}

// Early return with condition
fn find_first_even(numbers: &[i32]) -> Option<usize> {
    for (i, &num) in numbers.iter().enumerate() {
        if num % 2 == 0 {
            return Some(i);  // Early return
        }
    }
    None  // Return None if no even numbers found
}

fn main() {
    let data = [1, 2, 3, 4, 5];
    let (sum, count, avg) = calculate_stats(&data);
    println!("Sum: {}, Count: {}, Average: {:.2}", sum, count, avg);
    
    match divide(10.0, 2.0) {
        Some(result) => println!("Division result: {}", result),
        None => println!("Cannot divide by zero"),
    }
    
    match parse_number("42") {
        Ok(num) => println!("Parsed number: {}", num),
        Err(e) => println!("Parse error: {}", e),
    }
}

Advanced Function Features

// Generic functions
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    
    largest
}

// Function pointers
fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(f(arg))
}

// Closures (anonymous functions)
let closure = |x: i32| -> i32 { x * 2 };
let closure_inferred = |x| x * 2;  // Type inference

// Higher-order functions
fn apply_to_numbers(numbers: &[i32], f: fn(i32) -> i32) -> Vec<i32> {
    numbers.iter().map(|&x| f(x)).collect()
}

fn main() {
    let numbers = [1, 2, 3, 4, 5];
    let biggest = largest(&numbers);
    println!("Largest number: {}", biggest);
    
    let result = do_twice(add_one, 5);
    println!("Result: {}", result);  // 7
    
    let doubled = apply_to_numbers(&numbers, |x| x * 2);
    println!("Doubled: {:?}", doubled);  // [2, 4, 6, 8, 10]
}

Method Syntax and Associated Functions

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // Associated function (like static method)
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
    
    // Method - takes &self
    fn area(&self) -> u32 {
        self.width * self.height
    }
    
    // Method - takes &mut self
    fn scale(&mut self, factor: u32) {
        self.width *= factor;
        self.height *= factor;
    }
    
    // Method - takes self (consumes)
    fn into_tuple(self) -> (u32, u32) {
        (self.width, self.height)
    }
}

fn main() {
    let square = Rectangle::square(10);
    println!("Square area: {}", square.area());
    
    let mut rect = Rectangle { width: 30, height: 50 };
    println!("Area: {}", rect.area());
    
    rect.scale(2);
    println!("Scaled area: {}", rect.area());
    
    let (w, h) = rect.into_tuple();
    println!("Dimensions: {}x{}", w, h);
}

Common Pitfalls

  • Forgetting semicolon in functions that return ()
  • Mixing up when to use &, &mut, or take ownership
  • Returning references to local variables
  • Not handling all cases in functions returning Option/Result
  • Confusing associated functions with methods

Modules in Rust

Modules help organize code into logical units and control privacy. Rust's module system includes modules, crates, and workspaces for large-scale code organization.

Basic Module Structure

// Declare a module
mod math {
    // Public function
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }
    
    // Private function (default)
    fn subtract(a: i32, b: i32) -> i32 {
        a - b
    }
    
    // Nested module
    pub mod advanced {
        pub fn multiply(a: i32, b: i32) -> i32 {
            a * b
        }
    }
}

fn main() {
    // Use module functions
    let sum = math::add(5, 3);
    println!("5 + 3 = {}", sum);
    
    let product = math::advanced::multiply(4, 5);
    println!("4 * 5 = {}", product);
    
    // This would error - subtract is private:
    // math::subtract(10, 5);
}

Module Files and Directories

// File structure:
// src/
//   main.rs
//   math.rs
//   math/
//     advanced.rs

// In main.rs:
mod math;  // This tells Rust to look for math.rs or math/mod.rs

fn main() {
    math::add(5, 3);
    math::advanced::multiply(4, 5);
}

// In math.rs:
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub mod advanced;  // Declare advanced module

// In math/advanced.rs:
pub fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

pub fn divide(a: i32, b: i32) -> Option<i32> {
    if b == 0 {
        None
    } else {
        Some(a / b)
    }
}

Use Declarations and Imports

mod math {
    pub mod basic {
        pub fn add(a: i32, b: i32) -> i32 {
            a + b
        }
    }
    
    pub mod advanced {
        pub fn multiply(a: i32, b: i32) -> i32 {
            a * b
        }
    }
}

// Bring modules into scope
use math::basic::add;
use math::advanced::multiply;

// Rename imports
use math::advanced::multiply as mult;

// Import multiple items
use math::basic::{add, /* subtract */};  // subtract would be private

// Import all public items (use sparingly)
// use math::advanced::*;

fn main() {
    // Now we can use the functions directly
    let sum = add(5, 3);
    let product = multiply(4, 5);
    let also_product = mult(4, 5);
    
    println!("Sum: {}, Product: {}, Also: {}", sum, product, also_product);
}

Privacy and Visibility

mod outer {
    pub mod inner {
        pub fn public_function() {
            println!("This is public");
        }
        
        pub(crate) fn crate_visible() {
            println!("Visible within crate");
        }
        
        fn private_function() {
            println!("This is private");
        }
        
        // Function that uses private function
        pub fn public_calls_private() {
            println!("Public function calling private:");
            private_function();
        }
    }
    
    // Can access sibling module's private items
    pub fn access_inner() {
        inner::private_function();  // This works within same parent module
    }
}

fn main() {
    outer::inner::public_function();
    outer::inner::public_calls_private();
    outer::access_inner();
    
    // These would error:
    // outer::inner::private_function();  // private
    // outer::inner::crate_visible();     // crate-visible, but we're in same crate
}

Advanced Module Patterns

// Prelude pattern for common imports
mod prelude {
    pub use std::format;
    pub use std::string::ToString;
    pub use crate::math::*;
}

// Re-exporting (pub use)
mod math {
    pub mod basic {
        pub fn add(a: i32, b: i32) -> i32 {
            a + b
        }
    }
    
    // Re-export to make add available at math level
    pub use basic::add;
}

// Now we can use math::add directly
use math::add;

// Inline modules with attributes
#[cfg(test)]
mod tests {
    use super::*;  // Import items from parent module
    
    #[test]
    fn test_addition() {
        assert_eq!(add(2, 2), 4);
    }
}

// Module with external code
#[path = "custom_path/math_utils.rs"]
mod math_utils;

fn main() {
    let result = add(5, 3);
    println!("5 + 3 = {}", result);
}

Crate Structure

// Typical crate structure:
// Cargo.toml
// src/
//   lib.rs       // Library crate root
//   main.rs      // Binary crate root
//   math.rs      // Module
//   math/        // Module directory
//     advanced.rs
//   utils/       // Another module
//     mod.rs     // Module declaration file

// In lib.rs:
pub mod math;
pub mod utils;

// Re-export commonly used items
pub use math::basic::add;
pub use math::advanced::{multiply, divide};

// Library API
pub fn compute(a: i32, b: i32) -> i32 {
    multiply(add(a, b), 2)
}

// In main.rs (if binary crate):
use my_crate::compute;  // Assuming crate name is "my_crate"

fn main() {
    let result = compute(3, 4);
    println!("Result: {}", result);  // (3+4)*2 = 14
}

Common Pitfalls

  • Forgetting to make items pub for external use
  • Circular module dependencies
  • Confusing module file structure (mod.rs vs named files)
  • Overusing pub use which can make API unclear
  • Not organizing code into modules as project grows

File Input/Output in Rust

Rust provides comprehensive file I/O operations through the std::fs and std::io modules. File operations return Result types that must be handled.

Basic File Operations

use std::fs;
use std::io::{self, Write};
use std::path::Path;

fn main() -> io::Result<()> {
    // Writing to a file
    fs::write("example.txt", "Hello, File!\nThis is line 2\nNumber: 42")?;
    println!("File written successfully");
    
    // Reading from a file
    let content = fs::read_to_string("example.txt")?;
    println!("File content:\n{}", content);
    
    // Reading as bytes
    let bytes = fs::read("example.txt")?;
    println!("File size: {} bytes", bytes.len());
    
    // Check if file exists
    if Path::new("example.txt").exists() {
        println!("File exists");
    }
    
    Ok(())
}

Working with File Handles

use std::fs::File;
use std::io::{BufReader, BufRead, BufWriter};

fn main() -> io::Result<()> {
    // Open file for writing
    let mut file = File::create("output.txt")?;
    writeln!(&mut file, "Hello, File!")?;
    writeln!(&mut file, "This is line 2")?;
    file.write_all(b"Binary data\n")?;
    
    // Open file for reading
    let file = File::open("output.txt")?;
    let reader = BufReader::new(file);
    
    // Read line by line
    for line in reader.lines() {
        println!("{}", line?);
    }
    
    // Append to file
    let mut file = fs::OpenOptions::new()
        .append(true)
        .open("output.txt")?;
    writeln!(&mut file, "Appended line")?;
    
    Ok(())
}

Different File Opening Modes

use std::fs::OpenOptions;

fn main() -> io::Result<()> {
    // Various file opening modes
    let file1 = File::create("new_file.txt")?;           // Create or truncate
    let file2 = File::open("existing_file.txt")?;        // Read only
    
    // Using OpenOptions for more control
    let file3 = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)     // Create if doesn't exist
        .open("data.txt")?;
    
    let file4 = OpenOptions::new()
        .append(true)
        .open("log.txt")?;  // Append mode
    
    let file5 = OpenOptions::new()
        .write(true)
        .create_new(true)  // Only create if doesn't exist
        .open("brand_new.txt")?;
    
    // Example: Append to log file
    let mut log_file = OpenOptions::new()
        .create(true)
        .append(true)
        .open("application.log")?;
    
    writeln!(&mut log_file, "New log entry")?;
    
    Ok(())
}

Reading Different Data Types

use std::io::{BufRead, BufReader};

fn read_mixed_data() -> io::Result<()> {
    // Write mixed data
    let data = "John Doe 25 85.5\nJane Smith 30 92.0\n";
    fs::write("data.txt", data)?;
    
    // Read and parse mixed data types
    let file = File::open("data.txt")?;
    let reader = BufReader::new(file);
    
    for line in reader.lines() {
        let line = line?;
        let parts: Vec<&str> = line.split_whitespace().collect();
        
        if parts.len() == 4 {
            let first_name = parts[0];
            let last_name = parts[1];
            let age: u32 = parts[2].parse().unwrap_or(0);
            let score: f64 = parts[3].parse().unwrap_or(0.0);
            
            println!("{} {}, Age: {}, Score: {}", 
                     first_name, last_name, age, score);
        }
    }
    
    Ok(())
}

fn read_structured_data() -> io::Result<()> {
    // Using serde for structured data (with serde_json crate)
    // #[derive(Serialize, Deserialize)]
    // struct Person {
    //     name: String,
    //     age: u32,
    //     scores: Vec<f64>,
    // }
    
    // let person = Person { ... };
    // let json = serde_json::to_string(&person)?;
    // fs::write("person.json", json)?;
    
    // let content = fs::read_to_string("person.json")?;
    // let person: Person = serde_json::from_str(&content)?;
    
    Ok(())
}

File System Operations

use std::fs;
use std::path::Path;

fn file_system_operations() -> io::Result<()> {
    // Create directory
    fs::create_dir("my_dir")?;
    fs::create_dir_all("parent/child/grandchild")?;  // Create all directories in path
    
    // Check file metadata
    let metadata = fs::metadata("example.txt")?;
    println!("File size: {} bytes", metadata.len());
    println!("Is file: {}", metadata.is_file());
    println!("Is directory: {}", metadata.is_dir());
    
    // Copy file
    fs::copy("example.txt", "example_copy.txt")?;
    
    // Rename/move file
    fs::rename("example_copy.txt", "renamed.txt")?;
    
    // Remove file
    fs::remove_file("renamed.txt")?;
    
    // Remove directory (must be empty)
    fs::remove_dir("my_dir")?;
    
    // Remove directory and all contents
    fs::remove_dir_all("parent")?;
    
    // List directory contents
    for entry in fs::read_dir(".")? {
        let entry = entry?;
        let path = entry.path();
        if path.is_file() {
            println!("File: {}", path.display());
        } else if path.is_dir() {
            println!("Directory: {}", path.display());
        }
    }
    
    Ok(())
}

Error Handling in File I/O

use std::io;

fn robust_file_operations() -> io::Result<()> {
    // Handling file not found gracefully
    let content = fs::read_to_string("nonexistent.txt")
        .unwrap_or_else(|_| "File not found".to_string());
    println!("Content: {}", content);
    
    // Using match for different error cases
    match fs::read_to_string("important.txt") {
        Ok(content) => println!("File content: {}", content),
        Err(e) if e.kind() == io::ErrorKind::NotFound => {
            println!("File not found, creating default...");
            fs::write("important.txt", "Default content")?;
        }
        Err(e) => return Err(e),  // Propagate other errors
    }
    
    // Using Result combinators
    let result = fs::read_to_string("config.txt")
        .and_then(|content| {
            if content.is_empty() {
                Err(io::Error::new(io::ErrorKind::InvalidData, "Empty file"))
            } else {
                Ok(content)
            }
        });
    
    match result {
        Ok(config) => println!("Config: {}", config),
        Err(e) => println!("Error reading config: {}", e),
    }
    
    Ok(())
}

// Function that returns Result
fn read_config() -> io::Result<String> {
    let content = fs::read_to_string("config.txt")?;
    if content.trim().is_empty() {
        return Err(io::Error::new(io::ErrorKind::InvalidData, "Empty config"));
    }
    Ok(content)
}

Common Pitfalls

  • Not handling I/O errors (using unwrap() instead of proper error handling)
  • Forgetting that file paths are relative to current working directory
  • Not closing files explicitly (Rust does this automatically, but be mindful of scope)
  • Assuming files exist without checking
  • Not using buffered I/O for large files

Standard Library Introduction

Rust's standard library (std) provides essential functionality for Rust programs. It includes types for I/O, collections, concurrency, and many utilities.

Key Standard Library Components

// Commonly used standard library modules
use std::collections::{HashMap, HashSet, VecDeque};
use std::io::{self, Read, Write, BufRead};
use std::fs;
use std::path::Path;
use std::time::{Duration, Instant};
use std::thread;
use std::sync::{Arc, Mutex};

fn main() {
    // Option and Result - fundamental for error handling
    let some_value: Option<i32> = Some(42);
    let none_value: Option<i32> = None;
    
    let ok_result: Result<i32, &str> = Ok(42);
    let err_result: Result<i32, &str> = Err("Something went wrong");
    
    // String and str
    let owned_string = String::from("Hello");
    let string_slice: &str = "World";
    
    // Vectors and arrays
    let vector = vec![1, 2, 3, 4, 5];
    let array = [1, 2, 3, 4, 5];
    
    println!("Standard library types are ready to use!");
}

Essential Traits and Types

use std::fmt;
use std::ops::Add;

// Common traits
#[derive(Debug, Clone, PartialEq, Eq)]  // Automatically implement traits
struct Point {
    x: i32,
    y: i32,
}

// Manual implementation of Display trait
impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

// Implementing operator overloading
impl Add for Point {
    type Output = Point;
    
    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    
    println!("Debug: {:?}", p1);    // Uses Debug trait
    println!("Display: {}", p1);    // Uses Display trait
    println!("Sum: {}", p1 + p2);   // Uses Add trait
    
    // Clone trait
    let p3 = p1.clone();
    println!("Cloned: {:?}", p3);
    
    // PartialEq trait
    println!("Equal: {}", p1 == p3);  // true
}

Common Utility Functions

use std::mem;

fn utility_functions() {
    // Memory utilities
    let x = 42;
    println!("Size of i32: {}", mem::size_of::<i32>());
    println!("Size of String: {}", mem::size_of::<String>());
    
    // Take ownership and return it (moves value)
    let s = String::from("hello");
    let same_s = mem::take(&mut s.clone());  // Note: needs mutable reference
    
    // Replace value
    let mut v = vec![1, 2, 3];
    let old_v = mem::replace(&mut v, vec![4, 5, 6]);
    println!("Old: {:?}, New: {:?}", old_v, v);
    
    // Convert to raw parts
    let s = String::from("hello");
    let (ptr, len, cap) = s.into_raw_parts();
    // Can reconstruct string from raw parts
    let _reconstructed = unsafe { String::from_raw_parts(ptr, len, cap) };
}

// Environment and program information
fn program_info() {
    // Command line arguments
    let args: Vec<String> = std::env::args().collect();
    println!("Program name: {}", args[0]);
    
    // Environment variables
    if let Ok(path) = std::env::var("PATH") {
        println!("PATH: {}", path);
    }
    
    // Current directory
    if let Ok(current_dir) = std::env::current_dir() {
        println!("Current directory: {}", current_dir.display());
    }
}

Error Handling Utilities

use std::error::Error;
use std::fmt;

#[derive(Debug)]
struct CustomError {
    message: String,
}

impl fmt::Display for CustomError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "CustomError: {}", self.message)
    }
}

impl Error for CustomError {}

fn might_fail(should_fail: bool) -> Result<String, CustomError> {
    if should_fail {
        Err(CustomError {
            message: "It failed!".to_string(),
        })
    } else {
        Ok("Success!".to_string())
    }
}

fn error_handling_examples() -> Result<(), Box<dyn Error>> {
    // Using ? operator for error propagation
    let content = std::fs::read_to_string("file.txt")?;
    
    // Converting between error types
    let number = "42".parse::<i32>()
        .map_err(|e| CustomError { message: e.to_string() })?;
    
    // Combinator methods
    let result = might_fail(false)
        .and_then(|s| Ok(s + " And more!"))
        .or_else(|_| Ok("Default value".to_string()));
    
    println!("Result: {}", result?);
    
    Ok(())
}

fn main() -> Result<(), Box<dyn Error>> {
    error_handling_examples()?;
    Ok(())
}

Standard Library Organization

/*
Standard Library Organization:

core/     - No-std components (always available)
alloc/    - Heap allocation types (needs allocator)
std/      - Full standard library

Main modules:
- std::collections  - HashMap, Vec, String, etc.
- std::io           - Input/output operations
- std::fs           - File system operations  
- std::path         - Path manipulation
- std::env          - Environment variables
- std::process      - Process management
- std::thread       - Concurrency
- std::sync         - Synchronization primitives
- std::time         - Time operations
- std::net          - Networking
- std::fmt          - Formatting
- std::default      - Default values
- std::convert      - Type conversions
- std::ops          - Operator overloading
- std::cmp          - Comparisons
*/

// No-std programming (for embedded systems)
// #![no_std]
// use core::prelude::v1::*;

fn main() {
    println!("Standard library provides batteries-included functionality");
}

Common Pitfalls

  • Not understanding the difference between core, alloc, and std
  • Forgetting to handle Result and Option types properly
  • Using unwrap() in production code instead of proper error handling
  • Not leveraging standard library traits for custom types
  • Reimplementing functionality that's already in the standard library

Standard Library Collections

Rust's standard library provides efficient, generic collections for storing and manipulating data. Each collection has different performance characteristics and use cases.

Vector (Vec<T>)

fn vector_examples() {
    // Creating vectors
    let mut vec1 = Vec::new();
    vec1.push(1);
    vec1.push(2);
    vec1.push(3);
    
    let vec2 = vec![1, 2, 3, 4, 5];  // Macro syntax
    let vec3 = Vec::with_capacity(10);  // Pre-allocate
    
    // Accessing elements
    println!("First: {}", vec2[0]);        // Panics if out of bounds
    println!("First: {:?}", vec2.get(0));  // Returns Option
    
    // Iteration
    for element in &vec2 {           // Borrow - doesn't consume
        println!("Element: {}", element);
    }
    
    for element in vec2 {            // Consumes vector
        println!("Element: {}", element);
    }
    // vec2 is no longer usable here
    
    // Common operations
    let mut numbers = vec![1, 2, 3, 4, 5];
    numbers.pop();                    // Remove last
    numbers.insert(2, 99);           // Insert at index
    numbers.remove(1);               // Remove at index
    numbers.retain(|&x| x % 2 == 0); // Keep only even numbers
    
    println!("Modified: {:?}", numbers);
}

String and String Slices

fn string_examples() {
    // String (owned, growable)
    let mut s1 = String::new();
    s1.push_str("Hello");
    s1.push(' ');
    s1.push_str("World");
    
    let s2 = "Initial content".to_string();
    let s3 = String::from("From string literal");
    
    // String slices (&str) - borrowed
    let literal = "Hello World";  // This is &str
    let slice = &s1[0..5];       // "Hello"
    
    // Conversion between String and &str
    let owned: String = slice.to_string();
    let borrowed: &str = &owned;
    
    // String operations
    let text = String::from("Hello Rust!");
    println!("Length: {}", text.len());           // Number of bytes
    println!("Chars count: {}", text.chars().count());  // Number of characters
    
    // Splitting
    for word in text.split_whitespace() {
        println!("Word: {}", word);
    }
    
    // Concatenation
    let s4 = s1 + " " + &s2;  // Note: s1 is moved
    let s5 = format!("{} {}", s3, "concatenated");
    
    println!("s4: {}, s5: {}", s4, s5);
}

HashMap and HashSet

use std::collections::{HashMap, HashSet};

fn hash_examples() {
    // HashMap - key-value storage
    let mut scores = HashMap::new();
    scores.insert("Alice", 10);
    scores.insert("Bob", 20);
    scores.insert("Charlie", 30);
    
    // Accessing values
    if let Some(score) = scores.get("Alice") {
        println!("Alice's score: {}", score);
    }
    
    // Update values
    scores.insert("Alice", 25);  // Overwrite
    scores.entry("Bob").or_insert(50);  // Insert if not exists
    scores.entry("David").or_insert(40);  // Insert new
    
    // Iteration
    for (name, score) in &scores {
        println!("{}: {}", name, score);
    }
    
    // HashSet - unique values
    let mut set1 = HashSet::new();
    set1.insert(1);
    set1.insert(2);
    set1.insert(3);
    set1.insert(2);  // Duplicate - ignored
    
    let set2: HashSet<_> = [3, 4, 5].iter().cloned().collect();
    
    // Set operations
    println!("Union: {:?}", set1.union(&set2));
    println!("Intersection: {:?}", set1.intersection(&set2));
    println!("Difference: {:?}", set1.difference(&set2));
    
    println!("Set1: {:?}", set1);
}

Other Collections

use std::collections::{VecDeque, BinaryHeap, BTreeMap, BTreeSet};

fn other_collections() {
    // VecDeque - double-ended queue
    let mut deque = VecDeque::new();
    deque.push_back(1);    // Add to end
    deque.push_front(0);   // Add to front
    deque.pop_back();      // Remove from end
    deque.pop_front();     // Remove from front
    
    // BinaryHeap - priority queue (max-heap by default)
    let mut heap = BinaryHeap::new();
    heap.push(3);
    heap.push(1);
    heap.push(5);
    heap.push(2);
    
    while let Some(max) = heap.pop() {
        println!("Max: {}", max);  // 5, 3, 2, 1
    }
    
    // BTreeMap - sorted map
    let mut btree = BTreeMap::new();
    btree.insert(3, "three");
    btree.insert(1, "one");
    btree.insert(2, "two");
    
    // Keys are sorted
    for (key, value) in &btree {
        println!("{}: {}", key, value);  // 1, 2, 3
    }
    
    // BTreeSet - sorted set
    let mut btree_set = BTreeSet::new();
    btree_set.insert(3);
    btree_set.insert(1);
    btree_set.insert(2);
    
    println!("BTreeSet: {:?}", btree_set);  // {1, 2, 3}
}

Collection Performance Characteristics

/*
Collection    | Insert | Access | Search | Remove | Use Case
--------------|--------|--------|--------|--------|---------
Vec<T>        | O(1)*  | O(1)   | O(n)   | O(n)   | General purpose, indexing
VecDeque<T>   | O(1)*  | O(1)   | O(n)   | O(n)   | Queue, double-ended operations
LinkedList<T> | O(1)   | O(n)   | O(n)   | O(1)   | Frequent insert/remove at ends
HashMap<K,V>  | O(1)*  | O(1)*  | O(1)*  | O(1)*  | Key-value lookups
BTreeMap<K,V> | O(log n)|O(log n)|O(log n)|O(log n)| Sorted key-value
HashSet<T>    | O(1)*  | O(1)*  | O(1)*  | O(1)*  | Unique elements, membership
BTreeSet<T>   | O(log n)|O(log n)|O(log n)|O(log n)| Sorted unique elements
BinaryHeap<T> | O(log n)|O(1)   | O(n)   | O(log n)| Priority queue

* = amortized constant time, depends on hash distribution
*/

fn performance_considerations() {
    // Choose the right collection for your use case
    
    // Vec: When you need indexing or are mostly adding to the end
    let mut vec = Vec::with_capacity(1000);  // Pre-allocate if size known
    
    // HashMap: When you need fast key-based lookups
    use std::collections::HashMap;
    let mut map = HashMap::with_capacity(1000);
    
    // VecDeque: When you need queue-like behavior (FIFO)
    use std::collections::VecDeque;
    let mut queue = VecDeque::new();
    
    // BinaryHeap: When you need priority-based access
    use std::collections::BinaryHeap;
    let mut heap = BinaryHeap::new();
    
    println!("Choose collections based on your access patterns");
}

Collection Methods and Patterns

fn collection_methods() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // Functional programming style
    let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
    let evens: Vec<&i32> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
    let sum: i32 = numbers.iter().sum();
    
    println!("Doubled: {:?}", doubled);
    println!("Evens: {:?}", evens);
    println!("Sum: {}", sum);
    
    // Chaining operations
    let result: Vec<i32> = numbers
        .iter()
        .filter(|&&x| x > 5)
        .map(|&x| x * 3)
        .collect();
    println!("Filtered and mapped: {:?}", result);
    
    // Using collect with type inference
    let squared: Vec<i32> = numbers.iter().map(|&x| x * x).collect();
    let as_hashset: HashSet<i32> = numbers.iter().cloned().collect();
    let as_string: String = numbers.iter().map(|x| x.to_string()).collect();
    
    // Partitioning
    let (even, odd): (Vec<i32>, Vec<i32>) = numbers
        .into_iter()
        .partition(|&x| x % 2 == 0);
    
    println!("Even: {:?}, Odd: {:?}", even, odd);
}

Common Pitfalls

  • Using [] indexing that can panic instead of get()
  • Not pre-allocating capacity when size is known for Vec/HashMap
  • Forgetting that HashMap keys need to implement Hash and Eq
  • Using the wrong collection for the access pattern
  • Not handling the possibility of hash collisions in performance-critical code

Standard Library Iterators

Iterators are a fundamental abstraction in Rust for processing sequences of elements. They are lazy, composable, and often zero-cost.

Basic Iterator Usage

fn basic_iterator_examples() {
    let numbers = vec![1, 2, 3, 4, 5];
    
    // Creating iterators
    let iter1 = numbers.iter();      // Immutable references
    let iter2 = numbers.iter_mut();  // Mutable references  
    let iter3 = numbers.into_iter(); // Takes ownership (consumes)
    
    // Using iterators
    for number in numbers.iter() {
        println!("Number: {}", number);
    }
    
    // Collecting into new collections
    let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
    let as_string: String = numbers.iter().map(|x| x.to_string()).collect();
    
    println!("Doubled: {:?}", doubled);
    println!("As string: {}", as_string);
    
    // numbers is still usable here because we used iter()
}

Common Iterator Adaptors

fn iterator_adaptors() {
    let numbers = 1..=10;  // Range is an iterator
    
    // map - transform each element
    let squares: Vec<i32> = numbers.clone().map(|x| x * x).collect();
    
    // filter - keep elements that satisfy predicate
    let evens: Vec<i32> = numbers.clone().filter(|x| x % 2 == 0).collect();
    
    // take - take first n elements
    let first_three: Vec<i32> = numbers.clone().take(3).collect();
    
    // skip - skip first n elements
    let after_three: Vec<i32> = numbers.clone().skip(3).collect();
    
    // zip - combine two iterators
    let pairs: Vec<(i32, i32)> = numbers.clone()
        .zip(numbers.clone().map(|x| x * 2))
        .collect();
    
    // chain - combine two iterators sequentially
    let chained: Vec<i32> = (1..=3).chain(7..=10).collect();
    
    // enumerate - get (index, value) pairs
    for (i, value) in numbers.clone().enumerate() {
        println!("Index: {}, Value: {}", i, value);
    }
    
    println!("Squares: {:?}", squares);
    println!("Evens: {:?}", evens);
    println!("First three: {:?}", first_three);
    println!("Pairs: {:?}", pairs);
    println!("Chained: {:?}", chained);
}

Consumer Methods

fn consumer_methods() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // sum - sum all elements
    let total: i32 = numbers.iter().sum();
    
    // product - multiply all elements
    let product: i32 = numbers.iter().product();
    
    // count - count elements
    let count = numbers.iter().count();
    
    // min/max - find minimum/maximum
    let min = numbers.iter().min();
    let max = numbers.iter().max();
    
    // find - find first element matching predicate
    let first_even = numbers.iter().find(|&&x| x % 2 == 0);
    
    // position - find position of element
    let pos_of_5 = numbers.iter().position(|&&x| x == 5);
    
    // all/any - check if all/any elements satisfy predicate
    let all_positive = numbers.iter().all(|&&x| x > 0);
    let any_negative = numbers.iter().any(|&&x| x < 0);
    
    // fold - accumulate values
    let sum_fold = numbers.iter().fold(0, |acc, &x| acc + x);
    
    // for_each - execute closure for each element
    numbers.iter().for_each(|x| println!("Value: {}", x));
    
    println!("Total: {}, Count: {}", total, count);
    println!("Min: {:?}, Max: {:?}", min, max);
    println!("First even: {:?}", first_even);
    println!("All positive: {}", all_positive);
    println!("Sum with fold: {}", sum_fold);
}

Creating Custom Iterators

struct Counter {
    current: u32,
    max: u32,
}

impl Counter {
    fn new(max: u32) -> Counter {
        Counter { current: 0, max }
    }
}

// Implement Iterator trait
impl Iterator for Counter {
    type Item = u32;
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.current < self.max {
            self.current += 1;
            Some(self.current)
        } else {
            None
        }
    }
}

// Implementing IntoIterator for custom types
struct MyCollection {
    data: Vec<i32>,
}

impl IntoIterator for MyCollection {
    type Item = i32;
    type IntoIter = std::vec::IntoIter<Self::Item>;
    
    fn into_iter(self) -> Self::IntoIter {
        self.data.into_iter()
    }
}

fn custom_iterator_examples() {
    // Using custom iterator
    let counter = Counter::new(5);
    for num in counter {
        println!("Counter: {}", num);  // 1, 2, 3, 4, 5
    }
    
    // Using iterator methods on custom iterator
    let sum: u32 = Counter::new(5).sum();
    let doubled: Vec<u32> = Counter::new(3).map(|x| x * 2).collect();
    
    println!("Sum: {}", sum);        // 15
    println!("Doubled: {:?}", doubled);  // [2, 4, 6]
    
    // MyCollection with IntoIterator
    let collection = MyCollection { data: vec![1, 2, 3] };
    for value in collection {  // Uses into_iter automatically
        println!("Value: {}", value);
    }
}

Advanced Iterator Patterns

use std::iter::{once, repeat, empty};

fn advanced_iterator_patterns() {
    // Creating iterators from functions
    let ones = repeat(1).take(5);           // 1, 1, 1, 1, 1
    let empty_iter = empty::<i32>();        // No elements
    let single = once(42);                  // Single element: 42
    
    // Cycle - repeat iterator endlessly
    let cycled: Vec<i32> = vec![1, 2, 3].into_iter().cycle().take(7).collect();
    println!("Cycled: {:?}", cycled);  // [1, 2, 3, 1, 2, 3, 1]
    
    // Windows and chunks for slices
    let data = [1, 2, 3, 4, 5];
    for window in data.windows(3) {
        println!("Window: {:?}", window);  // [1,2,3], [2,3,4], [3,4,5]
    }
    
    for chunk in data.chunks(2) {
        println!("Chunk: {:?}", chunk);  // [1,2], [3,4], [5]
    }
    
    // Peekable iterator (look ahead without consuming)
    let mut peekable = data.iter().peekable();
    while let Some(&item) = peekable.peek() {
        println!("Next item will be: {}", item);
        peekable.next();  // Actually consume it
    }
    
    // Inspect - debug without affecting iterator
    let sum: i32 = data.iter()
        .inspect(|x| println!("Processing: {}", x))
        .map(|&x| x * 2)
        .inspect(|x| println!("Doubled: {}", x))
        .sum();
    println!("Final sum: {}", sum);
}

Iterator Performance

fn iterator_performance() {
    // Iterators are often zero-cost abstractions
    // The generated assembly can be as efficient as hand-written loops
    
    let numbers: Vec<i32> = (1..=1000).collect();
    
    // This iterator chain...
    let sum: i32 = numbers
        .iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| x * x)
        .sum();
    
    // ...is as efficient as this manual loop:
    let mut manual_sum = 0;
    for &x in &numbers {
        if x % 2 == 0 {
            manual_sum += x * x;
        }
    }
    
    assert_eq!(sum, manual_sum);
    
    // Lazy evaluation - nothing happens until you consume
    let lazy_iter = (1..)
        .map(|x| x * 2)
        .filter(|x| x % 3 == 0)
        .take(5);
    
    // The above doesn't do any work until we collect
    let result: Vec<i32> = lazy_iter.collect();
    println!("Lazy result: {:?}", result);  // [6, 12, 18, 24, 30]
    
    // Using size hints for optimization
    let iter = (1..100).filter(|x| x % 2 == 0);
    println!("Size hint: {:?}", iter.size_hint());  // (0, Some(99))
}

// Benchmarking iterator vs loop (conceptual)
/*
In practice, iterators are often as fast or faster than loops because:
- They can be optimized better by the compiler
- They avoid bounds checks in many cases  
- They enable vectorization opportunities
*/

Common Pitfalls

  • Forgetting to consume iterators (they're lazy)
  • Using into_iter() when you meant iter()
  • Not leveraging iterator adaptors and going back to loops unnecessarily
  • Creating intermediate collections when not needed
  • Ignoring iterator size hints for optimization opportunities

Structs in Rust

Structs are custom data types that let you name and package together multiple related values. Rust has three struct variants: classic, tuple, and unit structs.

Basic Struct Definition and Usage

// Classic struct with named fields
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

// Tuple struct (like a named tuple)
struct Color(i32, i32, i32);

// Unit struct (no fields)
struct Marker;

fn main() {
    // Creating struct instances
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    
    // Accessing fields
    println!("User: {}, Email: {}", user1.username, user1.email);
    
    // Creating mutable struct
    let mut user2 = User {
        email: String::from("another@example.com"),
        username: String::from("anotherusername"),
        active: true,
        sign_in_count: 1,
    };
    
    user2.sign_in_count = 2;  // Can modify because user2 is mutable
    
    // Tuple struct usage
    let black = Color(0, 0, 0);
    println!("Black: ({}, {}, {})", black.0, black.1, black.2);
    
    // Unit struct usage
    let marker = Marker;
}

Struct Methods and Associated Functions

struct Rectangle {
    width: u32,
    height: u32,
}

// Implementation block for methods
impl Rectangle {
    // Method - takes &self (immutable reference to self)
    fn area(&self) -> u32 {
        self.width * self.height
    }
    
    // Method - takes &mut self (mutable reference)
    fn scale(&mut self, factor: u32) {
        self.width *= factor;
        self.height *= factor;
    }
    
    // Method - takes self (consumes the struct)
    fn into_tuple(self) -> (u32, u32) {
        (self.width, self.height)
    }
    
    // Associated function (no self parameter) - like static method
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
    
    // Method with parameters
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let mut rect = Rectangle { width: 30, height: 50 };
    
    // Call methods
    println!("Area: {}", rect.area());
    
    rect.scale(2);
    println!("Scaled area: {}", rect.area());
    
    let small = Rectangle { width: 10, height: 10 };
    println!("Can hold small? {}", rect.can_hold(&small));
    
    // Call associated function
    let square = Rectangle::square(25);
    println!("Square area: {}", square.area());
    
    // Consume the struct
    let dimensions = rect.into_tuple();
    println!("Dimensions: {}x{}", dimensions.0, dimensions.1);
    // rect is no longer usable here
}

Struct Update Syntax and Patterns

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn struct_patterns() {
    let user1 = User {
        email: String::from("user1@example.com"),
        username: String::from("user1"),
        active: true,
        sign_in_count: 1,
    };
    
    // Struct update syntax
    let user2 = User {
        email: String::from("user2@example.com"),
        username: String::from("user2"),
        ..user1  // Use the rest of the fields from user1
    };
    
    // Destructuring structs
    let User { username, email, active, .. } = user2;
    println!("User: {}, Email: {}, Active: {}", username, email, active);
    
    // Destructuring in function parameters
    fn print_user_info(User { username, email, .. }: &User) {
        println!("{} - {}", username, email);
    }
    
    print_user_info(&user1);
    
    // Pattern matching with structs
    match user1 {
        User { username, active: true, .. } => {
            println!("Active user: {}", username);
        }
        User { username, active: false, .. } => {
            println!("Inactive user: {}", username);
        }
    }
}

Generic Structs

// Generic struct with one type parameter
struct Point<T> {
    x: T,
    y: T,
}

// Generic struct with multiple type parameters
struct Pair<T, U> {
    first: T,
    second: U,
}

// Implementation for generic struct
impl<T> Point<T> {
    fn new(x: T, y: T) -> Point<T> {
        Point { x, y }
    }
    
    fn x(&self) -> &T {
        &self.x
    }
}

// Implementation with trait bounds
impl<T: PartialEq> Point<T> {
    fn is_equal(&self, other: &Point<T>) -> bool {
        self.x == other.x && self.y == other.y
    }
}

// Specialized implementation for specific type
impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

fn main() {
    // Using generic structs with different types
    let integer_point = Point { x: 5, y: 10 };
    let float_point = Point { x: 1.0, y: 4.0 };
    let string_pair = Pair { first: "hello", second: "world" };
    
    println!("Integer point: ({}, {})", integer_point.x, integer_point.y);
    println!("Float point distance: {}", float_point.distance_from_origin());
    println!("String pair: {} {}", string_pair.first, string_pair.second);
}

Advanced Struct Patterns

use std::fmt;

// Struct with lifetime parameters
struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn new(text: &'a str) -> ImportantExcerpt<'a> {
        ImportantExcerpt {
            part: text,
        }
    }
    
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

// Newtype pattern (wrapper around existing type)
struct Meters(f64);

impl Meters {
    fn to_kilometers(&self) -> f64 {
        self.0 / 1000.0
    }
}

// Implementing traits for structs
impl fmt::Display for Meters {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} meters", self.0)
    }
}

fn advanced_struct_examples() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    let excerpt = ImportantExcerpt::new(first_sentence);
    
    println!("Excerpt: {}", excerpt.part);
    
    let distance = Meters(5000.0);
    println!("{} = {} kilometers", distance, distance.to_kilometers());
    
    // Builder pattern for complex struct construction
    let user = UserBuilder::new()
        .username("john_doe")
        .email("john@example.com")
        .build();
    
    println!("Built user: {}", user.username);
}

// Builder pattern implementation
struct UserBuilder {
    username: Option<String>,
    email: Option<String>,
}

impl UserBuilder {
    fn new() -> UserBuilder {
        UserBuilder {
            username: None,
            email: None,
        }
    }
    
    fn username(mut self, username: &str) -> UserBuilder {
        self.username = Some(username.to_string());
        self
    }
    
    fn email(mut self, email: &str) -> UserBuilder {
        self.email = Some(email.to_string());
        self
    }
    
    fn build(self) -> User {
        User {
            username: self.username.unwrap_or_else(|| "unknown".to_string()),
            email: self.email.unwrap_or_else(|| "unknown@example.com".to_string()),
            sign_in_count: 0,
            active: true,
        }
    }
}

Common Pitfalls

  • Forgetting to make struct fields public when needed (pub)
  • Confusing when to use &self vs self in methods
  • Not handling lifetimes properly in structs with references
  • Overusing generic parameters when not needed
  • Forgetting to derive or implement common traits (Debug, Clone, etc.)

Enums in Rust

Enums (enumerations) allow you to define a type by enumerating its possible variants. Rust enums are much more powerful than in many other languages.

Basic Enum Definition and Usage

// Simple enum
enum Color {
    Red,
    Green,
    Blue,
}

// Enum with data
enum Message {
    Quit,                       // No data
    Move { x: i32, y: i32 },    // Named fields like struct
    Write(String),              // Single piece of data
    ChangeColor(i32, i32, i32), // Multiple pieces of data
}

// Enum with methods
impl Message {
    fn call(&self) {
        // Method body would be defined here
        println!("Message called");
    }
}

fn main() {
    // Creating enum instances
    let red = Color::Red;
    let green = Color::Green;
    
    let msg1 = Message::Write(String::from("hello"));
    let msg2 = Message::Move { x: 10, y: 20 };
    let msg3 = Message::ChangeColor(255, 0, 0);
    
    // Using methods on enums
    msg1.call();
    
    // Pattern matching with enums
    match red {
        Color::Red => println!("It's red!"),
        Color::Green => println!("It's green!"),
        Color::Blue => println!("It's blue!"),
    }
    
    process_message(msg2);
}

fn process_message(msg: Message) {
    match msg {
        Message::Quit => {
            println!("The Quit variant has no data");
        }
        Message::Move { x, y } => {
            println!("Move to coordinates ({}, {})", x, y);
        }
        Message::Write(text) => {
            println!("Text message: {}", text);
        }
        Message::ChangeColor(r, g, b) => {
            println!("Change color to RGB({}, {}, {})", r, g, b);
        }
    }
}

The Option Enum

// Option is a built-in enum for handling optional values
// enum Option<T> {
//     Some(T),
//     None,
// }

fn option_examples() {
    let some_number = Some(5);
    let some_string = Some("a string");
    let absent_number: Option<i32> = None;
    
    // Using match with Option
    match some_number {
        Some(value) => println!("Got a value: {}", value),
        None => println!("Got nothing"),
    }
    
    // Using if let with Option
    if let Some(value) = some_string {
        println!("The string is: {}", value);
    }
    
    // Option combinators
    let number = Some(5);
    let doubled = number.map(|x| x * 2);           // Some(10)
    let filtered = number.filter(|&x| x > 3);      // Some(5)
    let or_else = absent_number.or(Some(42));      // Some(42)
    
    // Unwrapping options (use carefully)
    let value = some_number.unwrap();              // 5
    // let panic = absent_number.unwrap();         // This would panic!
    
    let safe_value = absent_number.unwrap_or(0);   // 0 (default value)
    let or_else_value = absent_number.unwrap_or_else(|| 42);  // 42
    
    println!("Doubled: {:?}", doubled);
    println!("Safe value: {}", safe_value);
}

The Result Enum

// Result is a built-in enum for error handling
// enum Result<T, E> {
//     Ok(T),
//     Err(E),
// }

fn result_examples() -> Result<(), String> {
    let success: Result<i32, &str> = Ok(42);
    let failure: Result<i32, &str> = Err("Something went wrong");
    
    // Using match with Result
    match success {
        Ok(value) => println!("Success: {}", value),
        Err(e) => println!("Error: {}", e),
    }
    
    // Using if let with Result
    if let Err(error) = failure {
        println!("Failed with: {}", error);
    }
    
    // Result combinators
    let mapped = success.map(|x| x * 2);                   // Ok(84)
    let and_then = success.and_then(|x| Ok(x + 10));       // Ok(52)
    let or_else = failure.or_else(|_| Ok(0));              // Ok(0)
    
    // The ? operator for error propagation
    let content = read_file("example.txt")?;
    println!("File content: {}", content);
    
    // Converting between Option and Result
    let some_value = Some(42);
    let result: Result<i32, &str> = some_value.ok_or("No value");
    
    Ok(())
}

fn read_file(filename: &str) -> Result<String, String> {
    // Simulate file reading
    if filename == "example.txt" {
        Ok("File content".to_string())
    } else {
        Err("File not found".to_string())
    }
}

// Using Result in main
fn main() -> Result<(), Box<dyn std::error::Error>> {
    result_examples()?;
    Ok(())
}

Advanced Enum Patterns

use std::fmt;

// Generic enums
enum MyResult<T, E> {
    Success(T),
    Failure(E),
}

// Enum with methods
impl<T, E> MyResult<T, E> {
    fn is_success(&self) -> bool {
        match self {
            MyResult::Success(_) => true,
            MyResult::Failure(_) => false,
        }
    }
    
    fn unwrap(self) -> T {
        match self {
            MyResult::Success(value) => value,
            MyResult::Failure(_) => panic!("Called unwrap on Failure"),
        }
    }
}

// Pattern matching advanced features
fn advanced_pattern_matching() {
    let complex_enum = Message::Move { x: 10, y: 20 };
    
    // Matching with guards
    match complex_enum {
        Message::Move { x, y } if x == 0 && y == 0 => {
            println!("At origin");
        }
        Message::Move { x, y } if x > 0 && y > 0 => {
            println!("In first quadrant: ({}, {})", x, y);
        }
        Message::Move { x, y } => {
            println!("At coordinates: ({}, {})", x, y);
        }
        _ => {}
    }
    
    // @ bindings - bind to variable while also matching
    let some_value = Some(15);
    match some_value {
        Some(x @ 1..=10) => println!("Small number: {}", x),
        Some(x @ 11..=100) => println!("Medium number: {}", x),
        Some(x) => println!("Large number: {}", x),
        None => println!("No number"),
    }
}

// Implementing traits for enums
impl fmt::Display for Color {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Color::Red => write!(f, "Red"),
            Color::Green => write!(f, "Green"),
            Color::Blue => write!(f, "Blue"),
        }
    }
}

fn main() {
    let color = Color::Red;
    println!("The color is: {}", color);
    
    let my_result: MyResult<i32, &str> = MyResult::Success(42);
    println!("Is success: {}", my_result.is_success());
    
    advanced_pattern_matching();
}

Common Enum Use Cases

// State machines
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

impl TrafficLight {
    fn next(&self) -> TrafficLight {
        match self {
            TrafficLight::Red => TrafficLight::Green,
            TrafficLight::Yellow => TrafficLight::Red,
            TrafficLight::Green => TrafficLight::Yellow,
        }
    }
    
    fn duration(&self) -> u32 {
        match self {
            TrafficLight::Red => 30,
            TrafficLight::Yellow => 5,
            TrafficLight::Green => 45,
        }
    }
}

// AST (Abstract Syntax Tree) nodes
enum Expr {
    Number(i32),
    Add(Box<Expr>, Box<Expr>),
    Multiply(Box<Expr>, Box<Expr>),
    Variable(String),
}

impl Expr {
    fn evaluate(&self, vars: &std::collections::HashMap<String, i32>) -> i32 {
        match self {
            Expr::Number(n) => *n,
            Expr::Add(left, right) => left.evaluate(vars) + right.evaluate(vars),
            Expr::Multiply(left, right) => left.evaluate(vars) * right.evaluate(vars),
            Expr::Variable(name) => *vars.get(name).unwrap_or(&0),
        }
    }
}

// Command pattern
enum Command {
    Move { x: i32, y: i32 },
    Attack { target: String },
    Wait,
    Quit,
}

impl Command {
    fn execute(&self) {
        match self {
            Command::Move { x, y } => println!("Moving to ({}, {})", x, y),
            Command::Attack { target } => println!("Attacking {}", target),
            Command::Wait => println!("Waiting..."),
            Command::Quit => println!("Quitting"),
        }
    }
}

fn enum_use_cases() {
    let mut light = TrafficLight::Red;
    for _ in 0..5 {
        println!("Light: {:?}, Duration: {}s", light, light.duration());
        light = light.next();
    }
    
    // AST example: (2 + 3) * 4
    let expr = Expr::Multiply(
        Box::new(Expr::Add(
            Box::new(Expr::Number(2)),
            Box::new(Expr::Number(3)),
        )),
        Box::new(Expr::Number(4)),
    );
    
    let result = expr.evaluate(&std::collections::HashMap::new());
    println!("Expression result: {}", result);  // 20
    
    // Command pattern
    let commands = vec![
        Command::Move { x: 10, y: 20 },
        Command::Attack { target: "enemy".to_string() },
        Command::Wait,
        Command::Quit,
    ];
    
    for command in commands {
        command.execute();
    }
}

Common Pitfalls

  • Forgetting to handle all enum variants in match statements
  • Using unwrap() on Option/Result without considering failure cases
  • Not leveraging the ? operator for error propagation
  • Creating overly complex enums when separate types would be better
  • Forgetting that enum variants are namespaced under the enum type

Traits in Rust

Traits define shared behavior that types can implement. They're similar to interfaces in other languages but more powerful, supporting associated types, default implementations, and more.

Basic Trait Definition and Implementation

// Define a trait
trait Summary {
    fn summarize(&self) -> String;
    
    // Default implementation
    fn summarize_author(&self) -> String {
        String::from("(Unknown author)")
    }
    
    // Method with default implementation that uses other methods
    fn summarize_with_author(&self) -> String {
        format!("{} by {}", self.summarize(), self.summarize_author())
    }
}

// Implement the trait for a type
struct NewsArticle {
    headline: String,
    location: String,
    author: String,
    content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
    
    // Override the default implementation
    fn summarize_author(&self) -> String {
        self.author.clone()
    }
}

// Another type implementing the same trait
struct Tweet {
    username: String,
    content: String,
    reply: bool,
    retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn main() {
    let article = NewsArticle {
        headline: String::from("Penguins win the Stanley Cup Championship!"),
        location: String::from("Pittsburgh, PA, USA"),
        author: String::from("Iceburgh"),
        content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
    };
    
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    };
    
    println!("Article summary: {}", article.summarize());
    println!("Tweet summary: {}", tweet.summarize());
    println!("Article with author: {}", article.summarize_with_author());
}

Trait Bounds and Generic Functions

use std::fmt::Display;

// Function with trait bound
fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

// Multiple trait bounds
fn notify_multiple<T: Summary + Display>(item: &T) {
    println!("Item: {} - Summary: {}", item, item.summarize());
}

// Alternative syntax with where clause
fn some_function<T, U>(t: &T, u: &U) -> i32 
where 
    T: Display + Clone,
    U: Clone + Summary,
{
    println!("T: {}, U summary: {}", t, u.summarize());
    42
}

// Returning types that implement traits
fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}

// Using trait bounds with structs
struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

fn trait_bounds_examples() {
    let article = NewsArticle { /* ... */ };
    notify(&article);
    
    let pair = Pair::new(3, 4);
    pair.cmp_display();
    
    let summarizable = returns_summarizable();
    println!("Returned: {}", summarizable.summarize());
}

Common Standard Library Traits

use std::fmt;
use std::ops::Add;

// Derivable traits
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point {
    x: i32,
    y: i32,
}

// Manual implementation of Display
impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

// Implementing operator overloading with Add trait
impl Add for Point {
    type Output = Point;
    
    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

// Implementing From/Into traits for conversions
impl From<(i32, i32)> for Point {
    fn from(pair: (i32, i32)) -> Self {
        Point {
            x: pair.0,
            y: pair.1,
        }
    }
}

fn standard_traits_examples() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    
    // Using Debug trait
    println!("Debug: {:?}", p1);
    
    // Using Display trait  
    println!("Display: {}", p1);
    
    // Using Add trait
    let sum = p1 + p2;
    println!("Sum: {}", sum);
    
    // Using From trait
    let p3 = Point::from((5, 6));
    let p4: Point = (7, 8).into();  // Into is automatically implemented when From is
    
    println!("From tuple: {}", p3);
    println!("Into: {}", p4);
    
    // Using Clone trait
    let cloned = p1.clone();
    println!("Cloned: {}", cloned);
    
    // Using PartialEq trait
    println!("Equal: {}", p1 == cloned);
}

Advanced Trait Features

// Associated types
trait Iterator {
    type Item;  // Associated type
    
    fn next(&mut self) -> Option<Self::Item>;
}

// Implementing iterator with associated type
struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

// Generic traits with associated types
trait Container<T> {
    fn contains(&self, item: &T) -> bool;
}

impl<T: PartialEq> Container<T> for Vec<T> {
    fn contains(&self, item: &T) -> bool {
        self.iter().any(|x| x == item)
    }
}

// Supertraits (trait that requires another trait)
trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

// Implement OutlinePrint for Point (requires Display)
impl OutlinePrint for Point {}

// Fully Qualified Syntax for Disambiguation
trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

fn advanced_trait_examples() {
    let mut counter = Counter::new();
    while let Some(num) = counter.next() {
        println!("Counter: {}", num);
    }
    
    let point = Point { x: 10, y: 20 };
    point.outline_print();
    
    let person = Human;
    person.fly();                    // Calls Human::fly
    Pilot::fly(&person);            // Calls Pilot::fly
    Wizard::fly(&person);           // Calls Wizard::fly
}
                
                

Trait Objects and Dynamic Dispatch

// Trait objects for heterogeneous collections
fn trait_objects_examples() {
    let article = NewsArticle { /* ... */ };
    let tweet = Tweet { /* ... */ };
    
    // Vector of trait objects - can store different types that implement Summary
    let summaries: Vec<Box<dyn Summary>> = vec![
        Box::new(article),
        Box::new(tweet),
    ];
    
    for item in summaries {
        println!("Summary: {}", item.summarize());
    }
}

// Using trait objects in function parameters
fn notify_dynamic(item: &dyn Summary) {
    println!("Notification: {}", item.summarize());
}

// Trait objects with static dispatch vs dynamic dispatch
fn static_dispatch<T: Summary>(item: &T) {
    // Monomorphization - compiler generates specific code for each type
    println!("{}", item.summarize());
}

fn dynamic_dispatch(item: &dyn Summary) {
    // Dynamic dispatch - uses vtable at runtime
    println!("{}", item.summarize());
}

// Object-safe traits (can be used as trait objects)
trait ObjectSafe {
    fn method(&self);
}

// This trait is NOT object-safe because it has generic methods
// trait NotObjectSafe {
//     fn generic_method<T>(&self, t: T);
// }

fn main() {
    let article = NewsArticle { /* ... */ };
    let tweet = Tweet { /* ... */ };
    
    notify_dynamic(&article);
    notify_dynamic(&tweet);
    
    static_dispatch(&article);
    static_dispatch(&tweet);
}

Common Pitfalls

  • Forgetting to bring traits into scope when using their methods
  • Creating trait bounds that are too restrictive or not restrictive enough
  • Confusing static dispatch (generics) with dynamic dispatch (trait objects)
  • Not understanding object safety rules for trait objects
  • Overusing trait objects when generics would be more efficient

Advanced Rust Concepts

Advanced Rust features provide powerful tools for writing efficient, safe, and expressive code. These include smart pointers, async/await, unsafe Rust, and more.

Smart Pointers

use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;
use std::sync::Mutex;

fn smart_pointer_examples() {
    // Box - heap allocation with single ownership
    let boxed = Box::new(5);
    println!("Boxed value: {}", *boxed);
    
    // Rc - reference counting for multiple ownership (single-threaded)
    let rc1 = Rc::new(String::from("hello"));
    let rc2 = Rc::clone(&rc1);
    println!("Rc count: {}", Rc::strong_count(&rc1));
    
    // Arc - atomic reference counting (thread-safe)
    let arc1 = Arc::new(String::from("world"));
    let arc2 = Arc::clone(&arc1);
    println!("Arc value: {}", *arc1);
    
    // RefCell - interior mutability (runtime borrow checking)
    let ref_cell = RefCell::new(42);
    {
        let mut borrow = ref_cell.borrow_mut();
        *borrow += 1;
    } // borrow goes out of scope here
    println!("RefCell value: {}", ref_cell.borrow());
    
    // Mutex - mutual exclusion (thread-safe interior mutability)
    let mutex = Mutex::new(100);
    {
        let mut guard = mutex.lock().unwrap();
        *guard += 1;
    }
    println!("Mutex value: {}", *mutex.lock().unwrap());
    
    // Combining Rc and RefCell for multiple ownership with mutability
    let shared_mutable = Rc::new(RefCell::new(0));
    let clone1 = Rc::clone(&shared_mutable);
    let clone2 = Rc::clone(&shared_mutable);
    
    *clone1.borrow_mut() += 1;
    *clone2.borrow_mut() += 1;
    
    println!("Shared mutable: {}", *shared_mutable.borrow());
}

Async/Await

// Requires tokio or async-std runtime
// #[tokio::main]
// async fn main() {
//     let result = fetch_data().await;
//     println!("Result: {}", result);
// }

/*
async fn fetch_data() -> String {
    // Simulate async operation
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
    "Data fetched".to_string()
}

async fn process_multiple() {
    // Join multiple async operations
    let (result1, result2) = tokio::join!(
        fetch_data(),
        fetch_data()
    );
    println!("Results: {}, {}", result1, result2);
    
    // Select between async operations
    tokio::select! {
        result = fetch_data() => println!("First completed: {}", result),
        _ = tokio::time::sleep(tokio::time::Duration::from_secs(2)) => {
            println!("Timeout reached");
        }
    }
}
*/

// For this example, we'll use a synchronous version
fn async_concepts() {
    println!("Async/await enables writing asynchronous code that looks synchronous");
    println!("It's built on top of futures and requires an async runtime");
}

Unsafe Rust

unsafe fn unsafe_examples() {
    // Dereferencing raw pointers
    let mut num = 5;
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;
    
    unsafe {
        println!("r1 is: {}", *r1);
        *r2 = 10;
        println!("r2 is: {}", *r2);
    }
    
    // Calling unsafe functions
    unsafe {
        dangerous_function();
    }
    
    // Creating safe abstractions over unsafe code
    let mut v = vec![1, 2, 3, 4, 5];
    let (a, b) = split_at_mut(&mut v, 2);
    println!("First part: {:?}", a);
    println!("Second part: {:?}", b);
}

unsafe fn dangerous_function() {
    println!("This is an unsafe function");
}

// Safe abstraction over unsafe code
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr();
    
    assert!(mid <= len);
    
    unsafe {
        (
            std::slice::from_raw_parts_mut(ptr, mid),
            std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

// Implementing unsafe traits
unsafe trait UnsafeTrait {
    // trait methods
}

unsafe impl UnsafeTrait for i32 {
    // implementation
}

fn main() {
    unsafe {
        unsafe_examples();
    }
}

Advanced Pattern Matching

fn advanced_patterns() {
    // Matching ranges
    let x = 5;
    match x {
        1..=5 => println!("One through five"),
        6 | 7 | 8 => println!("Six, seven, or eight"),
        _ => println!("Something else"),
    }
    
    // Destructuring nested structures
    struct Point {
        x: i32,
        y: i32,
    }
    
    enum Color {
        Rgb(i32, i32, i32),
        Hsv(i32, i32, i32),
    }
    
    enum Message {
        ChangeColor(Color),
    }
    
    let msg = Message::ChangeColor(Color::Rgb(0, 160, 255));
    
    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!("Change color to RGB({}, {}, {})", r, g, b);
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!("Change color to HSV({}, {}, {})", h, s, v);
        }
    }
    
    // @ bindings
    let p @ Point { x: px, y: py } = Point { x: 10, y: 20 };
    println!("Point: ({}, {})", px, py);
    println!("Whole point: {:?}", p);
    
    // Matching with guards
    let num = Some(4);
    match num {
        Some(x) if x < 5 => println!("Less than five: {}", x),
        Some(x) => println!("{}", x),
        None => (),
    }
    
    // if let chains (Rust 1.63+)
    let some_option = Some(5);
    let another_option = Some(10);
    
    // This would work in newer Rust versions:
    // if let Some(x) = some_option && let Some(y) = another_option {
    //     println!("x = {}, y = {}", x, y);
    // }
}

Macros

// Declarative macros (macro_rules!)
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

// Using the macro
fn macro_examples() {
    let v = vec![1, 2, 3];
    println!("Macro-generated vector: {:?}", v);
}

// Procedural macros require separate crate
/*
// In a procedural macro crate
use proc_macro::TokenStream;
use quote::quote;
use syn;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_hello_macro(&ast)
}

fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let gen = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}!", stringify!(#name));
            }
        }
    };
    gen.into()
}
*/

// Function-like macros
/*
macro_rules! sql {
    ($($arg:tt)*) => { /* ... */ };
}

// Usage: sql!(SELECT * FROM users WHERE id = 1);
*/

fn main() {
    macro_examples();
}

Advanced Lifetime Patterns

// Lifetime elision rules
fn first_word(s: &str) -> &str {
    // Compiler applies elision rules:
    // 1. Each parameter gets its own lifetime
    // 2. If there's exactly one input lifetime, it's assigned to all output lifetimes
    // 3. If there are multiple input lifetimes but one is &self or &mut self,
    //    the lifetime of self is assigned to all output lifetimes
    
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}

// Explicit lifetime annotations
struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    // Lifetime elision works here
    fn level(&self) -> i32 {
        3
    }
    
    // Need explicit lifetime here
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

// Higher-ranked trait bounds (HRTB)
fn call_on_ref_zero<F>(f: F) 
where 
    F: for<'a> Fn(&'a i32),
{
    let zero = 0;
    f(&zero);
}

// Static lifetime
fn static_lifetime_example() -> &'static str {
    "I have a static lifetime"
}

fn lifetime_advanced() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    let i = ImportantExcerpt {
        part: first_sentence,
    };
    
    println!("Excerpt: {}", i.part);
    
    let static_str = static_lifetime_example();
    println!("Static string: {}", static_str);
    
    call_on_ref_zero(|x| println!("x is: {}", x));
}

Common Pitfalls

  • Using unsafe code without proper validation and documentation
  • Creating memory leaks with Rc/RefCell cycles
  • Not understanding async runtime requirements
  • Overusing macros when functions would suffice
  • Misunderstanding lifetime relationships in complex data structures