Code standards
Status: Accepted
How Rust is written in lemonfiber. Every rule here is mechanically enforced or
explicitly marked judgment — a standard that is neither is decoration.
Satisfies: the correctness posture of P3, G4.
The minimum supported Rust version is pinned and tested, not merely declared. CI builds against it; a feature requiring a newer compiler is a deliberate MSRV bump, not an accident discovered downstream.
Lint policy — strict, zero-suppression
Section titled “Lint policy — strict, zero-suppression”#![forbid(unsafe_code)]#![deny( clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::todo, clippy::unimplemented, clippy::indexing_slicing,)]#![warn(clippy::pedantic, clippy::nursery)]#![deny(missing_docs)]| Rule | Rationale |
|---|---|
forbid(unsafe_code) |
Nothing in this domain needs it. forbid, not deny — it cannot be re-permitted locally. |
No unwrap/expect/panic in library code |
A panic is an unhandled error with no remedy — the exact opposite of G4. Every error path must be a typed value. |
No indexing_slicing |
arr[i] panics; arr.get(i) is a value. |
No todo/unimplemented |
Shipped code is finished (Q-R philosophy, comment policy). |
pedantic + nursery |
On as warnings; each finding is fixed, not silenced. |
missing_docs |
Public API is documented (M7). |
Zero suppression
Section titled “Zero suppression”There are exactly two legal responses to a lint finding: change the code, or change the rule (with a documented reason, at the crate root, reviewed).
#[allow(...)] in src/ is not a third option, and an architecture test
fails the build on its presence. A suppression does not answer a finding — it
hides one, in the place least likely to be looked at again.
Test code is exempt: #[cfg(test)] and tests/ may unwrap, expect, and
allow freely. A test that panics on a broken assumption is working correctly.
Error handling
Section titled “Error handling”Typed in the core, rich at the binary — and the type carries the remedy so it cannot be forgotten.
In lemonfiber-core — thiserror, matchable
Section titled “In lemonfiber-core — thiserror, matchable”#[derive(Debug, thiserror::Error)]pub enum StorageError { #[error("data root spans more than one filesystem")] SplitMount { path: PathBuf, remedy: Remedy },
#[error("data root is on an exFAT volume, which cannot hardlink")] NoHardlinks { path: PathBuf, remedy: Remedy },}Every variant carries a Remedy (G4-R1). This is the mechanism that makes
“every error names its fix” structural rather than a thing reviewers must police:
a variant without a remedy does not compile.
Callers match on these — the doctor
and remediation
subsystems map error kinds to findings and fixes, which anyhow-style opaque
errors could not support.
In the lemonfiber binary — color-eyre
Section titled “In the lemonfiber binary — color-eyre”The binary renders errors in G4’s four-part shape: what happened, what it means,
what to do, where to look. color-eyre provides the report; the Remedy supplies
the “what to do”.
The Remedy type
Section titled “The Remedy type”pub struct Remedy { pub summary: String, // one line: what to do pub steps: Vec<String>, // concrete actions pub doc: Option<DocLink>, // deeper reference in .docs/}Remedies are data, so they can be rendered identically across CLI, TUI and web
(G1), and tested.
Module boundaries
Section titled “Module boundaries”Enforced by the crate graph, not by convention (component-model):
lemonfiber-coredepends on no UI crate (ARCH-R11).stack::composeanddocker::*do not depend on each other (ARCH-R14).- An architecture test asserts these edges, so a violating
usefails CI.
Naming & idiom
Section titled “Naming & idiom”Judgment rules — bind everyone, enforced by review:
- J Match the surrounding code. New code should be indistinguishable from what’s there.
- J Names carry the why so comments don’t have to (J1).
- J Prefer
Resultand the type system over runtime checks. An invariant encoded in a type cannot be violated; one checked at runtime can. - J No premature abstraction. A trait with one implementation is usually a function.
Formatting
Section titled “Formatting”rustfmt with the repo’s rustfmt.toml, enforced in CI. Not negotiable and not
discussed — formatting arguments are wasted minutes, and a formatter ends them.
Async discipline
Section titled “Async discipline”tokio, shallow (component-model).
Judgment rules:
- J Async only where there is real concurrency. Pure computation stays sync.
- J The render loop never
.awaits I/O or holds a lock across a frame (ARCH-R15) — this one is also checked by a lint on the render module. - J Background tasks own their data and send snapshots (
ARCH-R16).
Requirements
Section titled “Requirements”| ID | Requirement |
|---|---|
| Q-R11 | unsafe MUST be forbidden crate-wide. |
| Q-R12 | unwrap, expect, panic, todo, unimplemented and slice indexing MUST be denied in non-test code. |
| Q-R13 | Lint suppressions MUST NOT appear in src/; an architecture test MUST fail on their presence. |
| Q-R14 | Rule changes MUST be at the crate root with a documented reason, never inline. |
| Q-R15 | Library errors MUST be typed and matchable; every user-facing variant MUST carry a remedy. |
| Q-R16 | A user-facing error variant without a remedy MUST NOT compile. |
| Q-R17 | The MSRV MUST be pinned and built in CI. |
| Q-R18 | Module boundary rules MUST be asserted by an architecture test. |
| Q-R19 | rustfmt and the full lint set MUST pass in CI. |
| Q-R20 | Public items MUST be documented (missing_docs denied). |
Related
Section titled “Related”- code-comments.md — the comment half of the same posture
- testing-strategy.md · ci-cd.md
- component-model — the boundaries enforced here
- G4 Error model
This page lives in another repository Rendered from lemonfiber/spec at 1d10402, 2026-09-09. Read the source of this page