Rust programming
Neon Law Navigator is a Rust workspace. This page is the common Rust reference for agents and humans; use the official Rust sources below when language behavior matters.
Canonical language references:
- The Rust Programming Language Rust by Example Rust API Guidelines Rust async book Rust edition guide rustfmt reference Clippy lint index
Workspace defaults
- Toolchain: Rust 1.97.0, edition 2021.
unsafe_code = "forbid"at the workspace level.rustfmtis authoritative. Clippy pedantic warnings are enabled; run with-D warningsbefore committing. Tokio is the async runtime. Axum is the web framework. SeaORM is the ORM. Restate SDK belongs only inworkflows-service; the rest of the workspace submits through theworkflowscrate.
Error handling
- Libraries use typed
thiserrorenums. Binaries useanyhow::Result<T>at the boundary and add context with.context(...)or.with_context(...). HTTP handlers convert through the workspaceAppError/IntoResponsepattern. Do not useBox<dyn Error>in public signatures. Do notunwrap()orexpect()outside tests andmain()unless the invariant is truly local and the message proves it in one line.
Types and modules
- Prefer newtypes for ids that cross module boundaries. Prefer enums over booleans for meaningful state. Prefer
Option<T>over sentinel values. Accept&strinstead ofString, and&[T]instead ofVec<T>, when ownership is not needed. Keep one concept per file; split large files when two concepts are hiding inside one module. Use amoddeclaration pluspub use foo::Type;for public re-exports.
Comments describe the present
- A comment, doc comment, or test states what the code does today, not how it used to work. Git history is the record of past decisions — don't re-narrate it in the tree. Drop phrasing like "we used to…", "no longer…", "previously", "legacy", or "kept for backwards compat"; if a behavior changed, delete the old path rather than leaving a deprecated-but-handled flag or alias behind.
- Keep the exceptions that still earn their place: the why behind a live invariant (a comment that stops someone
re-introducing a removed approach), a migration file's short rationale (a migration is itself a dated record), and
guard tests that assert current behavior — an old URL must
404or redirect, a removed column must fail loud. These describe today's contract, not nostalgia. - When you delete a feature, delete its references too: doc examples, README rows, and the test that only exercised the removed path. A dangling pointer to a file, module, or flag that no longer exists is a bug, not history.
Async and concurrency
- Use
async fnover manual future types except at trait/object boundaries. Use structured concurrency first:tokio::join!,tokio::try_join!,JoinSet, andselect!. A baretokio::spawnneeds an owner and a cancellation story. Use bounded channels.unbounded_channelis allowed only for tightly controlled intra-process control planes. Do not hold aMutexguard across.awaitunless the call sites are audited. Usespawn_blockingfor CPU-bound or sync-only blocking work. Usetokio::time::timeoutaround external calls.
Inside Restate handlers, the rules are stricter: do not use native concurrency for journaled work. See
agent-workflows.md and durable-workflows.md.
Axum
- Add routes in the existing router shape; do not introduce another web framework. Prefer typed extractors and explicit
state over ad-hoc request parsing. Keep auth and visibility checks close to existing middleware/access helpers. New
/portal/...routes must respectaccess-model.mdand OPA policy. Return404where the existing surface intentionally hides staff-only management routes from clients. - Body/consuming extractors (
Json,Form, multipart) go last in a handler's argument list — the body can only be consumed once.5xxresponses log viatracingbefore returning;4xxresponses do not.
SeaORM and Postgres
- Postgres is the only database. No SQLite fallback. Migrations, entities, and seed changes must land together when they
depend on each other. Use transactions for multi-row invariants. Raw SQL skips SeaORM-managed timestamp behavior; set
updated_at = now()yourself when doing approved production SQL. Re-seeding is idempotent and inserts missing rows; it does not update live production rows.
Service lifecycle
- Long-running binaries initialize config, database, telemetry, and external clients before serving traffic. Hold the
telemetry guard until the end of
main. Use the workspace shutdown helper instead of ad-hoc signal handling. Health and readiness endpoints should reflect the dependency contract the service actually needs. Liveness (/health) must not probe downstreams — a transient DB blip would restart-loop the pod; readiness (/readyz) is where the real dependency round-trip gates traffic.
Testing
- Tests ship in the same commit as the implementation. Unit tests live beside code in
#[cfg(test)] mod tests. Integration tests live under<crate>/tests/. Async tests use#[tokio::test]. CLI smoke tests useassert_cmdandpredicates. Snapshot tests are appropriate for HTML and JSON shapes. Restate handler changes need replay-aware coverage, not only a happy-path compile.
Dependencies and assets
- Routine Rust dependency refresh uses
cargo updatefor semver-compatible lockfile updates.cargo upgradechanges version requirements and needs explicit review. Keep crate updates separate from vendored frontend asset refreshes. Vendored web assets are served same-origin fromweb/public/; do not link runtime CDNs.
Before committing Rust
cargo fmt
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
Run narrower tests while iterating, but report the exact gate you actually ran.