318 lines
7.5 KiB
Markdown
318 lines
7.5 KiB
Markdown
|
|
# Cargo Strict Configuration
|
||
|
|
|
||
|
|
The exact knobs every new Rust project gets. Drop these in unmodified.
|
||
|
|
|
||
|
|
## `rust-toolchain.toml`
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[toolchain]
|
||
|
|
channel = "stable"
|
||
|
|
components = ["rustfmt", "clippy", "rust-src"]
|
||
|
|
profile = "default"
|
||
|
|
```
|
||
|
|
|
||
|
|
Pin nightly separately when miri runs:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
rustup install nightly
|
||
|
|
rustup component add miri rust-src --toolchain nightly
|
||
|
|
```
|
||
|
|
|
||
|
|
## `Cargo.toml` — `[lints]` section
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[lints.rust]
|
||
|
|
unsafe_op_in_unsafe_fn = "deny"
|
||
|
|
missing_docs = "warn"
|
||
|
|
missing_debug_implementations = "warn"
|
||
|
|
unreachable_pub = "warn"
|
||
|
|
unused_must_use = "deny"
|
||
|
|
elided_lifetimes_in_paths = "warn"
|
||
|
|
non_ascii_idents = "deny"
|
||
|
|
trivial_numeric_casts = "warn"
|
||
|
|
unused_lifetimes = "warn"
|
||
|
|
single_use_lifetimes = "warn"
|
||
|
|
|
||
|
|
[lints.clippy]
|
||
|
|
# Groups
|
||
|
|
all = { level = "deny", priority = -1 }
|
||
|
|
pedantic = { level = "warn", priority = -1 }
|
||
|
|
nursery = { level = "warn", priority = -1 }
|
||
|
|
cargo = { level = "warn", priority = -1 }
|
||
|
|
|
||
|
|
# Hard denies - turn warnings into errors for sharp tools
|
||
|
|
undocumented_unsafe_blocks = "deny"
|
||
|
|
multiple_unsafe_ops_per_block = "deny"
|
||
|
|
unwrap_used = "deny"
|
||
|
|
expect_used = "deny"
|
||
|
|
panic = "deny"
|
||
|
|
todo = "deny"
|
||
|
|
unimplemented = "deny"
|
||
|
|
unreachable = "deny"
|
||
|
|
indexing_slicing = "deny"
|
||
|
|
mem_forget = "deny"
|
||
|
|
arithmetic_side_effects = "warn"
|
||
|
|
cast_possible_truncation = "warn"
|
||
|
|
cast_possible_wrap = "warn"
|
||
|
|
cast_precision_loss = "warn"
|
||
|
|
cast_sign_loss = "warn"
|
||
|
|
as_underscore = "deny"
|
||
|
|
as_ptr_cast_mut = "deny"
|
||
|
|
ptr_as_ptr = "warn"
|
||
|
|
borrow_as_ptr = "warn"
|
||
|
|
fn_to_numeric_cast_any = "deny"
|
||
|
|
clone_on_ref_ptr = "warn"
|
||
|
|
mutex_atomic = "warn"
|
||
|
|
rc_buffer = "warn"
|
||
|
|
rc_mutex = "warn"
|
||
|
|
exit = "warn"
|
||
|
|
allow_attributes_without_reason = "warn"
|
||
|
|
dbg_macro = "warn"
|
||
|
|
print_stderr = "warn"
|
||
|
|
print_stdout = "warn"
|
||
|
|
use_debug = "warn"
|
||
|
|
|
||
|
|
# Stylistic relaxations (project-wide opinions only)
|
||
|
|
module_name_repetitions = "allow"
|
||
|
|
must_use_candidate = "allow"
|
||
|
|
missing_errors_doc = "allow" # we use anyhow::Result with .context() everywhere; doc rule is noisy
|
||
|
|
|
||
|
|
# Restriction lints - opt-in soundness rails
|
||
|
|
unreachable = "deny"
|
||
|
|
mod_module_files = "warn" # prefer foo.rs over foo/mod.rs
|
||
|
|
empty_drop = "warn"
|
||
|
|
empty_structs_with_brackets = "warn"
|
||
|
|
empty_enum = "warn"
|
||
|
|
exhaustive_enums = "warn" # public enums should consider #[non_exhaustive]
|
||
|
|
exhaustive_structs = "warn"
|
||
|
|
```
|
||
|
|
|
||
|
|
The `priority = -1` trick: group-level levels are weak; specific lints below them win. This lets us deny `unwrap_used` while still allowing `pedantic` group warnings instead of denies.
|
||
|
|
|
||
|
|
## `Cargo.toml` — release profile
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[profile.release]
|
||
|
|
opt-level = 3
|
||
|
|
lto = "fat"
|
||
|
|
codegen-units = 1
|
||
|
|
strip = "symbols"
|
||
|
|
panic = "abort" # smaller, faster - if you need unwinding (FFI catch), set "unwind"
|
||
|
|
debug = "line-tables-only"
|
||
|
|
|
||
|
|
[profile.dev]
|
||
|
|
opt-level = 0
|
||
|
|
debug = true
|
||
|
|
incremental = true
|
||
|
|
codegen-units = 256
|
||
|
|
split-debuginfo = "unpacked"
|
||
|
|
|
||
|
|
# A profile for miri - opt-level 1 keeps simulation bearable while still
|
||
|
|
# exercising real codegen patterns. miri ignores most profile keys but reads
|
||
|
|
# overflow-checks.
|
||
|
|
[profile.miri]
|
||
|
|
inherits = "test"
|
||
|
|
opt-level = 1
|
||
|
|
overflow-checks = true
|
||
|
|
```
|
||
|
|
|
||
|
|
## `Cargo.toml` — workspace level
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[workspace]
|
||
|
|
resolver = "3"
|
||
|
|
|
||
|
|
[workspace.package]
|
||
|
|
edition = "2024"
|
||
|
|
rust-version = "1.83" # bump only when a needed feature lands
|
||
|
|
license = "Apache-2.0 OR MIT"
|
||
|
|
|
||
|
|
[workspace.lints]
|
||
|
|
# Then in each member crate:
|
||
|
|
# [lints]
|
||
|
|
# workspace = true
|
||
|
|
```
|
||
|
|
|
||
|
|
## `rustfmt.toml`
|
||
|
|
|
||
|
|
```toml
|
||
|
|
edition = "2024"
|
||
|
|
max_width = 100
|
||
|
|
imports_granularity = "Module"
|
||
|
|
group_imports = "StdExternalCrate"
|
||
|
|
reorder_imports = true
|
||
|
|
reorder_modules = true
|
||
|
|
newline_style = "Unix"
|
||
|
|
use_field_init_shorthand = true
|
||
|
|
use_try_shorthand = true
|
||
|
|
unstable_features = false
|
||
|
|
```
|
||
|
|
|
||
|
|
Most options come from stable rustfmt. `imports_granularity` and `group_imports` are nightly-only but ignored cleanly on stable; CI runs `cargo +nightly fmt --check` for the import grouping.
|
||
|
|
|
||
|
|
## `clippy.toml`
|
||
|
|
|
||
|
|
```toml
|
||
|
|
# Reduce cognitive load thresholds.
|
||
|
|
cognitive-complexity-threshold = 25
|
||
|
|
type-complexity-threshold = 250
|
||
|
|
too-many-arguments-threshold = 6
|
||
|
|
too-many-lines-threshold = 100
|
||
|
|
|
||
|
|
# msrv - keeps clippy from suggesting features past our MSRV
|
||
|
|
msrv = "1.83"
|
||
|
|
|
||
|
|
# Avoid `panic` lint complaining about derived Debug impls calling unreachable_unchecked etc.
|
||
|
|
allow-unwrap-in-tests = true
|
||
|
|
allow-expect-in-tests = true
|
||
|
|
allow-panic-in-tests = true
|
||
|
|
allow-dbg-in-tests = true
|
||
|
|
allow-print-in-tests = true
|
||
|
|
|
||
|
|
# Force named arguments above N params
|
||
|
|
single-char-binding-names-threshold = 4
|
||
|
|
```
|
||
|
|
|
||
|
|
## `deny.toml` (cargo-deny)
|
||
|
|
|
||
|
|
```toml
|
||
|
|
[advisories]
|
||
|
|
db-path = "~/.cargo/advisory-db"
|
||
|
|
db-urls = ["https://github.com/rustsec/advisory-db"]
|
||
|
|
yanked = "deny"
|
||
|
|
ignore = []
|
||
|
|
|
||
|
|
[licenses]
|
||
|
|
allow = [
|
||
|
|
"MIT",
|
||
|
|
"Apache-2.0",
|
||
|
|
"Apache-2.0 WITH LLVM-exception",
|
||
|
|
"BSD-2-Clause",
|
||
|
|
"BSD-3-Clause",
|
||
|
|
"ISC",
|
||
|
|
"Unicode-DFS-2016",
|
||
|
|
"Unicode-3.0",
|
||
|
|
"Zlib",
|
||
|
|
"MPL-2.0",
|
||
|
|
"CC0-1.0",
|
||
|
|
]
|
||
|
|
confidence-threshold = 0.93
|
||
|
|
exceptions = []
|
||
|
|
|
||
|
|
[bans]
|
||
|
|
multiple-versions = "warn"
|
||
|
|
wildcards = "deny"
|
||
|
|
highlight = "all"
|
||
|
|
deny = [
|
||
|
|
# Pin out unmaintained alternatives
|
||
|
|
{ name = "async-std", reason = "use tokio" },
|
||
|
|
{ name = "actix-web", reason = "use axum" },
|
||
|
|
{ name = "chrono", reason = "use jiff" },
|
||
|
|
]
|
||
|
|
|
||
|
|
[sources]
|
||
|
|
unknown-registry = "deny"
|
||
|
|
unknown-git = "warn"
|
||
|
|
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||
|
|
```
|
||
|
|
|
||
|
|
## CI Workflow (`.github/workflows/ci.yml`)
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
name: ci
|
||
|
|
on:
|
||
|
|
push:
|
||
|
|
branches: [main]
|
||
|
|
pull_request:
|
||
|
|
|
||
|
|
env:
|
||
|
|
CARGO_TERM_COLOR: always
|
||
|
|
RUST_BACKTRACE: 1
|
||
|
|
|
||
|
|
jobs:
|
||
|
|
fmt:
|
||
|
|
runs-on: ubuntu-latest
|
||
|
|
steps:
|
||
|
|
- uses: actions/checkout@v4
|
||
|
|
- uses: dtolnay/rust-toolchain@nightly
|
||
|
|
with:
|
||
|
|
components: rustfmt
|
||
|
|
- run: cargo +nightly fmt --all -- --check
|
||
|
|
|
||
|
|
clippy:
|
||
|
|
runs-on: ubuntu-latest
|
||
|
|
steps:
|
||
|
|
- uses: actions/checkout@v4
|
||
|
|
- uses: dtolnay/rust-toolchain@stable
|
||
|
|
with:
|
||
|
|
components: clippy
|
||
|
|
- uses: Swatinem/rust-cache@v2
|
||
|
|
- run: cargo clippy --all-targets --all-features --workspace -- -D warnings
|
||
|
|
|
||
|
|
test:
|
||
|
|
runs-on: ubuntu-latest
|
||
|
|
steps:
|
||
|
|
- uses: actions/checkout@v4
|
||
|
|
- uses: dtolnay/rust-toolchain@stable
|
||
|
|
- uses: Swatinem/rust-cache@v2
|
||
|
|
- uses: taiki-e/install-action@nextest
|
||
|
|
- run: cargo nextest run --all-targets --all-features --workspace
|
||
|
|
|
||
|
|
miri:
|
||
|
|
runs-on: ubuntu-latest
|
||
|
|
steps:
|
||
|
|
- uses: actions/checkout@v4
|
||
|
|
- uses: dtolnay/rust-toolchain@nightly
|
||
|
|
with:
|
||
|
|
components: miri, rust-src
|
||
|
|
- uses: Swatinem/rust-cache@v2
|
||
|
|
- uses: taiki-e/install-action@nextest
|
||
|
|
- env:
|
||
|
|
MIRIFLAGS: "-Zmiri-strict-provenance -Zmiri-symbolic-alignment-check"
|
||
|
|
run: cargo +nightly miri nextest run --all-features --workspace
|
||
|
|
|
||
|
|
machete:
|
||
|
|
runs-on: ubuntu-latest
|
||
|
|
steps:
|
||
|
|
- uses: actions/checkout@v4
|
||
|
|
- uses: dtolnay/rust-toolchain@stable
|
||
|
|
- uses: bnjbvr/cargo-machete@main
|
||
|
|
|
||
|
|
deny:
|
||
|
|
runs-on: ubuntu-latest
|
||
|
|
steps:
|
||
|
|
- uses: actions/checkout@v4
|
||
|
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||
|
|
with:
|
||
|
|
command: check all
|
||
|
|
|
||
|
|
audit:
|
||
|
|
runs-on: ubuntu-latest
|
||
|
|
steps:
|
||
|
|
- uses: actions/checkout@v4
|
||
|
|
- uses: rustsec/audit-check@v1
|
||
|
|
with:
|
||
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Project bootstrap
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cargo new --bin my-app --edition 2024
|
||
|
|
cd my-app
|
||
|
|
cargo install cargo-nextest cargo-machete cargo-deny cargo-edit cargo-watch
|
||
|
|
rustup install nightly
|
||
|
|
rustup component add miri --toolchain nightly
|
||
|
|
# drop the configs above
|
||
|
|
git add . && git commit -m "chore: bootstrap strict toolchain"
|
||
|
|
```
|
||
|
|
|
||
|
|
After every change:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cargo fmt --all -- --check && \
|
||
|
|
cargo clippy --all-targets --all-features -- -D warnings && \
|
||
|
|
cargo nextest run && \
|
||
|
|
cargo +nightly miri nextest run # only if unsafe is involved
|
||
|
|
```
|