Skip to content

Instantly share code, notes, and snippets.

@umuro
Created July 15, 2025 18:31
Show Gist options
  • Select an option

  • Save umuro/738f670c6ded3f4ad507f10c342ab3f3 to your computer and use it in GitHub Desktop.

Select an option

Save umuro/738f670c6ded3f4ad507f10c342ab3f3 to your computer and use it in GitHub Desktop.
Rust Review Expert

Unified Project Knowledge Base

Reviewed by Claude.ai

This comprehensive document consolidates all project knowledge for Rust development with SQLite, functional programming patterns, and code review guidelines.

Code Review Guidelines

Review Structure

Start your reviews by "Reviewed by Claude.ai" before the other content. Make it known that it's your work.

SQL Anti-Patterns to Avoid in SQLite

Here are SQL query writing anti-patterns to avoid in SQLite:

JOIN Anti-patterns

Implicit joins with WHERE clauses - Avoid SELECT * FROM users, orders WHERE users.id = orders.user_id. Use explicit JOIN syntax for clarity and to prevent accidental Cartesian products.

Wrong JOIN types - Using INNER JOIN when you need LEFT JOIN, or vice versa. This silently filters out data you might need.

Joining on non-indexed columns repeatedly - While not about creating indexes, repeatedly joining on columns that clearly should be indexed indicates poor query design.

WHERE Clause Anti-patterns

Using OR with different columns - WHERE status = 'active' OR created_date > '2023-01-01' can't use indexes effectively. Consider UNION instead.

Negation with NOT IN and NULL values - WHERE id NOT IN (1, 2, NULL) returns no rows due to NULL behavior. Use NOT EXISTS or handle NULLs explicitly.

Using functions in WHERE predicates - WHERE YEAR(created_date) = 2023 prevents index usage. Use WHERE created_date >= '2023-01-01' AND created_date < '2024-01-01'.

Comparing different data types - SQLite's type affinity can cause unexpected results. Be explicit about type conversions.

SELECT Anti-patterns

Using DISTINCT as a crutch - If you need DISTINCT, often indicates a poorly constructed JOIN or missing WHERE conditions.

Selecting large TEXT/BLOB columns unnecessarily - Only select large columns when actually needed, not in list queries.

Using ORDER BY without LIMIT - Sorting large result sets without pagination wastes resources.

Subquery Anti-patterns

Correlated subqueries in SELECT clauses - SELECT (SELECT COUNT(*) FROM orders WHERE user_id = users.id) FROM users executes the subquery for each row. Use JOINs instead.

EXISTS vs IN misuse - Use EXISTS for checking existence, IN for small, static lists. Avoid IN with large subqueries.

Using subqueries when CTEs would be clearer - Complex nested subqueries are harder to read and debug than Common Table Expressions.

Aggregation Anti-patterns

Mixing aggregated and non-aggregated columns - SELECT name, COUNT(*) FROM users without GROUP BY gives unpredictable results.

Using HAVING instead of WHERE - Filter rows with WHERE before grouping, use HAVING only for aggregate conditions.

Inefficient window functions - ROW_NUMBER() OVER (ORDER BY created_date) on large tables without proper partitioning.

CASE Statement Anti-patterns

Complex nested CASE expressions - Multiple levels of CASE WHEN become unreadable. Break into multiple columns or use lookup tables.

Using CASE for simple boolean logic - CASE WHEN status = 'active' THEN 1 ELSE 0 END instead of just checking the boolean condition directly.

Data Type Anti-patterns

String comparisons without considering collation - Case-sensitive vs case-insensitive comparisons can yield unexpected results.

Comparing dates as strings - WHERE date_column > '2023-1-1' fails due to string comparison. Use proper date formats or date functions.

Implicit type conversion assumptions - Relying on SQLite's type affinity without understanding the conversion rules.

Logic Anti-patterns

Using UNION when UNION ALL suffices - UNION removes duplicates, which is expensive. Use UNION ALL when duplicates don't matter.

Overcomplicated WHERE conditions - Multiple ANDs and ORs without proper parentheses create logic errors and poor readability.

Using cursors/loops in application code - Processing rows one-by-one in application code instead of using set-based operations.

NULL Handling Anti-patterns

Not accounting for NULL in comparisons - WHERE column = value doesn't match NULL values. Use IS NULL or IS NOT NULL explicitly.

Using mathematical operations on potentially NULL columns - SELECT price * quantity returns NULL if either value is NULL. Use COALESCE or IFNULL.

These anti-patterns focus specifically on query construction and logic, leading to incorrect results, poor performance, or unmaintainable code even when the underlying schema and indexes are properly designed.

Rust Anti-Patterns

Here are some common Rust anti-patterns to avoid:

Ownership and Borrowing Anti-patterns

Cloning Everything

// Anti-pattern: Unnecessary cloning
fn process_data(data: Vec<String>) -> Vec<String> {
    data.clone().into_iter().map(|s| s.clone().to_uppercase()).collect()
}

// Better: Use references when possible
fn process_data(data: &[String]) -> Vec<String> {
    data.iter().map(|s| s.to_uppercase()).collect()
}

Fighting the Borrow Checker

// Anti-pattern: Using RefCell/Rc everywhere to avoid borrow checker
use std::rc::Rc;
use std::cell::RefCell;

struct BadDesign {
    data: Rc<RefCell<Vec<i32>>>,
}

// Better: Restructure to work with Rust's ownership model
struct GoodDesign {
    data: Vec<i32>,
}

String Handling Anti-patterns

Inefficient String Operations

// Anti-pattern: Unnecessary string allocations
let mut result = String::new();
for item in items {
    result = result + &item.to_string() + ",";
}

// Better: Use format! or push_str
let mut result = String::new();
for item in items {
    result.push_str(&item.to_string());
    result.push(',');
}

String vs &str Confusion

// Anti-pattern: Always using String when &str would work
fn process_text(text: String) -> String {
    text.to_uppercase()
}

// Better: Use &str for read-only operations
fn process_text(text: &str) -> String {
    text.to_uppercase()
}

Error Handling Anti-patterns

Overusing unwrap() and expect()

// Anti-pattern: Panicking on errors
let file = File::open("config.txt").unwrap();
let parsed: Config = serde_json::from_reader(file).expect("Invalid JSON");

// Better: Proper error handling
let file = File::open("config.txt")?;
let parsed: Config = serde_json::from_reader(file)?;

Ignoring Errors

// Anti-pattern: Silently ignoring errors
let _ = some_fallible_operation();

// Better: Handle or propagate errors appropriately
some_fallible_operation().unwrap_or_else(|e| {
    eprintln!("Operation failed: {}", e);
    // Handle the error appropriately
});

Collection Anti-patterns

Collecting When Not Needed

// Anti-pattern: Unnecessary collection
let sum: i32 = data.iter().map(|x| x * 2).collect::<Vec<_>>().iter().sum();

// Better: Chain iterators
let sum: i32 = data.iter().map(|x| x * 2).sum();

Index-based Iteration

// Anti-pattern: C-style loops
for i in 0..vec.len() {
    println!("{}", vec[i]);
}

// Better: Use iterators
for item in &vec {
    println!("{}", item);
}

Memory Management Anti-patterns

Premature Optimization with Unsafe

// Anti-pattern: Using unsafe unnecessarily
unsafe {
    let ptr = data.as_ptr();
    *ptr.add(index)
}

// Better: Use safe alternatives
data.get(index).copied().unwrap_or_default()

Leaking Memory with Forgotten Drops

// Anti-pattern: Forgetting to handle resources
let file = File::create("temp.txt")?;
// File never explicitly closed, relies on Drop

// Better: Use RAII patterns or explicit resource management
{
    let _file = File::create("temp.txt")?;
    // File automatically closed when _file goes out of scope
}

Design Anti-patterns

God Objects

// Anti-pattern: Monolithic structs doing everything
struct Application {
    database: Database,
    web_server: WebServer,
    cache: Cache,
    logger: Logger,
    config: Config,
    // ... many more fields
}

// Better: Separate concerns
struct Database { /* ... */ }
struct WebServer { /* ... */ }
struct Application {
    db: Database,
    server: WebServer,
}

Overusing Generics

// Anti-pattern: Unnecessary generic complexity
fn process<T, U, V>(input: T) -> Result<U, V>
where
    T: Clone + Debug + Send + Sync,
    U: Default + Clone,
    V: Error + Send + Sync,
{
    // Complex generic constraints for simple operations
}

// Better: Use generics only when needed
fn process_string(input: &str) -> Result<String, ProcessError> {
    // Simple, clear function signature
}

Concurrency Anti-patterns

Shared Mutable State Without Proper Synchronization

// Anti-pattern: Race conditions
static mut COUNTER: i32 = 0;

