Smell catalogue
A smell is a pattern that compiles but makes the code worse: it can crash, it costs performance or maintenance, or it is not idiomatic. AI agents produce these patterns often, usually because they translate Java directly or because they fixed a compiler error the fast way.
How to use the IDs
Section titled “How to use the IDs”Each smell has a permanent ID, from S01 to S38. The IDs never change and are never reused. Use them in review comments and in instructions to your agent: “Fix the S02 and S08 findings in importer.rs.”
Severity
Section titled “Severity”- High: can cause a crash, data loss, a security problem, or a deadlock.
- Medium: costs performance or maintenance.
- Low: style. Not idiomatic, but correct.
Each smell page
Section titled “Each smell page”Every smell page has the same sections: what it looks like, why AI agents produce it, why it is a problem, the better version, when it is acceptable, how to detect it, and what to tell your agent.
All smells
Section titled “All smells”Pages are being written milestone by milestone. A row without a link has no page yet.
| ID | Smell | Severity | Concepts |
|---|---|---|---|
| S01 | clone() to satisfy the borrow checkerA copy of the data is made only to make a borrow error go away. | Severity: Medium | Clone and Copy, Borrowing, Ownership and moves |
| S02 | unwrap() / expect() in non-test codeThe program stops (panics) on a failure that the caller could handle. | Severity: High | Result, not exceptions, Option, not null |
| S03 | Rc<RefCell<T>> object graph (Java translated directly)A Java object graph copied with shared, mutable, reference-counted cells. | Severity: Medium | Smart pointers, Ownership and moves |
| S04 | Arc<Mutex<T>> where no sharing across threads existsThread-safe wrappers in code that only one thread uses. | Severity: Low | Smart pointers, Threads and shared state |
| S05 | Owned parameters (String, Vec<T>) where a borrow is sufficientThe function takes ownership of data that it only reads. | Severity: Low | Ownership and moves, Borrowing, Strings |
| S06 | &String, &Vec<T>, &Box<T> parametersA reference to the container where a slice (&str, &[T]) or &T is more flexible. | Severity: Low | Borrowing, Strings, Collections |
| S07 | Result<T, String> or stringly-typed errorsThe caller cannot match on the kind of error, only read a message. | Severity: Medium | Result, not exceptions |
| S08 | Ignored Result (let _ =, .ok();)An error is thrown away silently. | Severity: High | Result, not exceptions |
| S09 | Error context lost (map_err(|_| ...))The original error is replaced, so the cause is no longer available. | Severity: Medium | Result, not exceptions |
| S10 | Index-based loops instead of iteratorsfor i in 0..v.len() with v[i], where an iterator is clearer and has no bounds checks. | Severity: Low | Iterators vs streams, Collections |
| S11 | collect() then iterate againAn intermediate collection that allocates for no reason. | Severity: Low | Iterators vs streams |
| S12 | Getters and setters for every fieldJavaBean accessors that add code but no encapsulation. | Severity: Low | Structs and impl |
| S13 | Trait hierarchy that copies a Java class hierarchyDeep supertrait chains that imitate inheritance. | Severity: Medium | Traits vs interfaces |
| S14 | Deref used to imitate inheritanceA wrapper type that auto-dereferences to a "base class". | Severity: Medium | Traits vs interfaces, Smart pointers |
| S15 | bool flags or string constants instead of an enumStates encoded as flags or strings, so invalid combinations are possible. | Severity: Medium | Enums and pattern matching |
| S16 | Wildcard _ => match arm that hides new variantsThe compiler cannot tell you when a new variant is not handled. | Severity: Medium | Enums and pattern matching |
| S17 | Struct with many Option fields that are really statesWhich fields are set depends on a hidden state that an enum should express. | Severity: Medium | Enums and pattern matching, Option, not null, Structs and impl |
| S18 | Blocking call inside async codeA blocking call stops the runtime worker thread and stalls other tasks.Page not written yet. | Severity: High | Async |
| S19 | std::sync::Mutex guard held across .awaitA lock that stays held while the task is suspended, which can deadlock.Page not written yet. | Severity: High | Async, Threads and shared state |
| S20 | unsafe without a // SAFETY: comment, or unsafe to avoid the borrow checkerUnchecked code with no written proof that it is correct.Page not written yet. | Severity: High | Unsafe and FFI |
| S21 | Unnecessary lifetime annotations, or 'static to silence errorsAnnotations that elision makes unnecessary, or a 'static bound added to stop an error. | Severity: Low | Lifetimes |
| S22 | Box<dyn Trait> where a generic or enum is sufficientDynamic dispatch and a heap allocation where the set of types is known. | Severity: Low | Traits vs interfaces, Generics, Smart pointers |
| S23 | as casts that can truncate or change signA numeric conversion that silently produces a wrong value.Page not written yet. | Severity: High | Result, not exceptions |
| S24 | Indexing (v[i], s[a..b]) on untrusted inputOut-of-range input makes the program panic. | Severity: High | Collections, Strings |
| S25 | #[allow(...)] to silence clippy or compiler warningsA warning is hidden instead of fixed, with no reason given. | Severity: Medium | Tooling |
| S26 | Hallucinated crate, API, or feature flagA dependency, function, or feature that does not exist, or does not do what the code expects. | Severity: High | Modules, crates, and Cargo |
| S27 | Outdated idioms (try!, extern crate, lazy_static, old edition)Old patterns that the current standard library or edition replaces. | Severity: Low | Modules, crates, and Cargo, Macros |
| S28 | Too many or unknown dependenciesCrates added for small tasks, or from unknown authors. | Severity: Medium | Modules, crates, and Cargo |
| S29 | static mut or global mutable stateShared mutable state that is unsafe or hard to test.Page not written yet. | Severity: High | Threads and shared state, Unsafe and FFI |
| S30 | Mocks for everything; tests with sleepTests that are brittle, slow, or test the mocks instead of the code. | Severity: Low | Testing |
| S31 | Function with too many parameters (no builder or config struct)Long parameter lists that are easy to call in the wrong order. | Severity: Low | Structs and impl |
| S32 | String building with + in a loop, format! used only to convertExtra allocations and noise when building or converting strings. | Severity: Low | Strings |
| S33 | async everywhere, or two async runtimesAsync code with no I/O, or two runtimes in one program.Page not written yet. | Severity: Medium | Async |
| S34 | Unbounded tokio::spawn or unbounded channelsNo back-pressure, so memory use can grow without limit.Page not written yet. | Severity: Medium | Async, Threads and shared state |
| S35 | Rc cycles (memory leak)Two values that own each other are never freed. | Severity: Medium | Smart pointers |
| S36 | panic! used for normal errorsAn expected failure stops the program instead of returning an error. | Severity: High | Result, not exceptions |
| S37 | Primitive types where a newtype gives safety (u64 for IDs)Two different IDs have the same type, so the compiler cannot catch a mix-up. | Severity: Low | Structs and impl |
| S38 | pub on everythingEvery field and function is public, so the API has no boundary. | Severity: Low | Structs and impl, Modules, crates, and Cargo |