with one click
rust-style
// General Rust conventions for Pluto. Use it once for understanding the codebase better.
// General Rust conventions for Pluto. Use it once for understanding the codebase better.
Guides Go→Rust porting for Pluto. Invoke when asked to port, implement parity for, or translate a Go component.
Iteratively review and fix a Pluto PR until it is "ideal" — drives the /review-pr multi-agent pipeline inside /ralph-loop, applying fixes between iterations and never posting inline comments. After the loop terminates, posts a single summary comment to the GitHub PR with everything that was resolved. Invoke as `/loop-review-pr <PR-number|PR-URL> [--max-iterations N]`.
Use when implementing, reviewing, debugging, or explaining Pluto Rust libp2p code: Node/P2PContext ownership, PlutoBehaviour composition, NetworkBehaviour and ConnectionHandler protocols, relay/force-direct/quic-upgrade behaviours, peerinfo/parsigex protocol handlers, DKG protocol handlers, and P2P tests.
Full multi-agent code review for a Pluto PR. Spawns parallel agents covering functional correctness, security, Rust style, and code quality, then posts all findings as isolated GitHub review comments and submits a final approve/request-changes verdict. Invoke as `/review-pr <PR-number>` or `/review-pr <GitHub-PR-URL>`.
Use when porting Charon components to understand Go codebase architecture, workflow, design patterns, or component responsibilities
Pluto-specific code review guidelines. Use as a general guideline when asked to conduct a code review.
| name | rust-style |
| description | General Rust conventions for Pluto. Use it once for understanding the codebase better. |
Run from pluto/ before declaring any work done:
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
cargo deny check
All must pass clean.
Define module-local error enums with thiserror:
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("message: {0}")]
Variant(String),
#[error(transparent)]
Underlying(#[from] OtherError),
}
pub type Result<T> = std::result::Result<T, Error>;
Rules:
errors.New("msg") → enum variant with #[error("msg")] (match strings exactly)errors.Wrap(err, "...") → #[from] / #[source] where appropriate#[error("...")] format string. If a field is not surfaced in the error message, either include it or remove it from the variant. Dead payload (captured but never displayed) is not allowed.?; never swallow errors with .ok() or filter_map in production codeunwrap(), expect(), panic!() outside of test codeanyhow in library crates; use typed errors everywhereAll arithmetic must be checked — arithmetic_side_effects = "deny" is enforced:
// Bad
let x = a + b;
// Good
let x = a.checked_add(b).ok_or(Error::Overflow)?;
Never use as for numeric type conversions — use fallible conversions with try_from:
// Bad - will cause clippy errors
let x = value as u32;
let y = some_usize as u64;
// Good - use try_from with proper error handling
let x = u32::try_from(value)?;
let y = u64::try_from(some_usize).expect("message explaining why this is safe");
Rules:
TryFrom/try_from for numeric conversions between different types? or expect with justification)expect is when the conversion is guaranteed to succeed (e.g., usize to u64 on 64-bit platforms)as casts: cast_possible_truncation, cast_possible_wrap, cast_sign_lossasync/await for I/O and network-bound code; use Tokio as the runtime.tokio::fs / tokio::io instead of blocking std::fs / std::io.tokio::task::spawn_blocking.tokio::sync::*) over std::sync::* when tasks may .await.tokio::time for timeouts, sleeps, and intervals (avoid std::thread::sleep).snake_case, types PascalCase, constants SCREAMING_SNAKE_CASE.format!("hello {name}")format!("hello {}", name)// TODO: and remove before PR merge.Prefer generic parameters over concrete types when a function only needs the behavior of a trait. This mirrors the standard library's own conventions and makes functions callable with a wider range of inputs without extra allocations.
| Instead of | Prefer | Accepts |
|---|---|---|
&str | impl AsRef<str> | &str, String, &String, … |
&Path | impl AsRef<Path> | &str, String, PathBuf, &Path, … |
&[u8] | impl AsRef<[u8]> | &[u8], Vec<u8>, arrays, … |
&Vec<T> | impl AsRef<[T]> | Vec<T>, slices, arrays, … |
String (owned, read-only) | impl Into<String> | &str, String, … |
Examples:
// accepts &str, String, PathBuf, &Path, …
fn read_file(path: impl AsRef<std::path::Path>) -> std::io::Result<String> {
std::fs::read_to_string(path.as_ref())
}
// accepts &str, String, &String, …
fn print_message(msg: impl AsRef<str>) {
println!(“{}”, msg.as_ref());
}
// accepts &[u8], Vec<u8>, arrays, …
fn hash_bytes(data: impl AsRef<[u8]>) -> [u8; 32] {
sha256(data.as_ref())
}
Rules:
.as_ref() once at the top of the function and bind it to a local variable when the value is used in multiple places.impl AsRef<T> if the function immediately converts to an owned type anyway — use impl Into<T> (or just accept the owned type) in that case.#[tokio::test] for async tests.test-case for repeated/parameterized tests (including async):#[cfg(test)]
mod tests {
use test_case::test_case;
#[test_case(1, 2 ; "small")]
#[test_case(10, 20 ; "large")]
fn adds(a: u64, b: u64) {
let _ = (a, b);
}
#[test_case("a" ; "case_a")]
#[test_case("b" ; "case_b")]
#[tokio::test]
async fn async_cases(input: &str) {
let _ = input;
}
}
Apply when reviewing or porting code:
Ordering::SeqCst is justified; prefer Relaxed/AcqRel for
standalone flags.Error::Io wraps std::io::Error (not String) to preserve
ErrorKind.impl AsRef<[u8]> / impl AsRef<str>
rather than concrete slice refs where appropriate.unwrap() / expect() / panic!() outside test code.checked_add, checked_mul, …).use declarations appear before all other items in each file.#[error("...")] string).