// Better: Use atomic types or mutexes
use std::sync::atomic::{AtomicI32, Ordering};
static COUNTER: AtomicI32 = AtomicI32::new(0);

The key to avoiding these anti-patterns is to embrace Rust's ownership model rather than fight it, use the type system to your advantage, and prefer safe, idiomatic code over premature optimization. When you find yourself fighting the compiler, it's often a sign that there's a better way to structure your code.

Functional Programming Patterns

Here are common imperative anti-patterns in Rust that can be replaced with functional approaches:

Manual Loop Anti-Patterns

Anti-pattern: Manual iteration with mutable state

// Imperative - avoid
let mut results = Vec::new();
for item in items {
    if item > 10 {
        results.push(item * 2);
    }
}

// Functional - prefer
let results: Vec<_> = items.iter()
    .filter(|&item| *item > 10)
    .map(|item| item * 2)
    .collect();

Anti-pattern: Accumulation with explicit mutation

// Imperative - avoid
let mut sum = 0;
for item in items {
    sum += item;
}

// Functional - prefer
let sum = items.iter().sum();

Conditional Logic Anti-Patterns

Anti-pattern: Nested if-else chains

// Imperative - avoid
let mut result = String::new();
if condition1 {
    if condition2 {
        result = "case1".to_string();
    } else {
        result = "case2".to_string();
    }
} else {
    result = "case3".to_string();
}

// Functional - prefer
let result = match (condition1, condition2) {
    (true, true) => "case1",
    (true, false) => "case2",
    (false, _) => "case3",
}.to_string();

Anti-pattern: Early returns with mutable flags

// Imperative - avoid
let mut found = false;
let mut result = None;
for item in items {
    if predicate(item) {
        result = Some(item);
        found = true;
        break;
    }
}

// Functional - prefer
let result = items.iter().find(|&item| predicate(item));

Error Handling Anti-Patterns

Anti-pattern: Explicit error checking with mutations

// Imperative - avoid
let mut success = true;
let mut results = Vec::new();
for item in items {
    match process(item) {
        Ok(val) => results.push(val),
        Err(_) => {
            success = false;
            break;
        }
    }
}

// Functional - prefer
let results: Result<Vec<_>, _> = items.iter()
    .map(|item| process(item))
    .collect();

Option/Result Anti-Patterns

Anti-pattern: Nested option unwrapping

// Imperative - avoid
let mut final_result = None;
if let Some(a) = opt_a {
    if let Some(b) = opt_b {
        final_result = Some(a + b);
    }
}

// Functional - prefer
let final_result = opt_a.zip(opt_b).map(|(a, b)| a + b);

Anti-pattern: Manual result chaining

// Imperative - avoid
let mut final_result = Err("failed");
match step1() {
    Ok(val1) => {
        match step2(val1) {
            Ok(val2) => final_result = Ok(val2),
            Err(e) => final_result = Err(e),
        }
    }
    Err(e) => final_result = Err(e),
}

// Functional - prefer
let final_result = step1().and_then(step2);

Collection Transformation Anti-Patterns

Anti-pattern: Index-based loops

// Imperative - avoid
let mut transformed = Vec::new();
for i in 0..items.len() {
    transformed.push(transform(items[i]));
}

// Functional - prefer
let transformed: Vec<_> = items.iter().map(transform).collect();

Anti-pattern: Mutable collection building

// Imperative - avoid
let mut groups = std::collections::HashMap::new();
for item in items {
    let key = item.category();
    if !groups.contains_key(&key) {
        groups.insert(key, Vec::new());
    }
    groups.get_mut(&key).unwrap().push(item);
}

// Functional - prefer
use itertools::Itertools;
let groups = items.into_iter()
    .into_group_map_by(|item| item.category());

State Management Anti-Patterns

Anti-pattern: Stateful processing loops

// Imperative - avoid
let mut state = InitialState::new();
for event in events {
    match event {
        Event::A => state.handle_a(),
        Event::B => state.handle_b(),
    }
}

// Functional - prefer
let final_state = events.iter().fold(InitialState::new(), |state, event| {
    match event {
        Event::A => state.with_a_handled(),
        Event::B => state.with_b_handled(),
    }
});

Resource Management Anti-Patterns

Anti-pattern: Manual resource cleanup

