Rust Standard

The Rust standard defines Wavenumber's first Rust profiles for host-side applications and embedded firmware. It keeps the audit boundary static: dev-std validates Cargo, rustup, repository shape, metadata, and signoff wiring. Tree-sitter Rust validates owned-source structure while Cargo, rustc, rustfmt, Clippy, rustdoc, tests, and hardware signoff prove Rust semantics.

Profiles

rust-app
Host-side Rust application, service, CLI, or ordinary std library with application-owned signoff.
rust-firmware
Embedded Rust firmware with no_std intent, cross-compilation, target, linker, runner, panic, allocator, and hardware signoff policy.

default_rust_app_standard

Returns the host-side Rust application and library profile.

default_rust_firmware_standard

Returns the embedded Rust firmware profile.

render_rust_app_standard

Renders the host-side Rust profile for human or machine use.

render_rust_firmware_standard

Renders the embedded Rust firmware profile for human or machine use.

Required Files

Configuration

Rust profile metadata lives under [rust]. The most common polyglot setting is source_root:

[rust]
source_root = "src/rs"

[rust.hygiene]
config = "rust-hygiene.toml"

The configured path must be repository-relative, stay inside the project root, and contain at least one owned .rs file. Generated, vendored, binding, and target/ output paths are excluded from owned-source discovery.

Documented exceptions live under [rust.exceptions]. The first supported keys are ambient_toolchain for projects that follow the ambient stable Rust toolchain and unsafe for reviewed unsafe-code boundaries.

[rust.exceptions]
ambient_toolchain = "docs/design/rust-toolchain-exception.html"
unsafe = "docs/design/unsafe-boundary.html"

Structural Hygiene

Greenfield rust-app and rust-firmware projects use strict structural signoff. The canonical limits are seven parameters per function or method, 100 physical lines per production function, 150 per test function, 1000 per owned source file, cyclomatic complexity 10, and control-flow nesting depth 4. These are Rust-specific review limits; they are intentionally not copied from the larger C/C++ file and function thresholds.

schema = 1
mode = "strict"

[limits]
max_parameters = 7
max_function_lines = 100
max_test_function_lines = 150
max_file_lines = 1000
max_cyclomatic_complexity = 10
max_nesting = 4

[paths]
source_roots = ["src", "tests"]

The dev-std scanner uses the maintained Tree-sitter Rust grammar rather than regular expressions. It recognizes free functions, methods, trait implementations, async functions, generics, attributes, nested modules, and macro token boundaries. Declarative or procedural macro expansion remains owned by rustc and Clippy because the unexpanded source tree does not contain generated function bodies. Nested functions receive their own identities and metrics; their branches and nesting do not inflate the containing function's measurements.

Source roots must cover the configured [rust].source_root and root integration tests, and every Cargo workspace member source and test directory. The canonical exclusions cover generated bindings, generated/, vendor/, third_party/, target/, and build output without excluding adjacent package-owned Rust. Polyglot siblings are not scanned because only .rs files below the declared Rust roots are owned.

The relaxed test-function line limit applies to functions in a tests module, exact #[test] or namespaced #[...::test] attributes, and exact #[cfg(test)] test helpers. Conditions such as #[cfg(not(test))] and #[cfg_attr(test, ...)] remain production functions and use the 100-line limit.

Clippy Structural Lints

Clippy owns semantic lint interpretation; dev-std owns the reviewed policy that pins thresholds and verifies failing signoff. Package lints, or root [workspace.lints.clippy] inherited by every member, must deny too_many_arguments, too_many_lines, cognitive_complexity, and allow_attributes_without_reason. The matching clippy.toml values are:

too-many-arguments-threshold = 7
too-many-lines-threshold = 100
cognitive-complexity-threshold = 15

The Tree-sitter gate separately measures cyclomatic complexity and nesting, properties that are not delegated to an implicit Clippy default.

Migration Ratchets And Exceptions

Existing debt uses mode = "ratchet" with a checked-in JSON baseline. A baseline identifies path, qualified item, rule, and current value. New violations and increases fail; resolved baseline entries remain visible as warnings until pruned. Baselines do not weaken canonical limits.

A reviewed exception is narrower than a baseline entry and must declare a stable id, exact path, qualified item, rule, accepted maximum value, rationale, and concrete review trigger. An unmatched exception fails as stale, and growth beyond the accepted maximum fails. Trait methods use the containing trait in their qualified identity so equally named methods do not share an exception or ratchet entry. Source-level #[allow] attributes also need Clippy's reason syntax.

[[exceptions]]
id = "ffi-callback-v1"
path = "src/ffi/callback.rs"
item = "Callback::invoke"
rule = "max_parameters"
max_value = 8
reason = "The reviewed C ABI fixes the callback signature."
review_trigger = "Remove when ABI v2 is the minimum supported version."

