Overview

The antipatterns.rs module Source Code File demonstrates 15 common Rust mistakes and their correct solutions. I put this together because I noticed the same patterns coming up, especially from developers (myself included) fighting the borrow checker instead of working with it.

Anti-Patterns Covered

1. Unnecessary .clone() Everywhere

  • ❌ Cloning to avoid borrow checker
  • ✅ Use references (&T) instead

2. Using .unwrap() in Production

  • ❌ Crashes program on error
  • ✅ Return Result<T, E> and handle gracefully

3. Returning References to Local Variables

  • ❌ Dangling references (won’t compile)
  • ✅ Return owned data or use ‘static lifetime

4. Using String When &str Would Work

  • ❌ Forces caller to own String
  • ✅ Accept &str - works with both

5. Ignoring Compiler Warnings

  • ❌ Unused mut, unused variables
  • ✅ Listen to the compiler

6. Using Indices Instead of Iterators

  • ❌ C-style loops can panic
  • ✅ Use iterators - safer and clearer

7. Manually Implementing What Traits Provide

  • ❌ Manual equality checks
  • ✅ #[derive(PartialEq, Debug, Clone)]

8. Using Vec When Array Would Work

  • ❌ Heap allocation for fixed-size data
  • ✅ Use arrays [T; N] for stack allocation

9. Nested match/if let Instead of Combinators

  • ❌ Deeply nested matching
  • ✅ Use .map(), .filter(), .and_then()

10. Using Mutex When Not Needed

  • ❌ Mutex in single-threaded code
  • ✅ Just use regular variables

11. Collecting Iterator Just to Iterate Again

  • ❌ Unnecessary intermediate collection
  • ✅ Chain iterators directly

12. Using Deref Coercion as Inheritance

  • ❌ Simulating inheritance with Deref
  • ✅ Use composition explicitly

13. Panicking in Library Code

  • ❌ panic!() crashes caller’s program
  • ✅ Return Result and let caller decide

14. Using format! When to_string() Works

  • ❌ Overkill for simple conversions
  • ✅ Use .to_string() for clarity

15. Implementing Default Manually

  • ❌ Manual new() constructor
  • ✅ Implement Default trait

Key Principle

Work WITH Rust’s ownership system, not against it.

Here’s what clicked for me after working through these: if you’re fighting the borrow checker, you’re almost certainly doing it wrong. The compiler is genuinely trying to help you write safe, efficient code, and once you stop resisting and start listening, the patterns become second nature.

Usage

Run the program and select option 5 to see the anti-patterns summary.

// Rust Anti-Patterns - Common Mistakes and How to Fix Them
// This module demonstrates what NOT to do and shows the correct approach

#![allow(dead_code, unused_variables, unused_mut)]

// ============================================================================
// ANTI-PATTERN 1: Unnecessary .clone() Everywhere
// ============================================================================

// ❌ WRONG: Cloning to avoid fighting the borrow checker
fn antipattern_clone_spam() {
    let data = vec![1, 2, 3, 4, 5];
    
    // Cloning unnecessarily - expensive and defeats Rust's zero-cost abstractions
    let sum = calculate_sum(data.clone());
    let max = find_max(data.clone());
    let min = find_min(data.clone());
    
    fn calculate_sum(v: Vec<i32>) -> i32 { v.iter().sum() }
    fn find_max(v: Vec<i32>) -> i32 { *v.iter().max().unwrap() }
    fn find_min(v: Vec<i32>) -> i32 { *v.iter().min().unwrap() }
}

// ✅ CORRECT: Use references instead of cloning
fn correct_use_references() {
    let data = vec![1, 2, 3, 4, 5];
    
    // Borrow instead of clone - zero cost, no memory allocation
    let sum = calculate_sum(&data);
    let max = find_max(&data);
    let min = find_min(&data);
    
    fn calculate_sum(v: &[i32]) -> i32 { v.iter().sum() }
    fn find_max(v: &[i32]) -> i32 { *v.iter().max().unwrap() }
    fn find_min(v: &[i32]) -> i32 { *v.iter().min().unwrap() }
}

// ============================================================================
// ANTI-PATTERN 2: Using .unwrap() in Production Code
// ============================================================================

// ❌ WRONG: unwrap() causes panics - crashes your program
fn antipattern_unwrap_everywhere(filename: &str) -> String {
    use std::fs;
    
    // If file doesn't exist or can't be read, program crashes!
    let content = fs::read_to_string(filename).unwrap();
    content
}

// ✅ CORRECT: Properly handle errors with Result
fn correct_error_handling(filename: &str) -> Result<String, std::io::Error> {
    use std::fs;
    
    // Return the Result - let caller decide how to handle error
    fs::read_to_string(filename)
}

// Or handle the error gracefully
fn correct_error_handling_with_default(filename: &str) -> String {
    use std::fs;
    
    // Provide fallback behavior instead of crashing
    fs::read_to_string(filename).unwrap_or_else(|_| String::from("default content"))
}