// Imperative - avoid
let mut file = File::open("data.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
drop(file); // Manual cleanup

// Functional - prefer
let contents = std::fs::read_to_string("data.txt")?;

These patterns leverage Rust's iterator combinators, pattern matching, and functional programming constructs to create more declarative, composable, and less error-prone code. The functional approaches often eliminate intermediate mutable state, reduce the potential for bugs, and express intent more clearly.

SQL Review Pattern: SELECT Clause Changes and Struct Dependencies

Pattern Detection

Look for pull requests that contain:

  • Modifications to SELECT statements (added, removed, or reordered columns)
  • Especially in queries that map to Rust structs (typically queries used in application code)

Review Checklist

πŸ” When you see SELECT clause changes:

  1. Identify struct dependencies

    • Check if the query results are deserialized into Rust structs
    • Search for struct names that might correspond to the query
  2. Verify struct updates

    • Confirm that corresponding Rust structs have been updated to match
    • Check field order matches SELECT column order (if order-dependent)
    • Ensure new fields are properly typed and nullable/optional as needed
  3. Breaking change assessment

    • Are existing struct fields being removed or renamed?
    • Are new non-nullable fields being added without defaults?
    • Is the column order changing in a way that breaks positional mapping?

Required Actions

⚠️ If struct changes are missing:

BLOCKING: SELECT clause modified without corresponding struct updates.
This will cause compilation failures and make the code unmergeable.
Please update the following structs: [list struct names]

⚠️ If struct changes are present:

ATTENTION: This PR modifies both SQL queries and Rust structs.
- Confirm this is intentional and not accidental
- Verify the changes are backwards compatible
- Consider impact on other team members' branches
- May require coordination with team before merging

Example Review Comment Template

πŸ”§ **Struct Dependency Check Required**

I notice you've modified the SELECT clause in `get_user_data.sql` but I don't see corresponding changes to the `UserData` struct. 

Please verify:
- [ ] `UserData` struct updated to match new column selection
- [ ] New fields properly typed (nullable/optional as needed)
- [ ] Column order matches struct field order
- [ ] No breaking changes that affect other team members

If struct changes are intentional, please coordinate with the team before merging to avoid conflicts.

This pattern helps catch a common source of merge conflicts and build failures in Rust applications using SQL queries.

AccountCtx Guidelines

AccountCtx cannot be used in any resource.rs files. To prevent this, we need to update new() to new(canonical_name) and solve related view issues because of that.

Don't say "The consistent use of AccountCtx::use_context() for canonical name extraction in resources creates a clean, reactive data flow that's merge-friendly." This is not excellent. We don't want globals in server side code.

Comprehensive Anti-Pattern Mastery Guide

Rust-Specific Anti-Patterns for Functional Programming

Ownership and Borrowing: Fighting the system instead of embracing it

Clone Everything Anti-Pattern emerges when developers battle the borrow checker by cloning data unnecessarily. This defeats Rust's zero-cost abstractions and creates performance bottlenecks. Instead of fn process_data(data: Vec<String>) -> Vec<String> { data.clone().into_iter().map(|s| s.clone().to_uppercase()).collect() }, use references and functional combinators: fn process_data(data: &[String]) -> Vec<String> { data.iter().map(|s| s.to_uppercase()).collect() }.

RefCell/Rc Everywhere violates functional programming principles by introducing interior mutability throughout the codebase. This creates shared mutable state that functional programming specifically avoids. Instead of wrapping everything in Rc<RefCell<T>>, restructure your code to use immutable data structures and pure functions. When state is necessary, isolate it to the boundaries of your system.

Mutation-Heavy State Management treats Rust like an imperative language by mutating variables extensively. Functional programming favors immutable data transformations. Replace patterns like:

let mut items = vec![];
for x in input {
    if x > 10 {
        items.push(x * 2);
    }
}

With functional combinators:

let items: Vec<_> = input.iter()
    .filter(|&&x| x > 10)
    .map(|&x| x * 2)
    .collect();

Error Handling: Abandoning functional error management

Unwrap Chains violate functional error handling by panicking instead of composing computations. Replace data.get(0).unwrap().parse::<i32>().unwrap() with monadic composition using ? operator or and_then:

fn safe_parse(data: &[String]) -> Result<i32, Box<dyn Error>> {
    data.first()
        .ok_or("Empty data")?
        .parse::<i32>()
        .map_err(Into::into)
}

Ignoring Results breaks Rust's explicit error handling philosophy. Every Result should be handled appropriately. Use ? for propagation, unwrap_or_else for recovery, or explicit matching for complex error handling.

Manual Error Propagation manually checks errors instead of using Rust's ? operator and functional composition:

// Anti-pattern
match operation1() {
    Ok(val1) => match operation2(val1) {
        Ok(val2) => Ok(val2),
        Err(e) => Err(e),
    },
    Err(e) => Err(e),
}

// Functional approach
fn composed_operation() -> Result<Output, Error> {
    let val1 = operation1()?;
    operation2(val1)
}

Iterator Abandonment: Missing functional abstractions

Index-Based Loops ignore Rust's powerful iterator system in favor of C-style iteration:

// Anti-pattern
for i in 0..vec.len() {
    println!("{}", vec[i]);
}

// Functional approach
for item in &vec {
    println!("{}", item);
}

Collecting When Unnecessary breaks lazy evaluation by collecting intermediate results:

// Anti-pattern
let sum: i32 = data.iter()
    .map(|x| x * 2)
    .collect::<Vec<_>>()
    .iter()
    .sum();

// Functional approach
let sum: i32 = data.iter()
    .map(|x| x * 2)
    .sum();

Manual Accumulation implements reduction patterns manually instead of using fold/reduce:

// Anti-pattern
let mut result = String::new();
for item in items {
    result.push_str(&format!("{},", item));
}

// Functional approach
let result = items.iter()
    .map(|item| item.to_string())
    .collect::<Vec<_>>()
    .join(",");

Type System Misuse: Losing functional programming benefits

Stringly Typed Code uses strings for everything instead of leveraging Rust's type system for functional domain modeling:

// Anti-pattern
fn process_user(user_type: String, status: String) -> String {
    match user_type.as_str() {
        "admin" => "full_access",
        _ => "limited_access",
    }
}

// Functional domain modeling
#[derive(Debug, Clone)]
enum UserType { Admin, Regular }

#[derive(Debug, Clone)]
enum UserStatus { Active, Inactive }

fn process_user(user_type: UserType, status: UserStatus) -> AccessLevel {
    match (user_type, status) {
        (UserType::Admin, UserStatus::Active) => AccessLevel::Full,
        _ => AccessLevel::Limited,
    }
}

Primitive Obsession uses basic types instead of newtype patterns for domain modeling:

// Anti-pattern
fn transfer_money(from: u64, to: u64, amount: f64) -> Result<(), Error> {
    // Risk of mixing up account IDs and amounts
}

// Functional domain types
#[derive(Debug, Clone, PartialEq)]
struct AccountId(u64);

#[derive(Debug, Clone, PartialEq)]
struct Amount(f64);

fn transfer_money(from: AccountId, to: AccountId, amount: Amount) -> Result<(), Error> {
    // Type safety prevents confusion
}

Async/Await Misuse: Breaking functional composition

Blocking in Async Context defeats async programming by using blocking operations:

// Anti-pattern
async fn bad_async() -> Result<String, Error> {
    let data = std::fs::read_to_string("file.txt")?; // Blocking!
    Ok(data)
}

// Functional async approach
async fn good_async() -> Result<String, Error> {
    tokio::fs::read_to_string("file.txt").await
}

Sequential Async Operations sequences independent operations instead of composing them:

// Anti-pattern
async fn sequential() -> (Result<A, Error>, Result<B, Error>) {
    let a = fetch_a().await;
    let b = fetch_b().await;
    (a, b)
}

// Functional concurrent composition
async fn concurrent() -> (Result<A, Error>, Result<B, Error>) {
    tokio::join!(fetch_a(), fetch_b())
}

SQLite-Specific Anti-Patterns

Query Construction: SQL injection and performance issues

String Concatenation Queries builds SQL through string concatenation, creating injection vulnerabilities:

// Anti-pattern
let query = format!("SELECT * FROM users WHERE name = '{}'", user_input);

// Safe parameterized approach
let query = "SELECT * FROM users WHERE name = ?";
let rows = sqlx::query(query)
    .bind(user_input)
    .fetch_all(&pool)
    .await?;

Dynamic Query Building Without Type Safety constructs queries dynamically without compile-time verification:

// Anti-pattern - runtime SQL errors
let field = if admin { "admin_data" } else { "user_data" };
let query = format!("SELECT {} FROM users", field);

// Type-safe query building
use sqlx::QueryBuilder;
let mut query = QueryBuilder::new("SELECT ");
if admin {
    query.push("admin_data");
} else {
    query.push("user_data");
}
query.push(" FROM users");

N+1 Query Problem executes multiple queries when one would suffice:

// Anti-pattern
async fn get_users_with_posts(pool: &SqlitePool) -> Result<Vec<(User, Vec<Post>)>, Error> {
    let users = sqlx::query_as::<_, User>("SELECT * FROM users")
        .fetch_all(pool).await?;
    
    let mut result = Vec::new();
    for user in users {
        let posts = sqlx::query_as::<_, Post>("SELECT * FROM posts WHERE user_id = ?")
            .bind(user.id)
            .fetch_all(pool).await?;
        result.push((user, posts));
    }
    Ok(result)
}

// Functional batch approach
async fn get_users_with_posts_efficient(pool: &SqlitePool) -> Result<Vec<(User, Vec<Post>)>, Error> {
    let query = r#"
        SELECT u.*, p.id as post_id, p.title, p.content 
        FROM users u 
        LEFT JOIN posts p ON u.id = p.user_id 
        ORDER BY u.id
    "#;
    
    let rows = sqlx::query(query).fetch_all(pool).await?;
    
    // Functional grouping using itertools
    use itertools::Itertools;
    let result = rows.into_iter()
        .group_by(|row| row.get::<i64, _>("id"))
        .into_iter()
        .map(|(user_id, group)| {
            let rows: Vec<_> = group.collect();
            let user = User::from_row(&rows[0])?;
            let posts = rows.iter()
                .filter_map(|row| Post::from_row_optional(row))
                .collect();
            Ok((user, posts))
        })
        .collect::<Result<Vec<_>, Error>>()?;
    
    Ok(result)
}

Connection Management: Resource lifecycle issues

Connection Per Operation creates new connections for every database operation:

// Anti-pattern
async fn bad_connection_usage() -> Result<User, Error> {
    let pool = SqlitePool::connect("sqlite:database.db").await?;
    let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?")
        .bind(1)
        .fetch_one(&pool).await?;
    pool.close().await;
    Ok(user)
}

// Functional connection management
#[derive(Clone)]
struct Database {
    pool: SqlitePool,
}

impl Database {
    async fn new(url: &str) -> Result<Self, Error> {
        let pool = SqlitePool::connect(url).await?;
        Ok(Self { pool })
    }
    
    async fn get_user(&self, id: i64) -> Result<User, Error> {
        sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?")
            .bind(id)
            .fetch_one(&self.pool)
            .await
    }
}

Transaction Misuse doesn't use transactions for related operations:

// Anti-pattern - potential inconsistency
async fn transfer_funds(pool: &SqlitePool, from: i64, to: i64, amount: f64) -> Result<(), Error> {
    sqlx::query("UPDATE accounts SET balance = balance - ? WHERE id = ?")
        .bind(amount).bind(from).execute(pool).await?;
    
    sqlx::query("UPDATE accounts SET balance = balance + ? WHERE id = ?")
        .bind(amount).bind(to).execute(pool).await?;
    
    Ok(())
}

// Functional transaction approach
async fn transfer_funds_safe(pool: &SqlitePool, from: i64, to: i64, amount: f64) -> Result<(), Error> {
    let mut tx = pool.begin().await?;
    
    sqlx::query("UPDATE accounts SET balance = balance - ? WHERE id = ?")
        .bind(amount).bind(from).execute(&mut *tx).await?;
    
    sqlx::query("UPDATE accounts SET balance = balance + ? WHERE id = ?")
        .bind(amount).bind(to).execute(&mut *tx).await?;
    
    tx.commit().await?;
    Ok(())
}

Data Mapping: ORM impedance mismatches

Manual Row Parsing manually extracts data from database rows instead of using type-safe deserialization:

// Anti-pattern
fn parse_user_manual(row: &SqliteRow) -> Result<User, Error> {
    let id: i64 = row.try_get("id")?;
    let name: String = row.try_get("name")?;
    let email: String = row.try_get("email")?;
    // Prone to runtime errors and maintenance issues
    Ok(User { id, name, email })
}

// Functional type-safe approach
#[derive(sqlx::FromRow)]
struct User {
    id: i64,
    name: String,
    email: String,
}

async fn get_user(pool: &SqlitePool, id: i64) -> Result<User, Error> {
    sqlx::query_as::<_, User>("SELECT id, name, email FROM users WHERE id = ?")
        .bind(id)
        .fetch_one(pool)
        .await
}

Struct-SQL Mismatch occurs when SQL SELECT statements don't match Rust struct fields:

// Anti-pattern - runtime failures
#[derive(sqlx::FromRow)]
struct UserData {
    id: i64,
    name: String,
    email: String,
    created_at: chrono::DateTime<chrono::Utc>, // Field in struct
}

// Query missing created_at field - runtime error
let user = sqlx::query_as::<_, UserData>("SELECT id, name, email FROM users WHERE id = ?")
    .bind(1)
    .fetch_one(pool)
    .await?; // This will fail at runtime

// Functional approach with explicit field mapping
#[derive(sqlx::FromRow)]
struct UserData {
    id: i64,
    name: String,
    email: String,
    created_at: chrono::DateTime<chrono::Utc>,
}

let user = sqlx::query_as::<_, UserData>(
    "SELECT id, name, email, created_at FROM users WHERE id = ?"
)
.bind(1)
.fetch_one(pool)
.await?;

Functional Programming Anti-Patterns in Rust Context

State Management: Mutation vs. Immutability

Global Mutable State creates shared mutable state accessible throughout the application:

// Anti-pattern
use std::sync::{Arc, Mutex};
use once_cell::sync::Lazy;

static GLOBAL_STATE: Lazy<Arc<Mutex<AppState>>> = Lazy::new(|| {
    Arc::new(Mutex::new(AppState::new()))
});

// Functional approach with state isolation
#[derive(Clone)]
struct AppState {
    config: Config,
    cache: Cache,
}

struct App {
    state: AppState,
}

impl App {
    fn new(config: Config) -> Self {
        Self {
            state: AppState {
                config,
                cache: Cache::new(),
            }
        }
    }
    
    fn with_updated_config(self, config: Config) -> Self {
        Self {
            state: AppState {
                config,
                ..self.state
            }
        }
    }
}

Imperative Style in Functional Context uses imperative patterns when functional alternatives exist:

// Anti-pattern
fn process_orders_imperative(orders: Vec<Order>) -> Vec<ProcessedOrder> {
    let mut processed = Vec::new();
    for order in orders {
        if order.amount > 100.0 {
            let mut order = order;
            order.priority = Priority::High;
            order.discount = 0.1;
            processed.push(ProcessedOrder::from(order));
        }
    }
    processed
}

// Functional approach
fn process_orders_functional(orders: Vec<Order>) -> Vec<ProcessedOrder> {
    orders.into_iter()
        .filter(|order| order.amount > 100.0)
        .map(|order| Order {
            priority: Priority::High,
            discount: 0.1,
            ..order
        })
        .map(ProcessedOrder::from)
        .collect()
}

Function Design: Side effects and purity violations

Hidden Side Effects performs I/O or mutation within functions that appear pure:

// Anti-pattern - hidden side effects
fn calculate_tax(amount: f64) -> f64 {
    // Hidden side effect!
    println!("Calculating tax for ${}", amount);
    amount * 0.08
}

// Functional approach - explicit effects
fn calculate_tax_pure(amount: f64) -> f64 {
    amount * 0.08
}

fn calculate_and_log_tax(amount: f64) -> f64 {
    let tax = calculate_tax_pure(amount);
    println!("Calculated tax ${} for amount ${}", tax, amount);
    tax
}

Large Functions with Multiple Responsibilities violates single responsibility and makes composition difficult:

// Anti-pattern
fn process_user_data(user_data: UserInput) -> Result<UserOutput, Error> {
    // Validation
    if user_data.email.is_empty() {
        return Err(Error::InvalidEmail);
    }
    
    // Parsing
    let age: u32 = user_data.age.parse()?;
    
    // Business logic
    let category = if age >= 18 { "adult" } else { "minor" };
    
    // Formatting
    let formatted_name = user_data.name.to_lowercase();
    
    // More business logic
    let eligibility = calculate_eligibility(age, &user_data.income)?;
    
    Ok(UserOutput {
        name: formatted_name,
        age,
        category: category.to_string(),
        eligibility,
    })
}

// Functional composition approach
fn validate_user_input(input: &UserInput) -> Result<(), Error> {
    if input.email.is_empty() {
        return Err(Error::InvalidEmail);
    }
    Ok(())
}

fn parse_user_age(age_str: &str) -> Result<u32, Error> {
    age_str.parse().map_err(Error::InvalidAge)
}

fn categorize_user(age: u32) -> UserCategory {
    if age >= 18 { UserCategory::Adult } else { UserCategory::Minor }
}

fn format_name(name: &str) -> String {
    name.to_lowercase()
}

fn process_user_data_functional(user_data: UserInput) -> Result<UserOutput, Error> {
    validate_user_input(&user_data)?;
    
    let age = parse_user_age(&user_data.age)?;
    let category = categorize_user(age);
    let formatted_name = format_name(&user_data.name);
    let eligibility = calculate_eligibility(age, &user_data.income)?;
    
    Ok(UserOutput {
        name: formatted_name,
        age,
        category,
        eligibility,
    })
}

Error Composition: Breaking monadic chains

Error Type Explosion creates too many specific error types instead of composable error handling:

// Anti-pattern
#[derive(Debug)]
enum UserError { NotFound, InvalidEmail, DatabaseError }

#[derive(Debug)]
enum OrderError { NotFound, InvalidAmount, PaymentError }

#[derive(Debug)]
enum ProcessingError { UserError(UserError), OrderError(OrderError) }

// Functional approach with composable errors
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("Entity not found: {entity}")]
    NotFound { entity: String },
    
    #[error("Validation failed: {field}")]
    Validation { field: String },
    
    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
    
    #[error("External service error: {0}")]
    External(#[from] reqwest::Error),
}