Starter files live in docs/templates/rust/. Local and CI/Rack signoff use the same failing command: dev-std audit . --scope language. Report-only structural lanes, missing configs, weaker thresholds, missing workspace inheritance, and incomplete baselines or exceptions fail the audit.

Tree-sitter and its Rust grammar install as locked runtime dependencies of wn-dev-std; consumers do not install a separate parser binary. The scanner supports the Rust grammar shipped with that reviewed package version and reports deterministic path, qualified item, rule, measured value, and limit details through normal text or JSON audit output. The TOML policy and JSON baseline contracts are published as docs/contracts/rust_hygiene.schema.v0.json and rust_hygiene_baseline.schema.v0.json.

Cargo Metadata

Cargo.toml must define either [package] or [workspace]. Package metadata must declare edition and rust-version. Workspace member packages may inherit those fields with edition.workspace = true and rust-version.workspace = true only when [workspace.package] defines the inherited values. Workspace roots should centralize shared metadata with [workspace.package], [workspace.dependencies], [workspace.lints], and root profiles where appropriate.

Workspace member discovery follows [workspace].members and honors bounded repository-relative [workspace].exclude entries. Member globs are treated as package-directory globs; non-directory matches such as notes files under crates/ are ignored.

Workspaces must declare resolver. Edition 2024 workspaces use resolver = "3"; Edition 2021 workspaces may use resolver = "2" or "3" when the project documents the pairing.

Toolchain

rust-toolchain.toml must contain a [toolchain] table with a stable channel, rustfmt, and clippy components. Nightly, beta, or custom channels require a documented exception and review trigger. Firmware profiles must include the configured cross-compilation target unless the target is a checked-in custom target JSON file.

Unsafe Policy

Host Rust projects must set unsafe_code = "forbid" in [lints.rust] or [workspace.lints.rust]. Workspace lints are active only for packages that opt in with [lints] workspace = true; a member may also satisfy the policy with a local [lints.rust] entry, but local member overrides must still use the profile's allowed unsafe lint level. Firmware may use "forbid" or "deny" by default, or declare a documented [rust.exceptions].unsafe boundary for hardware registers, generated bindings, FFI, or reviewed low-level code. The audit validates the lint metadata and exception document; it does not scan Rust source for unsafe blocks.

Signoff Commands

Rack or an equivalent project signoff surface must expose these host Rust lanes:

Rustdoc warning failures are controlled with the RUSTDOCFLAGS environment variable, not a Cargo flag. Cargo commands should use --locked in CI and release-facing signoff so dependency resolution remains deterministic. Audit checks command markers per declared command entry; --locked on one command does not satisfy another Cargo lane. The separate cargo test --doc lane is required even when a plain cargo test lane may also run doctests, because the standard wants doctest coverage to remain visible and independently auditable when projects later add target filters.

Embedded Rust

rust-firmware adds [rust.firmware] metadata:

[rust.firmware]
target = "thumbv7em-none-eabihf"
no_std_ref = "docs/design/rust-firmware-runtime.html"
panic_ref = "docs/design/rust-firmware-runtime.html"
allocator_ref = "docs/design/rust-firmware-runtime.html"
hardware_ref = "docs/setup.html"
memory_layout = "memory.x"
runner = "Embed.toml"

The audit treats no_std as metadata and documentation. It does not inspect Rust source for #![no_std]. Firmware projects must declare the target triple or custom target spec, .cargo/config.toml, target/rustflags/runner policy, memory or linker artifacts such as memory.x or a documented link.x provider, panic strategy, allocator policy, hardware setup, flashing/debugging workflow, and host-test split.

Embedded signoff should include:

Firmware repositories that are pure no_std binaries must still provide a host-buildable crate, feature split, or wrapper surface for the host cargo test, doctest, and rustdoc lanes. Board-only code remains covered by the target build, Clippy target lane, and hardware runner.

Runtime Frameworks

Tokio is the recommended default for host-side async applications that need async I/O, networking, timers, scheduling, or service runtime facilities. Embassy is the recommended default for embedded async firmware when target and HAL support are acceptable. These are not hard dependencies in the first slice. Projects that stay synchronous, use a framework-owned runtime, use RTIC, or rely on a vendor or RTOS scheduler document the runtime choice in setup or architecture docs.

Polyglot Layouts

Contract-first repositories may keep shared schemas, fixtures, docs, and test cases at the umbrella root while language implementations live under src/<language>/. Rust model code may live under src/rs/data_models/<domain>/ or src/rust/data_models/<domain>/. Rust transforms may use operation-first roots such as src/rs/transforms/importers/<namespace>/<domain>/, src/rs/transforms/exporters/<namespace>/<domain>/, src/rs/transforms/projections/<source-domain>_to_<target-domain>/, and src/rs/transforms/migrations/<domain>/.

Generated Rust bindings or schema copies must be isolated under generated roots and governed by artifact/vendor policy instead of counted as owned implementation source.