// ============================================================================
// ANTI-PATTERN 3: Returning References to Local Variables
// ============================================================================

// ❌ WRONG: This won't compile - dangling reference
// fn antipattern_return_local_reference() -> &str {
//     let s = String::from("hello");
//     &s  // ERROR: s is dropped at end of function, reference would be invalid
// }

// ✅ CORRECT: Return owned data
fn correct_return_owned() -> String {
    let s = String::from("hello");
    s  // Move ownership to caller
}

// Or use static lifetime for constants
fn correct_return_static() -> &'static str {
    "hello"  // String literals have 'static lifetime
}

// ============================================================================
// ANTI-PATTERN 4: Using String When &str Would Work
// ============================================================================

// ❌ WRONG: Forces caller to own a String
fn antipattern_require_string(name: String) {
    println!("Hello, {}", name);
}

// ✅ CORRECT: Accept &str - works with both String and &str
fn correct_accept_str_slice(name: &str) {
    println!("Hello, {}", name);
}

fn demonstrate_string_flexibility() {
    let owned = String::from("Alice");
    let borrowed = "Bob";
    
    // correct_accept_str_slice works with both
    correct_accept_str_slice(&owned);
    correct_accept_str_slice(borrowed);
    
    // antipattern_require_string forces conversion
    // antipattern_require_string(borrowed.to_string());  // Unnecessary allocation
}

// ============================================================================
// ANTI-PATTERN 5: Ignoring Compiler Warnings
// ============================================================================

// ❌ WRONG: Unused mut, unused variables
fn antipattern_ignore_warnings() {
    let mut x = 5;  // Warning: variable does not need to be mutable
    let y = 10;     // Warning: unused variable
    println!("{}", x);
}

// ✅ CORRECT: Listen to the compiler
fn correct_heed_warnings() {
    let x = 5;      // Removed unnecessary mut
    // Removed unused variable
    println!("{}", x);
}

// ============================================================================
// ANTI-PATTERN 6: Using Indices Instead of Iterators
// ============================================================================

// ❌ WRONG: C-style index loops - verbose and can panic
fn antipattern_index_loops(numbers: &[i32]) -> i32 {
    let mut sum = 0;
    for i in 0..numbers.len() {
        sum += numbers[i];  // Can panic if index out of bounds
    }
    sum
}

// ✅ CORRECT: Use iterators - safer and more idiomatic
fn correct_use_iterators(numbers: &[i32]) -> i32 {
    numbers.iter().sum()  // Clear, concise, can't panic
}

// ============================================================================
// ANTI-PATTERN 7: Manually Implementing What Traits Provide
// ============================================================================

// ❌ WRONG: Manual equality check
#[derive(Clone)]
struct Person {
    name: String,
    age: u32,
}

fn antipattern_manual_equality(p1: &Person, p2: &Person) -> bool {
    p1.name == p2.name && p1.age == p2.age
}

// ✅ CORRECT: Derive traits
#[derive(Clone, PartialEq, Eq, Debug)]
struct PersonCorrect {
    name: String,
    age: u32,
}

fn correct_use_traits() {
    let p1 = PersonCorrect { name: "Alice".to_string(), age: 30 };
    let p2 = PersonCorrect { name: "Alice".to_string(), age: 30 };
    
    // Just use == operator
    println!("Equal: {}", p1 == p2);
}

// ============================================================================
// ANTI-PATTERN 8: Using Vec When Array Would Work
// ============================================================================