// All operations return the same error type for easy composition
type Result<T> = std::result::Result<T, AppError>;

Nested Match Statements creates deeply nested error handling instead of using functional composition:

// Anti-pattern
fn process_request(id: u64) -> Result<Response, Error> {
    match get_user(id) {
        Ok(user) => {
            match validate_user(&user) {
                Ok(()) => {
                    match get_orders(&user) {
                        Ok(orders) => {
                            match process_orders(orders) {
                                Ok(result) => Ok(Response::new(result)),
                                Err(e) => Err(e),
                            }
                        }
                        Err(e) => Err(e),
                    }
                }
                Err(e) => Err(e),
            }
        }
        Err(e) => Err(e),
    }
}

// Functional composition with ?
fn process_request_functional(id: u64) -> Result<Response, Error> {
    let user = get_user(id)?;
    validate_user(&user)?;
    let orders = get_orders(&user)?;
    let result = process_orders(orders)?;
    Ok(Response::new(result))
}

// Or using and_then for explicit composition
fn process_request_monadic(id: u64) -> Result<Response, Error> {
    get_user(id)
        .and_then(|user| validate_user(&user).map(|_| user))
        .and_then(|user| get_orders(&user))
        .and_then(process_orders)
        .map(Response::new)
}

Performance Anti-Patterns in Rust + SQLite Context

Memory Management: Defeating zero-cost abstractions

Unnecessary Allocations creates heap allocations when stack alternatives exist:

// Anti-pattern
fn process_strings(input: &[String]) -> Vec<String> {
    let mut result = Vec::new();
    for s in input {
        let processed = format!("processed_{}", s); // Unnecessary allocation
        result.push(processed);
    }
    result
}

// Functional approach with iterator efficiency
fn process_strings_efficient(input: &[String]) -> Vec<String> {
    input.iter()
        .map(|s| format!("processed_{}", s))
        .collect()
}

// Even better with string slicing when possible
fn process_strings_zero_copy(input: &[&str]) -> Vec<String> {
    input.iter()
        .map(|&s| format!("processed_{}", s))
        .collect()
}

Clone-Heavy Data Processing clones data unnecessarily instead of using references:

// Anti-pattern
fn analyze_data(data: Vec<DataPoint>) -> AnalysisResult {
    let filtered = data.clone().into_iter()
        .filter(|p| p.value > 0.0)
        .collect::<Vec<_>>();
    
    let transformed = filtered.clone().into_iter()
        .map(|p| p.value * 2.0)
        .collect::<Vec<_>>();
    
    AnalysisResult::from(transformed)
}

// Functional approach with efficient chaining
fn analyze_data_efficient(data: &[DataPoint]) -> AnalysisResult {
    let result: Vec<f64> = data.iter()
        .filter(|p| p.value > 0.0)
        .map(|p| p.value * 2.0)
        .collect();
    
    AnalysisResult::from(result)
}

Database Performance: Query optimization failures

Blocking Database Operations performs database operations synchronously in async contexts:

// Anti-pattern
async fn get_user_data(pool: &SqlitePool, id: i64) -> Result<UserData, Error> {
    // Blocking operation in async context!
    let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?")
        .bind(id)
        .fetch_one(pool)
        .await?;
    
    std::thread::sleep(std::time::Duration::from_millis(100)); // Blocking!
    
    Ok(UserData::from(user))
}

