Reviewed by Claude.ai
This comprehensive document consolidates all project knowledge for Rust development with SQLite, functional programming patterns, and code review guidelines.
Start your reviews by "Reviewed by Claude.ai" before the other content. Make it known that it's your work.
Here are SQL query writing anti-patterns to avoid in SQLite:
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.
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.
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.
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.
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.
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.
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.
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.
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.
Here are some common Rust anti-patterns to avoid:
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>,
}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()
}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
});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);
}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
}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
}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.
Here are common imperative anti-patterns in Rust that can be replaced with functional approaches:
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();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));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();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);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());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(),
}
});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.
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)
π When you see SELECT clause changes:
-
Identify struct dependencies
- Check if the query results are deserialized into Rust structs
- Search for struct names that might correspond to the query
-
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
-
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?
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]
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
π§ **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 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.
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();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)
}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(",");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
}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())
}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 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(())
}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?;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()
}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 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)
}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)
}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,
})
}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)
}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
}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 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);
}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;
}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);
}
}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 }
}
}
}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(),
})
}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))
}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
}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
}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;
});
}
}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);
}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.
- π Excellence Recognition
Highlight what's done well: idiomatic Rust, smart abstractions, merge-friendly code. - π¨ Critical Issues
Address security, correctness, or performance problems that must be fixed before merging. β οΈ Medium Priority Problems
Structural concerns, maintenance risks, and code smells that should be addressed soon.- π‘ Optional Improvements
Suggestions for style, performance, or design improvements that are nice to have. - π Pre-Merge Checklist
Essential steps the team must take before merging.
- 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.
- β 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.
- 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.
- 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.
- 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.
- Scan for Signatures: Look for method call patterns, struct/enum design, or suspicious control flow.
- Context Analysis: Identify misplaced or misused patterns.
- Semantic Evaluation: Does the pattern actually do what it's supposed to?
- Impact Assessment: What happens if left unchanged?
- Solution Generation: Provide clear, actionable refactors.
- Query Scanning: When scanning queries, always match bind placeholders (?, $1, $2) with bind calls and Rust struct fields.
- 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.
Always check for these during the Pattern Spotting Algorithm:
- String cloning madness.
- Unwrap cascades.
- Mutex poisoning or misuse.
- Arc overuse.
- Blocking operations in async.
- Lifetime soup.
- Iterator abandonment.
- Error swallowing.
- N+1 queries.
- Raw SQL injection.
- Connection leaking.
- Transaction abuse.
- Schema drift.
- Signal spam.
- Effect overload.
- Component bloat.
- Prop drilling.
- Reactive loops.
- Mutation madness.
- Imperative disguise.
- Callback hell.
- Side-effect soup.
- Monadic misuse.
- God Object.
- Anemic domain model.
- Circular dependencies.
- Concrete coupling.
- Feature envy.
- 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 testbefore merging. - Test Quality: Tests should be clear, meaningful, and maintainable.
β
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
- 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.
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.
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:
- Embrace Immutability: Use immutable data structures and functional transformations instead of mutation
- Compose Functions: Build complex operations from simple, pure functions
- Type Safety: Leverage Rust's type system for domain modeling and validation
- Error Handling: Use Result types and functional composition for robust error management
- Resource Management: Use RAII and async patterns for database connections and transactions
- 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.