Common Anti-Patterns
Avoiding common Rust anti-patterns prevents borrow-checker friction, hidden heap allocations, fragile panic paths, and confusing method resolution behavior.
Top 6 Rust Anti-Patterns & Their Idiomatic Replacements:
1. Over-Concrete Borrowing: `&String`, `&Vec<T>`, `&PathBuf` ---> Use `&str`, `&[T]`, `&Path`!
2. `Deref` Polymorphism Abuse (Emulating OOP Inheritance) ---> Use explicit composition or traits!
3. Clone-to-Satisfy-the-Borrow-Checker ---> Restructure borrow scopes or use `Cow` / indices!
4. Boolean Trap Parameters (`connect("host", true, false)`) ---> Use two-variant `enum`s or a Builder!
5. Stringly-Typed Errors (`Result<T, String>`) ---> Use structured `enum` implementing `Error`!
6. Sentinel Values (`-1` or `""` for missing data) ---> Use `Option<T>` (`NonZero*` niche)!
1. Over-Concrete Reference Parameters (&String, &Vec<T>, &PathBuf)
Never write fn process(name: &String) or fn sum(items: &Vec<i32>)! Because &String automatically coerces to &str and &Vec<T> coerces to &[T] via Deref, accepting &str, &[T], and &Path allows callers to pass both heap-allocated owners (&my_string, &my_vec) and zero-allocation string/array literals ("literal", &[1, 2, 3]) or sub-slices (&my_vec[2..5])!
2. Abusing Deref to Simulate OOP Class Inheritance
Programmers coming from C++ or Java sometimes implement Deref<Target = BaseStruct> on DerivedStruct so DerivedStruct "inherits" all methods of BaseStruct.
This is a well-known Rust anti-pattern:
Derefis intended exclusively for smart pointers and transparent views (Box<T>,Arc<T>,MutexGuard<T>,String -> str,Vec<T> -> [T]) where the wrapper is a pointer to the target.- Implementing
Derefon domain structs (such asEmployeedereferencing toPerson) breaks generic trait bounds (Employeestill does not implement traits implemented byPerson!), causes confusingPartialEq/Hashdiscrepancies, and hides which struct's method is being called. Use explicit accessor methods (emp.person()) or shared traits instead!
3. Replacing "Boolean Parameter Traps" with Self-Documenting Enums
When a function takes one or more bool parameters (file_copy(src, dst, true, false)), a reader at the call site cannot tell what true and false mean without looking up the function definition—and passing (false, true) by mistake compiles without warning!
Replacing boolean flags with 2-variant Copy enums makes call sites self-documenting and impossible to transpose:
// ❌ ANTI-PATTERN: Call site `sync_directory(src, dst, true, false)` is unreadable!
// pub fn sync_directory_bad(src: &String, dst: &String, recursive: bool, overwrite: bool)
// ✅ IDIOMATIC RUST: Accepts `&str` slices and self-documenting 2-variant enums!
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TraversalMode {
Shallow,
Recursive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConflictPolicy {
SkipExisting,
OverwriteExisting,
}
pub fn sync_directory(
src: &str,
dst: &str,
mode: TraversalMode,
policy: ConflictPolicy,
) -> String {
format!("Sync '{src}' -> '{dst}' ({mode:?}, {policy:?})")
}
fn main() {
// Call site is 100% self-documenting and compiler-checked against argument transposition:
let summary = sync_directory(
"/var/data",
"/mnt/backup",
TraversalMode::Recursive,
ConflictPolicy::SkipExisting,
);
println!("{summary}");
}
Sync '/var/data' -> '/mnt/backup' (Recursive, SkipExisting)
Summary Table of Anti-Patterns vs Idiomatic Rust
| Anti-Pattern | Problem Caused | Idiomatic Replacement |
|---|---|---|
fn foo(v: &Vec<T>, s: &String) |
Forces callers with &[T] or &str literals to allocate on the heap just to call foo. |
fn foo(v: &[T], s: &str) |
if opt.is_some() { let x = opt.unwrap(); } |
Redundant double check; risks panic if refactored when condition changes. | if let Some(x) = opt { ... } or let Some(x) = opt else { return; }; |
impl Deref<Target = Base> for Sub |
Does not satisfy trait bounds T: BaseTrait; confuses method resolution. |
Explicit composition (sub.base()) or shared trait implementation. |
vec.iter().map(...).collect::<Vec<_>>() (when only iterating once) |
Unnecessary intermediate heap allocation. | Keep the iterator lazy until the final consumer! |
Returning -1 or usize::MAX for "not found" |
Caller can forget to check the magic sentinel and use it as a real index/count. | Return Option<usize> (or Option<NonZeroUsize> for 8-byte niche size!). |