pragmatic-engineer

engineering-standards-rust

Rust rules for code and reviews, covering ownership, errors, types, API design, async, unsafe, testing and Clippy lints. Use when writing or reviewing Rust, or a diff that touches .rs files.

pragmatic-engineer 0 Updated 1w ago

Resources

1
GitHub

Install

npx skillscat add pragmatic-engineer/playbook/engineering-standards-rust

Install via the SkillsCat registry.

SKILL.md

Engineering Standards: Rust

Language-specific engineering standards for Rust, derived from the team's base standards. RFC 2119 keywords (MUST, SHOULD) carry their standard meanings.

REQUIRED BACKGROUND: engineering-standards owns the general rules (PR readiness, size, test types and requirements, the mocking philosophy, deployment). Read it first. This skill adds only the Rust specifics and does not restate the base.

How to use this skill

  • The index below holds one line per rule. Each topic names a reference file in this skill's references/ folder and says when to load it. Load only the files the work touches.
  • When you review a diff that touches .rs files, load the reference for each topic the diff touches. Cite the rule and the lint or command that checks it.
  • The project's own setup wins. When Cargo.toml, clippy.toml, CI or the contributing guide sets a rule, follow it over this skill.
  • A Clippy lint in the pedantic, nursery or restriction group is off by default. The references mark those lints. Turn one on in the [lints.clippy] table of Cargo.toml (or [workspace.lints.clippy]) before you rely on it.

Rules index

Ownership and borrowing

  • Borrow to read, own to keep. Take &str, &[T] and &Path in parameters.
  • A clone MUST have a reason a reviewer can see. Shorten the borrow or use mem::take before you clone.
  • Prefer one owner. Use an arena with indices for trees and graphs, and Rc<RefCell<T>> only when sharing and mutation are both real.

Load references/ownership-and-borrowing.md when you fix a borrow checker error, add a .clone(), Rc, RefCell or a lifetime parameter, or choose a parameter type.

Error handling

  • A library MUST return typed errors (thiserror or a hand-written enum). An application MAY use anyhow where callers only report the error.
  • Library and production code MUST NOT unwrap or expect on a path that can fail at run time. Panic only for a broken invariant, and say which one in the expect message.
  • Add context at each fallible step. Expose the cause through source() and keep it out of the Display text.

Load references/error-handling.md when you define an error type, write ?, unwrap, expect or panic!, or add a dependency whose errors cross your API.

Types and traits

  • Wrap meaningful primitives in newtypes and validate once in the constructor.
  • Use an enum in place of a bool parameter or a string that holds one of a fixed set of values.
  • Implement From and TryFrom, avoid lossy as casts, and derive the common traits on public types.
  • Write a concrete type first. Make it generic when a second type needs it.

Load references/types-and-traits.md when you add a type or a trait, add a conversion between types, or parse untrusted input at a boundary.

API design

  • Public items follow the Rust API Guidelines: as_, to_ and into_ prefixes, getters with no get_, private fields, #[non_exhaustive] on types that will grow.
  • Public items have doc comments with examples that use ?, and # Errors, # Panics and # Safety sections where they apply.

Load references/api-design.md when you add or change a pub item, a crate feature or a doc comment, or prepare a library release.

Style and naming

  • Copy the project's naming scheme. No glob imports from other crates. Use let else and if let over a check then unwrap.

Load references/style-and-naming.md when you name things, write imports or match blocks, or review a diff for readability.

Async and concurrency

  • Do not block an async runtime thread. Move blocking work to spawn_blocking, rayon or a dedicated thread.
  • Do not hold a std lock guard or a RefCell borrow across .await. Use bounded channels, track every spawned task, and put a timeout on each remote call.

Load references/async-and-concurrency.md when you write async code, spawn a task or thread, or add a lock, channel or atomic.

Iterators and collections

  • Prefer iterator adapters to index loops, and do not collect only to loop again.
  • Use the entry API for insert-or-update, and Vec and HashMap unless the access pattern asks for another type.

Load references/iterators-and-collections.md when you write a loop, an iterator chain or a collect, or pick a collection type.

Performance

  • Profile a release build before you optimize, and benchmark with criterion or divan.
  • On a hot path, size collections up front, reuse buffers and avoid clones.

Load references/performance.md when a change targets speed or memory, touches a hot path, or edits a build profile.

Unsafe and FFI

  • Write unsafe only when safe Rust cannot express the code, behind a safe API. Each unsafe block has a // SAFETY: comment and each pub unsafe fn a # Safety section.
  • Run tests under Miri for crates with unsafe code. Do not let a panic cross an extern "C" function.

Load references/unsafe-and-ffi.md when a diff adds or changes unsafe, extern, raw pointers or #[repr(C)].

Security and input

  • Parse untrusted input into typed values at the boundary, cap its size, and do not panic on it.
  • Pass process arguments one by one, bind SQL parameters, and keep secrets out of Debug and logs.

Load references/security-and-input.md when code reads input from a network, a file or a user, runs a process, builds SQL, or handles a secret.

Testing

  • Unit tests sit in a #[cfg(test)] module, integration tests in tests/, and public items have doc tests.
  • Tests share no state and do not sleep. Tests may unwrap.

Load references/testing.md when you write or review a test, or reproduce a bug as a failing test.

Tooling and CI

  • CI runs cargo fmt --check and Clippy on all targets with warnings denied. Lint levels live in [workspace.lints].
  • Check dependencies with cargo deny or cargo audit, keep features additive, and test the MSRV.

Load references/tooling-and-ci.md when you change Cargo.toml lints, CI, the toolchain, features or dependencies. It ends with the setup of pragmatic-engineer/playbook, this plugin's source, as a worked example.

Project structure

  • One workspace for several crates, with shared versions in [workspace.dependencies]. Keep main.rs thin and items private by default.

Load references/project-structure.md when you add a crate or module, change visibility, or move code between crates.

Antipatterns

Load references/antipatterns.md when you review a Rust diff. It is a checklist of the most common smells, each with a fix, and a check where one exists.

Sources

The source list started from mre/idiomatic-rust, curated by Matthias Endler and contributors and dedicated to the public domain under CC0 1.0. The rules here are a summary in our own words. No text is copied from the sources below.