|
| 1 | +# Rust — Language-Specific Assessment Criteria |
| 2 | + |
| 3 | +## Language Classification |
| 4 | +- **Tier:** statically-typed |
| 5 | +- **Primary safety mechanism:** Ownership system + compile-time type checking + `Result<T,E>` error handling |
| 6 | +- **Key principle:** Rust's ownership/borrow checker and type system eliminate entire classes of bugs at compile time. Assess quality of type usage, error handling discipline, and minimization of escape hatches (`unsafe`, `unwrap()`). |
| 7 | + |
| 8 | +## Type Safety |
| 9 | +### What to Examine |
| 10 | +- Use of `unsafe {}` blocks — lower frequency is better; each should have a `// SAFETY:` comment |
| 11 | +- `unwrap()` / `expect()` in non-test code — panic-on-failure anti-pattern; `clippy::unwrap_used` / `clippy::expect_used` lints enforce discipline |
| 12 | +- `dyn Any` usage — type erasure escape hatch |
| 13 | +- `transmute` usage — dangerous type-level escape hatch |
| 14 | +- Raw pointer usage (`*const T`, `*mut T`) — another unsafe primitive |
| 15 | +- Custom newtypes for domain concepts (`struct UserId(u64)`, `struct Email(String)`) |
| 16 | +- Trait bounds and generic constraints — well-bounded generics signal strong type discipline |
| 17 | +- `#[must_use]` annotations — strong API discipline signal |
| 18 | +- `From`/`Into` trait implementations — idiomatic type conversions |
| 19 | +- Error handling: `thiserror`/`snafu` (libraries) vs `anyhow`/`color-eyre` (binaries), custom error enums with `Result<T,E>` |
| 20 | +- `Result<T,E>` vs bare panics — explicit error propagation with `?` operator |
| 21 | + |
| 22 | +### Evidence Commands |
| 23 | +```bash |
| 24 | +echo "=== unsafe usage count ===" |
| 25 | +grep -r "unsafe " --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 26 | + |
| 27 | +echo "=== SAFETY comments on unsafe ===" |
| 28 | +grep -r "// SAFETY:" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 29 | + |
| 30 | +echo "=== unwrap()/expect() in non-test code (approx — includes in-file test modules) ===" |
| 31 | +grep -rE "\.unwrap\(\)|\.expect\(" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | grep -v "/tests/" | wc -l |
| 32 | + |
| 33 | +echo "=== Clippy unwrap lint enabled ===" |
| 34 | +grep -rE "deny\(clippy::(unwrap_used|expect_used)" --include="*.rs" --include="Cargo.toml" --include="clippy.toml" --include=".clippy.toml" . 2>/dev/null | grep -v target | head -5 |
| 35 | + |
| 36 | +echo "=== transmute usage ===" |
| 37 | +grep -r "transmute" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 38 | + |
| 39 | +echo "=== Raw pointer usage ===" |
| 40 | +grep -rE "\*const |\*mut " --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 41 | + |
| 42 | +echo "=== dyn Any usage ===" |
| 43 | +grep -r "dyn Any" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 44 | + |
| 45 | +echo "=== Custom newtypes ===" |
| 46 | +grep -rE "^(pub(\([^)]+\))? )?struct [A-Z][a-zA-Z]+\(.+\)" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 47 | + |
| 48 | +echo "=== Error handling libraries ===" |
| 49 | +grep -rE "thiserror|anyhow|color-eyre|snafu" --include="Cargo.toml" . 2>/dev/null | grep -v target | head -5 |
| 50 | + |
| 51 | +echo "=== Result usage ===" |
| 52 | +grep -r "Result<" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 53 | + |
| 54 | +echo "=== Trait definitions ===" |
| 55 | +grep -rE "^(pub(\([^)]+\))? )?trait " --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 56 | + |
| 57 | +echo "=== #[must_use] annotations ===" |
| 58 | +grep -r "#\[must_use\]" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 59 | + |
| 60 | +echo "=== From/Into implementations ===" |
| 61 | +grep -rE "impl .* From<" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 62 | +``` |
| 63 | + |
| 64 | +### Scoring Criteria |
| 65 | +- **0-20**: Widespread `unsafe`, excessive `unwrap()`, no custom types, bare panics in library code |
| 66 | +- **21-40**: Minimal newtypes, frequent `unwrap()` outside tests, unstructured error handling |
| 67 | +- **41-60**: Some newtypes, `thiserror`/`anyhow` present, moderate `unwrap()` usage with `expect()` messages |
| 68 | +- **61-80**: Well-defined newtypes, structured error enums, `unsafe` blocks documented with `// SAFETY:`, minimal `unwrap()` |
| 69 | +- **81-100**: Comprehensive newtype usage, zero undocumented `unsafe`, `unwrap()`-free non-test code, rich trait bounds, `Result<T,E>` throughout |
| 70 | + |
| 71 | +### Language-Specific Notes |
| 72 | +- Rust's ownership system prevents data races and memory errors at compile time — the type system does more work than most languages. |
| 73 | +- `unsafe {}` is Rust's escape hatch — treat it like `any` in TypeScript or `interface{}` in Go. Every `unsafe` block should have a `// SAFETY:` comment. |
| 74 | +- `unwrap()` in non-test code is a panic-on-failure anti-pattern; `expect("reason")` is slightly better but still panics. Prefer `?` propagation. Projects enabling `clippy::unwrap_used` / `clippy::expect_used` lints demonstrate strong discipline. |
| 75 | +- `transmute` and raw pointers are deeper escape hatches than `unsafe` blocks alone — their presence warrants close inspection. |
| 76 | +- Error handling discipline (`thiserror`/`snafu` for libraries, `anyhow`/`color-eyre` for binaries, `?` operator) is a key Rust quality signal. |
| 77 | +- `#[deny(warnings)]` in source code is a known anti-pattern (causes breakage on compiler upgrades) — prefer CI-only `-D warnings` via rustflags. |
| 78 | + |
| 79 | +## Test Foundation |
| 80 | +### Tooling & Detection |
| 81 | +- **Framework:** cargo test (built-in, standard) |
| 82 | +- **Coverage:** cargo-tarpaulin, cargo-llvm-cov |
| 83 | +- **Mutation testing:** cargo-mutants |
| 84 | +- **Property-based testing:** proptest, quickcheck |
| 85 | +- **Snapshot testing:** insta (cargo-insta) |
| 86 | +- **Parametric testing:** rstest |
| 87 | +- **Benchmarking:** criterion, divan |
| 88 | +- **Mocking:** mockall |
| 89 | +- **CLI testing:** assert_cmd, assert_fs |
| 90 | +- **Parallel runner:** cargo-nextest |
| 91 | +- **File convention:** `#[cfg(test)] mod tests` in-file + `tests/*.rs` integration tests + doc tests in `///` and `//!` comments |
| 92 | + |
| 93 | +### Evidence Commands |
| 94 | +```bash |
| 95 | +echo "=== In-file test modules ===" |
| 96 | +grep -r "#\[cfg(test)\]" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 97 | + |
| 98 | +echo "=== Integration test files ===" |
| 99 | +find . -path "*/tests/*.rs" 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 100 | + |
| 101 | +echo "=== Source file count ===" |
| 102 | +find . -name "*.rs" 2>/dev/null | grep -v target | grep -v .git | grep -v "/tests/" | wc -l |
| 103 | + |
| 104 | +echo "=== Doc tests ===" |
| 105 | +grep -rE "/// ```|//! ```" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 106 | + |
| 107 | +echo "=== Testing/coverage in CI ===" |
| 108 | +grep -rE "tarpaulin|llvm-cov|cargo test|nextest" .github/ .circleci/ .buildkite/ Makefile 2>/dev/null | head -5 |
| 109 | + |
| 110 | +echo "=== Snapshot/property/benchmark/mock testing ===" |
| 111 | +grep -rE "insta|proptest|quickcheck|criterion|divan|mockall|rstest|assert_cmd|assert_fs" --include="Cargo.toml" . 2>/dev/null | grep -v target | head -10 |
| 112 | + |
| 113 | +echo "=== Ignored tests ===" |
| 114 | +grep -r "#\[ignore\]" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 115 | +``` |
| 116 | + |
| 117 | +### Scoring Notes |
| 118 | +- **Skip patterns:** `#[ignore]` attribute on test functions |
| 119 | +- **Mock indicators:** mockall or trait-based test doubles |
| 120 | +- Rust's in-file `#[cfg(test)] mod tests` pattern means test count requires grepping, not just file counting |
| 121 | +- Doc tests (`/// ````) are a strong positive signal — they ensure examples stay compilable |
| 122 | +- `cargo test` runs unit tests, integration tests, and doc tests by default |
| 123 | +- `insta` snapshot testing is widely used in the Rust ecosystem and a strong positive signal |
| 124 | +- `cargo-nextest` for parallel test execution is both a feedback loop and test foundation signal |
| 125 | + |
| 126 | +## Feedback Loops |
| 127 | +### Tooling |
| 128 | +- **Pre-commit:** lefthook, pre-commit framework |
| 129 | +- **Linter:** cargo clippy (official lint tool, highly configurable) |
| 130 | +- **Formatter:** rustfmt (official, universally adopted) |
| 131 | +- **Fast checking:** cargo check (type checking without full compilation) |
| 132 | +- **Watch mode:** cargo-watch, bacon (auto-rerun on file change) |
| 133 | +- **Parallel test runner:** cargo-nextest (faster parallel test execution) |
| 134 | +- **Security scanners:** cargo-audit, cargo-deny |
| 135 | + |
| 136 | +### Evidence Commands |
| 137 | +```bash |
| 138 | +ls lefthook.yml .pre-commit-config.yaml 2>/dev/null |
| 139 | +grep -rE "clippy|cargo-audit|cargo-deny|nextest|cargo check" .github/ .circleci/ .buildkite/ Makefile 2>/dev/null | head -5 |
| 140 | +``` |
| 141 | + |
| 142 | +## Code Clarity |
| 143 | +### Conventions |
| 144 | +- **Extensions:** .rs |
| 145 | +- **Idiomatic file size:** Moderate files; one type per module is idiomatic |
| 146 | +- **Naming:** snake_case for functions/modules/variables, PascalCase for types/traits, SCREAMING_SNAKE_CASE for constants |
| 147 | +- **File organization:** Module system — `module/mod.rs` (2015 style) or `module.rs` + `module/` subdirectory (2018+ style) |
| 148 | + |
| 149 | +## Consistency & Conventions |
| 150 | +### Tooling |
| 151 | +- **Linter:** clippy (clippy.toml or .clippy.toml for configuration) |
| 152 | +- **Formatter:** rustfmt (rustfmt.toml for configuration) |
| 153 | +- **Toolchain pinning:** rust-toolchain.toml |
| 154 | +- **Dependency policy:** cargo-deny (deny.toml) |
| 155 | +- **Cargo configuration:** .cargo/config.toml |
| 156 | +- **Observability:** tracing, log (ecosystem standard crates) |
| 157 | + |
| 158 | +### Evidence Commands |
| 159 | +```bash |
| 160 | +echo "=== Formatter/linter config ===" |
| 161 | +ls rustfmt.toml clippy.toml .clippy.toml .cargo/config.toml rust-toolchain.toml deny.toml 2>/dev/null |
| 162 | + |
| 163 | +echo "=== CI linting ===" |
| 164 | +grep -rE "clippy|rustfmt|cargo fmt" .github/ .circleci/ .buildkite/ Makefile 2>/dev/null | head -5 |
| 165 | + |
| 166 | +echo "=== Clippy deny warnings ===" |
| 167 | +grep -rE "\-D warnings|\-D clippy|deny\(clippy" .cargo/config.toml Makefile .github/ 2>/dev/null | head -5 |
| 168 | + |
| 169 | +echo "=== deny(warnings) anti-pattern ===" |
| 170 | +grep -r "deny(warnings)" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | head -5 |
| 171 | + |
| 172 | +echo "=== missing_docs enforcement ===" |
| 173 | +grep -rE "warn\(missing_docs\)|deny\(missing_docs\)" --include="*.rs" . 2>/dev/null | grep -v target | head -5 |
| 174 | + |
| 175 | +echo "=== Observability ===" |
| 176 | +grep -rE "tracing|\"log\"" --include="Cargo.toml" . 2>/dev/null | grep -v target | head -5 |
| 177 | +``` |
| 178 | + |
| 179 | +## Architecture |
| 180 | +### Framework Conventions |
| 181 | +- **Standard layout:** `src/` (source), `tests/` (integration tests), `benches/` (benchmarks), `examples/` (example programs) |
| 182 | +- **Entry points:** `src/main.rs` (binary), `src/lib.rs` (library) |
| 183 | +- **Workspace structure:** `[workspace]` in root `Cargo.toml` with member crates; `[workspace.dependencies]` for dependency inheritance (modern practice) |
| 184 | +- **Web frameworks:** actix-web, axum (current standard), rocket |
| 185 | +- **Async runtimes:** tokio (dominant), async-std |
| 186 | +- **Build scripts:** `build.rs` for code generation or native dependencies |
| 187 | + |
| 188 | +### Evidence Commands |
| 189 | +```bash |
| 190 | +echo "=== Standard layout ===" |
| 191 | +ls src/main.rs src/lib.rs tests/ benches/ examples/ 2>/dev/null |
| 192 | + |
| 193 | +echo "=== Workspace structure ===" |
| 194 | +grep -A5 "\[workspace\]" Cargo.toml 2>/dev/null | head -10 |
| 195 | + |
| 196 | +echo "=== Directory structure ===" |
| 197 | +find . -maxdepth 2 -type d 2>/dev/null | grep -v target | grep -v .git | head -20 |
| 198 | +``` |
| 199 | + |
| 200 | +### Co-Change Notes |
| 201 | +- Cargo.lock churn is expected on dependency updates and should not penalize |
| 202 | +- `src/` + `tests/` co-changes for the same feature are expected |
| 203 | +- Workspace member crates may have coordinated version bumps |
| 204 | + |
| 205 | +## Change Safety |
| 206 | +### Tooling |
| 207 | +- **Feature flags:** Cargo feature flags (`#[cfg(feature = "...")]`), compile-time feature gating |
| 208 | +- **Security scanning:** cargo-audit (advisories), cargo-deny (license + advisories + bans) |
| 209 | +- **Database migrations:** diesel, sqlx, sea-orm-migration |
| 210 | +- **Dependency management:** Cargo.lock (committed for binaries; optional for libraries) |
| 211 | +- **Toolchain pinning:** rust-toolchain.toml (specific version like `1.75.0` is a stronger signal than just `stable`) |
| 212 | +- **MSRV policy:** `rust-version` field in Cargo.toml indicates backward compatibility maintenance |
| 213 | + |
| 214 | +### Evidence Commands |
| 215 | +```bash |
| 216 | +echo "=== Feature flags ===" |
| 217 | +grep -r "cfg(feature" --include="*.rs" . 2>/dev/null | grep -v target | grep -v .git | wc -l |
| 218 | + |
| 219 | +echo "=== Security scanning ===" |
| 220 | +grep -rE "cargo-audit|cargo-deny" .github/ .circleci/ .buildkite/ Makefile 2>/dev/null | head -5 |
| 221 | + |
| 222 | +echo "=== Migration tools ===" |
| 223 | +grep -rE "diesel|sqlx|sea-orm" --include="Cargo.toml" . 2>/dev/null | grep -v target | head -5 |
| 224 | +find . -path "*/migrations/*" -name "*.sql" 2>/dev/null | grep -v target | wc -l |
| 225 | + |
| 226 | +echo "=== Reproducibility ===" |
| 227 | +[ -f "Cargo.lock" ] && echo "Cargo.lock present" || echo "Cargo.lock absent" |
| 228 | +cat rust-toolchain.toml 2>/dev/null || echo "No rust-toolchain.toml" |
| 229 | +grep -r "rust-version" --include="Cargo.toml" . 2>/dev/null | grep -v target | head -3 |
| 230 | +grep -r 'edition = ' --include="Cargo.toml" . 2>/dev/null | grep -v target | head -3 |
| 231 | +``` |
0 commit comments