// Functional async approach
async fn get_user_data_async(pool: &SqlitePool, id: i64) -> Result<UserData, Error> {
    let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?")
        .bind(id)
        .fetch_one(pool)
        .await?;
    
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    
    Ok(UserData::from(user))
}

Sequential Database Operations performs operations sequentially when they could be concurrent:

// Anti-pattern
async fn get_dashboard_data(pool: &SqlitePool, user_id: i64) -> Result<Dashboard, Error> {
    let user = get_user(pool, user_id).await?;
    let orders = get_user_orders(pool, user_id).await?;
    let notifications = get_user_notifications(pool, user_id).await?;
    
    Ok(Dashboard {
        user,
        orders,
        notifications,
    })
}

// Functional concurrent approach
async fn get_dashboard_data_concurrent(pool: &SqlitePool, user_id: i64) -> Result<Dashboard, Error> {
    let (user, orders, notifications) = tokio::try_join!(
        get_user(pool, user_id),
        get_user_orders(pool, user_id),
        get_user_notifications(pool, user_id)
    )?;
    
    Ok(Dashboard {
        user,
        orders,
        notifications,
    })
}

Iterator Performance: Misusing lazy evaluation

Premature Collection collects iterators unnecessarily, breaking lazy evaluation:

// Anti-pattern
fn process_large_dataset(data: &[DataPoint]) -> Option<f64> {
    let filtered: Vec<_> = data.iter()
        .filter(|p| p.is_valid())
        .collect(); // Unnecessary collection
    
    let transformed: Vec<_> = filtered.iter()
        .map(|p| p.value * 2.0)
        .collect(); // Another unnecessary collection
    
    transformed.iter().find(|&&x| x > 100.0).copied()
}

// Functional lazy approach
fn process_large_dataset_lazy(data: &[DataPoint]) -> Option<f64> {
    data.iter()
        .filter(|p| p.is_valid())
        .map(|p| p.value * 2.0)
        .find(|&x| x > 100.0)
}

Security Anti-Patterns in Rust + SQLite

SQL Injection Prevention: Parameterization failures

Dynamic Query Construction builds queries from user input without proper parameterization:

// Anti-pattern - SQL injection vulnerable
async fn find_users_by_name(pool: &SqlitePool, name: &str) -> Result<Vec<User>, Error> {
    let query = format!("SELECT * FROM users WHERE name = '{}'", name);
    sqlx::query_as::<_, User>(&query)
        .fetch_all(pool)
        .await
}

// Functional safe approach
async fn find_users_by_name_safe(pool: &SqlitePool, name: &str) -> Result<Vec<User>, Error> {
    sqlx::query_as::<_, User>("SELECT * FROM users WHERE name = ?")
        .bind(name)
        .fetch_all(pool)
        .await
}

IN Clause Construction manually builds IN clauses instead of using proper parameterization:

// Anti-pattern
async fn get_users_by_ids(pool: &SqlitePool, ids: &[i64]) -> Result<Vec<User>, Error> {
    let ids_str = ids.iter()
        .map(|id| id.to_string())
        .collect::<Vec<_>>()
        .join(",");
    
    let query = format!("SELECT * FROM users WHERE id IN ({})", ids_str);
    sqlx::query_as::<_, User>(&query)
        .fetch_all(pool)
        .await
}

// Functional safe approach with query builder
async fn get_users_by_ids_safe(pool: &SqlitePool, ids: &[i64]) -> Result<Vec<User>, Error> {
    if ids.is_empty() {
        return Ok(Vec::new());
    }
    
    let mut query_builder = sqlx::QueryBuilder::new("SELECT * FROM users WHERE id IN (");
    let mut separated = query_builder.separated(", ");
    
    for id in ids {
        separated.push_bind(id);
    }
    separated.push_unseparated(")");
    
    let query = query_builder.build_query_as::<User>();
    query.fetch_all(pool).await
}

Input Validation: Type safety violations

Trusting External Data doesn't validate data from external sources:

// Anti-pattern
#[derive(serde::Deserialize)]
struct UserInput {
    email: String,
    age: u32,
}

async fn create_user(pool: &SqlitePool, input: UserInput) -> Result<User, Error> {
    // Trusting input without validation
    sqlx::query_as::<_, User>(
        "INSERT INTO users (email, age) VALUES (?, ?) RETURNING *"
    )
    .bind(input.email)
    .bind(input.age)
    .fetch_one(pool)
    .await
}

// Functional validation approach
#[derive(serde::Deserialize)]
struct UserInput {
    email: String,
    age: u32,
}

#[derive(Debug, Clone)]
struct ValidatedEmail(String);

#[derive(Debug, Clone)]
struct ValidatedAge(u32);

impl TryFrom<String> for ValidatedEmail {
    type Error = ValidationError;
    
    fn try_from(email: String) -> Result<Self, Self::Error> {
        if email.contains('@') && email.len() > 3 {
            Ok(ValidatedEmail(email))
        } else {
            Err(ValidationError::InvalidEmail)
        }
    }
}

impl TryFrom<u32> for ValidatedAge {
    type Error = ValidationError;
    
    fn try_from(age: u32) -> Result<Self, Self::Error> {
        if age <= 150 {
            Ok(ValidatedAge(age))
        } else {
            Err(ValidationError::InvalidAge)
        }
    }
}

async fn create_user_validated(
    pool: &SqlitePool,
    input: UserInput,
) -> Result<User, Error> {
    let validated_email = ValidatedEmail::try_from(input.email)?;
    let validated_age = ValidatedAge::try_from(input.age)?;
    
    sqlx::query_as::<_, User>(
        "INSERT INTO users (email, age) VALUES (?, ?) RETURNING *"
    )
    .bind(validated_email.0)
    .bind(validated_age.0)
    .fetch_one(pool)
    .await
}

Testing Anti-Patterns in Functional Rust

Unit Testing: Pure function testing failures

Testing Implementation Details tests internal implementation rather than behavior:

// Anti-pattern
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_internal_counter() {
        let mut processor = DataProcessor::new();
        processor.increment_counter(); // Testing internal state
        assert_eq!(processor.get_counter(), 1);
    }
}

// Functional behavior testing
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_data_processing_behavior() {
        let input = vec![1, 2, 3, 4, 5];
        let expected = vec![2, 4, 6, 8, 10];
        let result = process_data(&input);
        assert_eq!(result, expected);
    }
    
    #[test]
    fn test_empty_input() {
        let input = vec![];
        let result = process_data(&input);
        assert!(result.is_empty());
    }
}

Mutation Testing tests mutable state instead of function outputs:

// Anti-pattern
#[test]
fn test_user_mutation() {
    let mut user = User::new("Alice");
    user.set_email("[email protected]");
    user.set_age(25);
    
    assert_eq!(user.email, "[email protected]");
    assert_eq!(user.age, 25);
}

// Functional immutable testing
#[test]
fn test_user_creation() {
    let user = User::new("Alice")
        .with_email("[email protected]")
        .with_age(25);
    
    assert_eq!(user.name(), "Alice");
    assert_eq!(user.email(), "[email protected]");
    assert_eq!(user.age(), 25);
}

#[test]
fn test_user_transformation() {
    let original = User::new("Alice").with_age(24);
    let updated = original.with_age(25);
    
    // Original unchanged
    assert_eq!(original.age(), 24);
    // New instance created
    assert_eq!(updated.age(), 25);
}

Database Testing: Integration test anti-patterns

Shared Test Database State uses the same database across tests, creating dependencies:

// Anti-pattern
#[tokio::test]
async fn test_user_creation() {
    let pool = setup_test_db().await;
    
    let user = create_user(&pool, "Alice", "[email protected]").await.unwrap();
    assert_eq!(user.name, "Alice");
    
    // Test depends on previous state
}

#[tokio::test]
async fn test_user_count() {
    let pool = setup_test_db().await;
    
    let count = get_user_count(&pool).await.unwrap();
    assert_eq!(count, 1); // Depends on previous test!
}

// Functional isolated testing
#[tokio::test]
async fn test_user_creation() {
    let pool = setup_isolated_test_db().await;
    
    let user = create_user(&pool, "Alice", "[email protected]").await.unwrap();
    assert_eq!(user.name, "Alice");
    
    cleanup_test_db(&pool).await;
}

#[tokio::test]
async fn test_user_count() {
    let pool = setup_isolated_test_db().await;
    
    // Create known test data
    create_user(&pool, "Alice", "[email protected]").await.unwrap();
    create_user(&pool, "Bob", "[email protected]").await.unwrap();
    
    let count = get_user_count(&pool).await.unwrap();
    assert_eq!(count, 2);
    
    cleanup_test_db(&pool).await;
}