// ❌ WRONG: Heap allocation for fixed-size data
fn antipattern_vec_for_fixed_size() {
    let days = vec!["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
    // Unnecessary heap allocation
}

// ✅ CORRECT: Use array for compile-time known sizes
fn correct_use_array() {
    let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
    // Stack allocated, no runtime overhead
}

// ============================================================================
// ANTI-PATTERN 9: Nested match/if let Instead of Combinators
// ============================================================================

// ❌ WRONG: Deeply nested matching
fn antipattern_nested_matching(input: Option<String>) -> Option<usize> {
    match input {
        Some(s) => {
            if s.len() > 0 {
                Some(s.len())
            } else {
                None
            }
        }
        None => None,
    }
}

// ✅ CORRECT: Use combinators
fn correct_use_combinators(input: Option<String>) -> Option<usize> {
    input.filter(|s| !s.is_empty()).map(|s| s.len())
}

// ============================================================================
// ANTI-PATTERN 10: Using Mutex When Not Needed
// ============================================================================

use std::sync::Mutex;

// ❌ WRONG: Mutex in single-threaded code
fn antipattern_unnecessary_mutex() {
    let counter = Mutex::new(0);
    
    for _ in 0..10 {
        let mut num = counter.lock().unwrap();
        *num += 1;
    }
    // Unnecessary overhead - no threading here
}

// ✅ CORRECT: Just use a regular variable
fn correct_simple_variable() {
    let mut counter = 0;
    
    for _ in 0..10 {
        counter += 1;
    }
}

// ============================================================================
// ANTI-PATTERN 11: Collecting Iterator Just to Iterate Again
// ============================================================================

// ❌ WRONG: Unnecessary intermediate collection
fn antipattern_collect_then_iterate(numbers: &[i32]) -> i32 {
    let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
    doubled.iter().sum()  // Why collect if we just iterate again?
}

// ✅ CORRECT: Chain iterators
fn correct_chain_iterators(numbers: &[i32]) -> i32 {
    numbers.iter().map(|x| x * 2).sum()  // No intermediate allocation
}

// ============================================================================
// ANTI-PATTERN 12: Using Deref Coercion as Inheritance
// ============================================================================

use std::ops::Deref;

// ❌ WRONG: Trying to simulate inheritance
struct Employee {
    person: Person,
    employee_id: u32,
}

impl Deref for Employee {
    type Target = Person;
    fn deref(&self) -> &Self::Target {
        &self.person
    }
}
// This is confusing - Deref is for smart pointers, not inheritance

// ✅ CORRECT: Use composition explicitly
struct EmployeeCorrect {
    person: PersonCorrect,
    employee_id: u32,
}

impl EmployeeCorrect {
    fn person(&self) -> &PersonCorrect {
        &self.person
    }
}

// ============================================================================
// ANTI-PATTERN 13: Panicking in Library Code
// ============================================================================

// ❌ WRONG: Library function that panics
fn antipattern_library_panic(divisor: i32) -> i32 {
    if divisor == 0 {
        panic!("Cannot divide by zero!");  // Crashes caller's program
    }
    100 / divisor
}

// ✅ CORRECT: Return Result and let caller decide
fn correct_library_result(divisor: i32) -> Result<i32, String> {
    if divisor == 0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(100 / divisor)
    }
}

// ============================================================================
// ANTI-PATTERN 14: Using format! When to_string() Works
// ============================================================================

// ❌ WRONG: Unnecessary formatting
fn antipattern_format_simple() {
    let num = 42;
    let s = format!("{}", num);  // Overkill for simple conversion
}

// ✅ CORRECT: Use to_string() for simple cases
fn correct_to_string() {
    let num = 42;
    let s = num.to_string();  // Clearer intent
}

// ============================================================================
// ANTI-PATTERN 15: Implementing Default Manually
// ============================================================================

// ❌ WRONG: Manual default implementation
struct Config {
    timeout: u32,
    retries: u32,
}

impl Config {
    fn new() -> Self {
        Config {
            timeout: 30,
            retries: 3,
        }
    }
}

// ✅ CORRECT: Implement Default trait
struct ConfigCorrect {
    timeout: u32,
    retries: u32,
}

impl Default for ConfigCorrect {
    fn default() -> Self {
        ConfigCorrect {
            timeout: 0,
            retries: 0,
        }
    }
}

// Or with custom defaults
struct ConfigCustom {
    timeout: u32,
    retries: u32,
}

impl Default for ConfigCustom {
    fn default() -> Self {
        ConfigCustom {
            timeout: 30,
            retries: 3,
        }
    }
}

// ============================================================================
// DEMO FUNCTION
// ============================================================================

pub fn run_antipatterns_demo() {
    println!("=== Rust Anti-Patterns Demo ===\n");
    
    println!("1. Clone vs References:");
    println!("   ❌ Cloning everywhere wastes memory and CPU");
    println!("   ✅ Use references (&T) when you don't need ownership\n");
    
    println!("2. Error Handling:");
    println!("   ❌ .unwrap() crashes your program");
    println!("   ✅ Return Result<T, E> and handle errors gracefully\n");
    
    println!("3. String Types:");
    println!("   ❌ Requiring String forces unnecessary allocations");
    println!("   ✅ Accept &str - works with both String and string literals\n");
    
    println!("4. Loops:");
    println!("   ❌ Index-based loops can panic and are verbose");
    println!("   ✅ Use iterators - safer, clearer, often faster\n");
    
    println!("5. Collections:");
    println!("   ❌ Vec for fixed-size data wastes heap memory");
    println!("   ✅ Use arrays [T; N] for compile-time known sizes\n");
    
    println!("6. Iterators:");
    println!("   ❌ Collecting just to iterate again wastes memory");
    println!("   ✅ Chain iterator operations - lazy evaluation\n");
    
    println!("7. Library Design:");
    println!("   ❌ panic!() in libraries crashes user's program");
    println!("   ✅ Return Result and let caller handle errors\n");
    
    println!("8. Traits:");
    println!("   ❌ Manual implementations of equality, debug, etc.");
    println!("   ✅ #[derive(Debug, PartialEq, Clone)] - let compiler do it\n");
    
    println!("Key Principle: Work WITH Rust's ownership system, not against it.");
    println!("If you're fighting the borrow checker, you're probably doing it wrong.\n");
}