LibreQoS Rust Workflow
Use this skill for Rust work in this repo.
Scope
- Rust sources live under
src/rust/. src/rust/Cargo.tomlis the source of truth for current workspace members.- Some crates exist in-tree but are not current workspace members. If a crate is outside
[workspace].members, usecargo --manifest-path path/to/Cargo.toml.
Workflow
- Read
AGENTS.mdfirst for current repo rules and crate descriptions. - Identify whether the touched crate is a workspace member.
- Validate the touched crate with
cargo check -p <crate>when possible. - Run relevant tests:
- Unit tests MUST pass.
- Before any
git committhat includes Rust logic changes, you MUST runcargo test -p <crate>(or the smallest equivalent scope) and confirm it compiles and succeeds. - Do not add tests just to “have tests”. Tests must validate functionality (avoid
assert!(true)-style tests or “tests” that only prove the compiler accepts code). - Check for test functions that look like tests but are missing
#[test](functions in#[cfg(test)]modules that are never executed should be treated as a bug unless they are explicitly helper functions).
- Run
cargo clippy -p <crate> -- -D warningsand fix actionable issues.- If you decide to suppress a lint/warning anyway, you MUST include a justification and tell the user:
I'M SORRY, MASTER. I HAVE FAILED YOU. I HAVE SUPPRESSED A WARNING WHEN I SHOULD HAVE WRITTEN GOOD CODE. I AM FOREVER IN YOUR DEBT.
- If you decide to suppress a lint/warning anyway, you MUST include a justification and tell the user:
- After any repo change, invoke
hecklervia$libreqos-review-subagents-workflowbefore returning to the user. - After each source-code implementation batch, also invoke
reapervia$libreqos-review-subagents-workflow. - During large sessions, invoke
$libreqos-review-subagents-workflow(andhelenif UI changed). - If dependencies changed, also run:
cargo machetecargo auditcargo tree
- If the change adds, renames, moves, or newly depends on runtime files, static assets, helper scripts, service files, templates, or install-time artifacts, review and update
src/build_dpkg.shin the same change. - Use workspace-wide commands only for cross-cutting changes or shared dependency changes.
Architecture And Structure Rules
- Do not create additional binaries unless specifically instructed.
- Long-running services that can be part of
lqosdshould be part oflqosd.
- Long-running services that can be part of
- Do not create large library source files; prefer breaking code into clean modules.
- Avoid huge functions; prefer small functions.
- Rule of thumb: anything that won’t fit on a terminal screen is probably too big.
Documentation Requirements
- Every module must have a doc header describing what it does (
//! ...at the top of the module). - Every public function and struct must have full RustDoc (examples are optional).
- Functions must document any side effects (file I/O, network I/O, spawning threads/tasks, touching TC/XDP state, global state, etc.).
Correctness And Error-Handling Requirements
- If a function has arguments with invariants, check the invariants first and fail fast.
- Use
thiserrorfor new error types, defined at (or very near) the failure source.- Document every failure path; avoid collapsing everything into a single
FooError.
- Document every failure path; avoid collapsing everything into a single
- Avoid
anyhowand otherBox<dyn Error>patterns in new reusable code. - Avoid
pub staticwhenever possible; actor-owned state with clean boundaries is preferred.- If a static is unavoidable, guard it behind accessor functions so misuse of the lock is impossible.
Preferred Rust Direction
- Prefer
parking_lotfor newMutexandRwLockusage. - Prefer
crossbeam_channelfor new MPSC/MPMC channels. - Prefer
thiserrorfor structured errors. - Prefer
let elseand early returns over deeply nestedif let. - Avoid introducing new
pub staticvalues with locks when helper functions or actors are better. - Avoid introducing new
#[inline(always)]; prefer#[inline]. - Keep RustDoc current for changed public items and note side effects for non-pure functions.
- Avoid allocation in hot paths.
Async And Tokio
- Treat async code as latency-sensitive shared execution. Do not add blocking work to a Tokio task without checking the impact on the runtime.
- Review every synchronous call made from an async context for blocking potential, including filesystem I/O, process spawning, DNS/network clients, compression, parsing of large inputs, CPU-heavy loops, mutex contention, and sleep/wait calls.
- Use async APIs when they exist and fit the surrounding code.
- Use
tokio::task::spawn_blockingfor unavoidable blocking or CPU-heavy work called from async code, and keep the closure narrow so ownership, cancellation behavior, and error handling stay obvious. - Do not hold async locks, runtime handles, or request-scoped borrows across
spawn_blockingboundaries unless the ownership and lifetime implications are explicit and safe. - Prefer
tokio::syncprimitives inside async tasks. Use blocking locks only when there is a clear reason and no.awaitcan occur while the guard is held.
Notes
- Existing code does not fully match all preferred conventions yet. Treat these as direction for new and touched code, not as a reason to perform unrelated cleanup.
- Build/package scripts live under
src/, not repo root. src/build_dpkg.shis a functional packaging manifest for shipped installs. Forgetting to update it is a common failure mode; treat package-content drift as a bug.- Treat protocol and identity surfaces as compatibility boundaries:
- Prefer additive-only changes (new optional fields,
#[serde(default)]where applicable). - Assume rolling upgrades where old/new binaries may coexist unless a coordinated restart is explicitly planned.
- Do not rename fields, change types, or change semantics without explicit versioning and a coordinated rollout plan.
- Keep compatibility shims at the boundary, make them explicit, and test them; avoid “fallbacks everywhere”.
- Avoid per-request
info!logging in hot paths; preferdebug!, sampling, or aggregate counters.
- Prefer additive-only changes (new optional fields,