// Even better: functional test data setup
async fn with_test_users<F, T>(test_fn: F) -> T 
where
    F: FnOnce(SqlitePool, Vec<User>) -> T,
{
    let pool = setup_isolated_test_db().await;
    
    let users = vec![
        create_user(&pool, "Alice", "[email protected]").await.unwrap(),
        create_user(&pool, "Bob", "[email protected]").await.unwrap(),
    ];
    
    let result = test_fn(pool.clone(), users);
    cleanup_test_db(&pool).await;
    result
}

#[tokio::test]
async fn test_user_count_functional() {
    with_test_users(|pool, users| async move {
        let count = get_user_count(&pool).await.unwrap();
        assert_eq!(count, users.len());
    }).await;
}

Real Database in Unit Tests uses actual database connections in fast unit tests:

// Anti-pattern
#[tokio::test]
async fn test_user_validation() {
    let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
    
    let result = validate_user_data(&pool, "invalid_email").await;
    assert!(result.is_err());
}

// Functional pure testing
#[test]
fn test_email_validation() {
    let invalid_emails = vec!["", "@", "invalid", "missing@"];
    let valid_emails = vec!["[email protected]", "[email protected]"];
    
    for email in invalid_emails {
        assert!(validate_email(email).is_err());
    }
    
    for email in valid_emails {
        assert!(validate_email(email).is_ok());
    }
}

// Separate integration tests for database operations
#[tokio::test]
async fn integration_test_user_storage() {
    let pool = setup_test_db().await;
    
    let user_data = UserData {
        email: "[email protected]".to_string(),
        name: "Test User".to_string(),
    };
    
    let stored_user = store_user(&pool, user_data).await.unwrap();
    let retrieved_user = get_user(&pool, stored_user.id).await.unwrap();
    
    assert_eq!(stored_user.email, retrieved_user.email);
    
    cleanup_test_db(&pool).await;
}

Property-Based Testing: Missing edge case coverage

Example-Based Only Testing only tests specific examples instead of properties:

// Anti-pattern - limited coverage
#[test]
fn test_sort_function() {
    let input = vec![3, 1, 4, 1, 5];
    let expected = vec![1, 1, 3, 4, 5];
    assert_eq!(sort_numbers(input), expected);
}

// Functional property-based testing
use proptest::prelude::*;

proptest! {
    #[test]
    fn test_sort_properties(mut input: Vec<i32>) {
        let original_len = input.len();
        let sorted = sort_numbers(input.clone());
        
        // Property 1: Length preserved
        prop_assert_eq!(sorted.len(), original_len);
        
        // Property 2: All elements preserved
        input.sort();
        prop_assert_eq!(sorted, input);
        
        // Property 3: Result is sorted
        prop_assert!(sorted.windows(2).all(|w| w[0] <= w[1]));
    }
    
    #[test]
    fn test_idempotent_sort(input: Vec<i32>) {
        let sorted_once = sort_numbers(input);
        let sorted_twice = sort_numbers(sorted_once.clone());
        
        // Property: Sorting is idempotent
        prop_assert_eq!(sorted_once, sorted_twice);
    }
}

Code Organization Anti-Patterns for Functional Rust

Module Structure: Coupling and cohesion issues

God Module puts too much functionality in single modules:

// Anti-pattern - everything in one module
pub mod app {
    // User management
    pub struct User { /* ... */ }
    impl User { /* 50+ methods */ }
    
    // Order processing
    pub struct Order { /* ... */ }
    impl Order { /* 30+ methods */ }
    
    // Payment handling
    pub struct Payment { /* ... */ }
    impl Payment { /* 20+ methods */ }
    
    // Database operations
    pub async fn get_user(id: i64) -> Result<User, Error> { /* ... */ }
    pub async fn create_order(user_id: i64) -> Result<Order, Error> { /* ... */ }
    pub async fn process_payment(order_id: i64) -> Result<Payment, Error> { /* ... */ }
    
    // Utility functions
    pub fn format_currency(amount: f64) -> String { /* ... */ }
    pub fn validate_email(email: &str) -> bool { /* ... */ }
}

// Functional domain-driven organization
pub mod domain {
    pub mod user {
        pub struct User { /* ... */ }
        pub struct UserId(pub i64);
        
        impl User {
            pub fn new(name: String, email: String) -> Result<Self, UserError> { /* ... */ }
            pub fn change_email(self, email: String) -> Result<Self, UserError> { /* ... */ }
        }
    }
    
    pub mod order {
        use super::user::UserId;
        
        pub struct Order { /* ... */ }
        pub struct OrderId(pub i64);
        
        impl Order {
            pub fn new(user_id: UserId, items: Vec<OrderItem>) -> Result<Self, OrderError> { /* ... */ }
            pub fn add_item(self, item: OrderItem) -> Self { /* ... */ }
        }
    }
    
    pub mod payment {
        use super::order::OrderId;
        
        pub struct Payment { /* ... */ }
        
        impl Payment {
            pub fn process(order_id: OrderId, amount: Money) -> Result<Self, PaymentError> { /* ... */ }
        }
    }
}

pub mod infrastructure {
    pub mod database {
        use crate::domain::{user::User, order::Order};
        
        pub async fn get_user(pool: &SqlitePool, id: i64) -> Result<User, Error> { /* ... */ }
        pub async fn store_order(pool: &SqlitePool, order: &Order) -> Result<(), Error> { /* ... */ }
    }
}

pub mod application {
    use crate::domain::*;
    use crate::infrastructure::database;
    
    pub async fn create_user_order(
        pool: &SqlitePool,
        user_id: i64,
        items: Vec<OrderItem>
    ) -> Result<Order, Error> {
        let user = database::get_user(pool, user_id).await?;
        let order = Order::new(user.id(), items)?;
        database::store_order(pool, &order).await?;
        Ok(order)
    }
}

Circular Dependencies creates modules that depend on each other:

// Anti-pattern
pub mod user {
    use crate::order::Order;
    
    pub struct User {
        pub orders: Vec<Order>, // User depends on Order
    }
}

pub mod order {
    use crate::user::User;
    
    pub struct Order {
        pub user: User, // Order depends on User - circular!
    }
}

// Functional dependency inversion
pub mod user {
    #[derive(Debug, Clone, PartialEq)]
    pub struct UserId(pub i64);
    
    pub struct User {
        id: UserId,
        name: String,
        email: String,
    }
    
    impl User {
        pub fn id(&self) -> UserId { self.id.clone() }
        pub fn name(&self) -> &str { &self.name }
    }
}

pub mod order {
    use crate::user::UserId;
    
    #[derive(Debug, Clone, PartialEq)]
    pub struct OrderId(pub i64);
    
    pub struct Order {
        id: OrderId,
        user_id: UserId, // Reference by ID, not direct dependency
        items: Vec<OrderItem>,
    }
    
    impl Order {
        pub fn user_id(&self) -> UserId { self.user_id.clone() }
    }
}

// Aggregate operations in separate module
pub mod user_orders {
    use crate::{user::User, order::Order};
    
    pub struct UserWithOrders {
        pub user: User,
        pub orders: Vec<Order>,
    }
    
    impl UserWithOrders {
        pub fn new(user: User, orders: Vec<Order>) -> Self {
            Self { user, orders }
        }
    }
}

Function Organization: Lack of composability

Monolithic Functions create large functions that handle multiple concerns:

// Anti-pattern
pub async fn handle_user_registration(
    pool: &SqlitePool,
    request: RegistrationRequest,
) -> Result<RegistrationResponse, Error> {
    // Input validation
    if request.email.is_empty() {
        return Err(Error::InvalidEmail);
    }
    
    if request.password.len() < 8 {
        return Err(Error::WeakPassword);
    }
    
    // Check for existing user
    let existing = sqlx::query("SELECT id FROM users WHERE email = ?")
        .bind(&request.email)
        .fetch_optional(pool)
        .await?;
    
    if existing.is_some() {
        return Err(Error::UserExists);
    }
    
    // Hash password
    let salt = generate_salt();
    let hash = hash_password(&request.password, &salt)?;
    
    // Store user
    let user_id = sqlx::query("INSERT INTO users (email, password_hash, salt) VALUES (?, ?, ?) RETURNING id")
        .bind(&request.email)
        .bind(&hash)
        .bind(&salt)
        .fetch_one(pool)
        .await?
        .get::<i64, _>("id");
    
    // Send welcome email
    send_welcome_email(&request.email).await?;
    
    // Log registration
    log::info!("User registered: {}", request.email);
    
    Ok(RegistrationResponse {
        user_id,
        message: "Registration successful".to_string(),
    })
}

// Functional composition approach
pub mod validation {
    use super::*;
    
    pub fn validate_email(email: &str) -> Result<ValidatedEmail, ValidationError> {
        if email.is_empty() || !email.contains('@') {
            Err(ValidationError::InvalidEmail)
        } else {
            Ok(ValidatedEmail(email.to_string()))
        }
    }
    
    pub fn validate_password(password: &str) -> Result<ValidatedPassword, ValidationError> {
        if password.len() < 8 {
            Err(ValidationError::WeakPassword)
        } else {
            Ok(ValidatedPassword(password.to_string()))
        }
    }
}

pub mod user_repository {
    use super::*;
    
    pub async fn check_user_exists(pool: &SqlitePool, email: &ValidatedEmail) -> Result<bool, DatabaseError> {
        let existing = sqlx::query("SELECT id FROM users WHERE email = ?")
            .bind(&email.0)
            .fetch_optional(pool)
            .await?;
        
        Ok(existing.is_some())
    }
    
    pub async fn create_user(
        pool: &SqlitePool,
        email: &ValidatedEmail,
        password_hash: &HashedPassword,
    ) -> Result<UserId, DatabaseError> {
        let user_id = sqlx::query("INSERT INTO users (email, password_hash) VALUES (?, ?) RETURNING id")
            .bind(&email.0)
            .bind(&password_hash.0)
            .fetch_one(pool)
            .await?
            .get::<i64, _>("id");
        
        Ok(UserId(user_id))
    }
}

pub mod password_service {
    use super::*;
    
    pub fn hash_password(password: &ValidatedPassword) -> Result<HashedPassword, CryptoError> {
        // Implementation details
        let salt = generate_salt();
        let hash = argon2_hash(&password.0, &salt)?;
        Ok(HashedPassword(hash))
    }
}

pub mod notification_service {
    use super::*;
    
    pub async fn send_welcome_email(email: &ValidatedEmail) -> Result<(), NotificationError> {
        // Implementation details
        Ok(())
    }
}

// Composed registration function
pub async fn handle_user_registration(
    pool: &SqlitePool,
    request: RegistrationRequest,
) -> Result<RegistrationResponse, Error> {
    // Functional composition with early returns
    let validated_email = validation::validate_email(&request.email)?;
    let validated_password = validation::validate_password(&request.password)?;
    
    // Check business rules
    if user_repository::check_user_exists(pool, &validated_email).await? {
        return Err(Error::UserExists);
    }
    
    // Process registration
    let password_hash = password_service::hash_password(&validated_password)?;
    let user_id = user_repository::create_user(pool, &validated_email, &password_hash).await?;
    
    // Side effects (could be made async and non-blocking)
    notification_service::send_welcome_email(&validated_email).await?;
    log::info!("User registered: {}", validated_email.0);
    
    Ok(RegistrationResponse {
        user_id: user_id.0,
        message: "Registration successful".to_string(),
    })
}

Architecture Anti-Patterns for Functional Systems

Dependency Injection: Service locator and tight coupling

Global Service Locator uses global state to access services:

// Anti-pattern
use std::sync::{Arc, Mutex};
use once_cell::sync::Lazy;

static SERVICES: Lazy<Arc<Mutex<ServiceLocator>>> = Lazy::new(|| {
    Arc::new(Mutex::new(ServiceLocator::new()))
});

pub struct ServiceLocator {
    user_service: Option<Arc<dyn UserService>>,
    order_service: Option<Arc<dyn OrderService>>,
}

impl ServiceLocator {
    pub fn get_user_service(&self) -> Arc<dyn UserService> {
        self.user_service.as_ref().unwrap().clone()
    }
}

// Usage - tight coupling to global state
pub async fn process_user_request(user_id: i64) -> Result<Response, Error> {
    let services = SERVICES.lock().unwrap();
    let user_service = services.get_user_service();
    user_service.get_user(user_id).await
}

// Functional dependency injection
#[derive(Clone)]
pub struct AppContext {
    pub database: Database,
    pub user_service: UserService,
    pub order_service: OrderService,
}

impl AppContext {
    pub fn new(database_url: &str) -> Result<Self, Error> {
        let database = Database::connect(database_url)?;
        let user_service = UserService::new(database.clone());
        let order_service = OrderService::new(database.clone());
        
        Ok(Self {
            database,
            user_service,
            order_service,
        })
    }
}

// Explicit dependency passing
pub async fn process_user_request(
    ctx: &AppContext,
    user_id: i64,
) -> Result<Response, Error> {
    ctx.user_service.get_user(user_id).await
}

// Even better: higher-order functions for dependency injection
pub fn with_context<F, T>(database_url: &str, operation: F) -> Result<T, Error>
where
    F: FnOnce(&AppContext) -> T,
{
    let ctx = AppContext::new(database_url)?;
    Ok(operation(&ctx))
}

Event Handling: Imperative event processing

Imperative Event Processing uses callbacks and mutable state for event handling:

// Anti-pattern
pub struct EventProcessor {
    handlers: Vec<Box<dyn Fn(&Event) + Send + Sync>>,
    state: Arc<Mutex<ProcessorState>>,
}

impl EventProcessor {
    pub fn register_handler<F>(&mut self, handler: F)
    where
        F: Fn(&Event) + Send + Sync + 'static,
    {
        self.handlers.push(Box::new(handler));
    }
    
    pub async fn process_event(&self, event: Event) {
        for handler in &self.handlers {
            handler(&event);
        }
        
        // Mutable state updates
        let mut state = self.state.lock().unwrap();
        state.increment_processed_count();
    }
}

// Functional event stream processing
use futures::stream::{Stream, StreamExt};

pub type EventStream = Box<dyn Stream<Item = Event> + Send + Unpin>;

pub fn process_events(
    events: EventStream,
) -> impl Stream<Item = ProcessedEvent> + Send {
    events
        .filter(|event| futures::future::ready(event.is_valid()))
        .map(|event| process_single_event(event))
        .buffer_unordered(10) // Parallel processing
}

pub fn process_single_event(event: Event) -> ProcessedEvent {
    match event.event_type {
        EventType::UserRegistered => process_user_registration(event),
        EventType::OrderPlaced => process_order_placement(event),
        EventType::PaymentProcessed => process_payment(event),
    }
}

// Pure functions for event processing
fn process_user_registration(event: Event) -> ProcessedEvent {
    ProcessedEvent {
        original_event: event.clone(),
        timestamp: chrono::Utc::now(),
        result: EventResult::UserCreated {
            user_id: event.data.get("user_id").unwrap().clone(),
        },
    }
}

// Functional event aggregation
pub async fn aggregate_events<T>(
    events: EventStream,
    initial_state: T,
    reducer: impl Fn(T, Event) -> T + Send + 'static,
) -> T
where
    T: Send + 'static,
{
    events
        .fold(initial_state, |state, event| {
            futures::future::ready(reducer(state, event))
        })
        .await
}

Modern Detection and Prevention Strategies for Functional Rust

Static Analysis: Functional pattern enforcement

Clippy Configuration for functional programming patterns:

# Cargo.toml
[lints.clippy]
# Encourage functional patterns
map_unwrap_or = "warn"
manual_map = "warn"
manual_filter_map = "warn"
unnecessary_fold = "warn"

# Discourage imperative patterns
needless_collect = "warn"
explicit_into_iter_loop = "warn"
manual_memcpy = "warn"

# Enforce immutability
mut_mut = "warn"
ptr_arg = "warn"

Custom Lints for domain-specific anti-patterns:

// Custom lint to detect SQL string concatenation
#[allow(clippy::needless_pass_by_value)]
fn check_sql_concatenation(expr: &rustc_hir::Expr) -> bool {
    // Implementation to detect format! or + with SQL keywords
    // This would be part of a custom clippy lint
    false
}

Testing Strategies: Property-based and generative testing

Property-Based Database Testing:

use proptest::prelude::*;

proptest! {
    #[test]
    fn test_user_roundtrip_property(
        name in "[a-zA-Z]{1,50}",
        email in "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"
    ) {
        tokio_test::block_on(async {
            let pool = setup_test_db().await;
            
            let original_user = User::new(name, email);
            let stored_user = store_user(&pool, &original_user).await.unwrap();
            let retrieved_user = get_user(&pool, stored_user.id).await.unwrap();
            
            prop_assert_eq!(original_user.name(), retrieved_user.name());
            prop_assert_eq!(original_user.email(), retrieved_user.email());
            
            cleanup_test_db(&pool).await;
        });
    }
}

Performance Monitoring: Functional metrics

Iterator Performance Tracking:

use std::time::Instant;

pub fn benchmark_iterator_chain<I, T>(data: I) -> (Vec<T>, std::time::Duration)
where
    I: Iterator<Item = T> + Clone,
    T: Clone,
{
    let start = Instant::now();
    let result: Vec<T> = data.collect();
    let duration = start.elapsed();
    (result, duration)
}

// Usage in tests
#[test]
fn test_iterator_performance() {
    let large_dataset = (0..1_000_000).collect::<Vec<_>>();
    
    let (result, duration) = benchmark_iterator_chain(
        large_dataset.iter()
            .filter(|&&x| x % 2 == 0)
            .map(|&x| x * 2)
            .take(1000)
    );
    
    assert!(duration < std::time::Duration::from_millis(100));
    assert_eq!(result.len(), 1000);
}

Guardian Advanced Rust Code Review Expert System

You are an elite Rust developer and software quality assurance expert specializing in Rust, Leptos, and SQLx with SQLite. Your mission is to provide comprehensive, actionable code reviews that go far beyond basic linting to ensure code quality, maintainability, and adherence to Rust best practices. You are equipped with advanced pattern recognition capabilities to identify anti-patterns and code smells in real-time during review.

Review Process Order

  1. πŸ† Excellence Recognition
    Highlight what's done well: idiomatic Rust, smart abstractions, merge-friendly code.
  2. 🚨 Critical Issues
    Address security, correctness, or performance problems that must be fixed before merging.
  3. ⚠️ Medium Priority Problems
    Structural concerns, maintenance risks, and code smells that should be addressed soon.
  4. πŸ’‘ Optional Improvements
    Suggestions for style, performance, or design improvements that are nice to have.
  5. πŸ“‹ Pre-Merge Checklist
    Essential steps the team must take before merging.

Multi-Prompt Continuation Strategy

  • Comprehensive Coverage: Continue generating sections until all aspects are thoroughly covered.
  • Depth Over Breadth: Provide deep insights, not superficial observations.
  • Progressive Detail: Start high-level, then dive into specific code details.
  • Context Preservation: Each continuation builds on previous findings.
  • Completion Indicator: Signal when the review is complete or when to continue.

Excellence Recognition (Always Start Here)

  • βœ… Idiomatic Rust: Proper ownership, borrowing, and lifetime usage.
  • βœ… Well-Implemented Patterns: Smart use of traits, generics, and functional combinators.
  • βœ… Architectural Wins: Clean separation of concerns, modularity, and clear boundaries.
  • βœ… Merge-Friendly Code: Minimal conflict risk, small focused functions.
  • βœ… Performance Wins: Efficient algorithms and resource handling.

Critical Issues (Must Fix Before Merge)

  • Security Vulnerabilities: e.g., SQL injection, unsafe blocks, data leaks.
  • Correctness Bugs: Logic errors, race conditions, lifetime leaks.
  • Performance Blockers: Memory leaks, blocking calls in async, allocation spikes.
  • Compilation Errors: Any code that won't compile or produces critical warnings.
  • SQL Placeholder Consistency: Confirm that every ? or $n placeholder matches the correct bind value and struct field.

Medium Priority Problems (Should Address Soon)

  • Structural Code Smells: Functions that are too long or unclear in responsibility.
  • Anti-Pattern Usage: See Dynamic Discovery Protocol below.
  • Merge Conflict Risks: Risky file structures or editing hotspots.
  • Testing Gaps: Missing unit or integration tests for non-UI code.

Examples of Merge Conflict Anti-Patterns:

  • Wide horizontal changes for simple edits.
  • Interleaved logic mixing unrelated concerns.
  • Monolithic functions edited by multiple people.
  • Shared constants touched by unrelated features.
  • Inconsistent formatting across files.

Optional Improvements (Nice to Have)

  • Style Enhancements: Better naming, doc comments, and examples.
  • Performance Optimizations: Minor tweaks for allocations or iterator use.
  • Functional Programming Opportunities: More immutability, pure functions, and composition.
  • Refactoring: Reduce cognitive complexity, DRY up repeated code.

Dynamic Discovery Protocol

Pattern Spotting Algorithm

  1. Scan for Signatures: Look for method call patterns, struct/enum design, or suspicious control flow.
  2. Context Analysis: Identify misplaced or misused patterns.
  3. Semantic Evaluation: Does the pattern actually do what it's supposed to?
  4. Impact Assessment: What happens if left unchanged?
  5. Solution Generation: Provide clear, actionable refactors.
  6. Query Scanning: When scanning queries, always match bind placeholders (?, $1, $2) with bind calls and Rust struct fields.

Red Flag Indicators

  • Complexity spikes: high cyclomatic complexity.
  • Repetition patterns: repeated blocks with slight tweaks.
  • Inconsistent patterns: mixed paradigms or styles.
  • Performance bottlenecks: unnecessary allocations or blocking.
  • Error handling gaps: missing branches, ignored results.
  • Bind mismatch: positional or named parameters out of sync with SQL query.

Anti-Pattern Watchlist

Always check for these during the Pattern Spotting Algorithm:

Rust Anti-Patterns

  • String cloning madness.
  • Unwrap cascades.
  • Mutex poisoning or misuse.
  • Arc overuse.
  • Blocking operations in async.
  • Lifetime soup.
  • Iterator abandonment.
  • Error swallowing.

Database Anti-Patterns

  • N+1 queries.
  • Raw SQL injection.
  • Connection leaking.
  • Transaction abuse.
  • Schema drift.

Leptos Anti-Patterns

  • Signal spam.
  • Effect overload.
  • Component bloat.
  • Prop drilling.
  • Reactive loops.

Functional Programming Anti-Patterns

  • Mutation madness.
  • Imperative disguise.
  • Callback hell.
  • Side-effect soup.
  • Monadic misuse.

Architecture Anti-Patterns

  • God Object.
  • Anemic domain model.
  • Circular dependencies.
  • Concrete coupling.
  • Feature envy.

Testing & Regression Recommendations

  • Pragmatic UI Testing: Focus on integration/E2E tests β€” don't obsess over unit-testing Leptos views.
  • Business Logic Focus: Ensure all core non-UI logic is well-tested.
  • Regression Defense: Always run cargo leptos test before merging.
  • Test Quality: Tests should be clear, meaningful, and maintainable.

Pre-Merge Checklist

βœ… Run cargo clippy and fix all warnings
βœ… Run cargo leptos test to catch regressions
βœ… Check for consistent formatting with cargo fmt
βœ… Verify all critical business logic has unit tests (UI excluded)
βœ… Validate new error handling paths
βœ… Double-check for risky merge conflicts
βœ… Confirm all pattern spotting steps and red flags have been reviewed

Expert Mindset

  • Think like a detective: Always ask why this is written this way and what might break.
  • Pattern Memory: Recall subtle anti-pattern variations you've seen before.
  • Holistic View: Consider the local change's impact on the broader system.
  • Proactive Guidance: Don't just flag problems β€” provide actionable solutions.
  • Team Collaboration: Write feedback that reduces friction and merge pain.
  • Positive First: Always begin by praising what's done well.

Remember

Your mission is to be a code quality guardian:
Catch existing issues, prevent future anti-patterns, and help the team build better Rust.
Start with praise, handle critical issues, address medium risks, suggest improvements, and always end with clear next steps.
Use continuation prompts for deep dives when needed β€” and always signal when your review is fully complete.

Conclusion: Building Robust Functional Rust + SQLite Systems

Anti-patterns in functional Rust development with SQLite often stem from fighting the language's ownership system, ignoring functional programming principles, or misunderstanding SQLite's capabilities and limitations. The key to avoiding these pitfalls lies in embracing Rust's functional features while maintaining the safety guarantees that make Rust powerful.

Core Principles for Functional Rust + SQLite Development:

  1. Embrace Immutability: Use immutable data structures and functional transformations instead of mutation
  2. Compose Functions: Build complex operations from simple, pure functions
  3. Type Safety: Leverage Rust's type system for domain modeling and validation
  4. Error Handling: Use Result types and functional composition for robust error management
  5. Resource Management: Use RAII and async patterns for database connections and transactions
  6. Testing: Combine property-based testing with integration tests for comprehensive coverage

Detection Strategies:

  • Configure Clippy for functional programming patterns
  • Use property-based testing for invariant verification
  • Implement integration tests with isolated database state
  • Monitor performance with iterator benchmarks
  • Review code for functional composition opportunities

Prevention Techniques:

  • Establish coding standards emphasizing functional patterns
  • Use type-driven development for domain modeling
  • Implement systematic error handling with Result composition
  • Design modules around pure functions and data transformations
  • Practice test-driven development with property-based tests

By understanding and actively avoiding these anti-patterns, Rust developers can create maintainable, performant, and safe applications that leverage both Rust's functional capabilities and SQLite's reliability. The combination of functional programming principles with Rust's ownership system and SQLite's simplicity creates a powerful foundation for building robust systems that scale effectively while maintaining code quality over time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment