writing-rust
Skill127 starsChanged 11 days agoMITView on GitHub
Use when writing or reviewing Rust in crates/, apps/backend, or bindings/ - covers the shared helpers to reach for (errors, retry, time, spawn, spans, newtypes, transactions), the crate and module conventions, and the anti-patterns lint does not catch
2 other projects use a version of this.
Adopted by
Most recently taken up by
- weaverbase/skillstheir own version, first seen Oct 13, 2026
- Chogos/claude-skillstheir own version, first seen Mar 9, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- Chogos/claude-skills★ 1same name
- weaverbase/skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository21 other files it gives its agents.
- AGENTS.mdAGENTS.md
- docs/AGENTS.mdAGENTS.md
- .github/workflows/AGENTS.mdAGENTS.md
- nix/AGENTS.mdAGENTS.md
- proto/AGENTS.mdAGENTS.md
- sdks/AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .github/workflows/CLAUDE.mdCLAUDE.md
- nix/CLAUDE.mdCLAUDE.md
- proto/CLAUDE.mdCLAUDE.md
- sdks/CLAUDE.mdCLAUDE.md
- authoring-specsSkill
- check-ciSkill
- checking-spec-complianceSkill
- reviewing-specsSkill
- smoke-checkSkill
- working-with-nixSkill
- working-with-worktreesSkill
- writing-agent-docsSkill
- writing-rust-testsSkill
- writing-typescriptSkill
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 2 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Search for an existing helper before adding one.
- Distinctive: line 11
Never copy a function between crates.
- Very specific: line 19
Error control flow: use typed error variants or stable error codes.
- Very specific: line 20
Never branch on Display, Debug, or error message text.
- Very specific: line 23
Never std::time::Instant or tokio::time in shared code.
- Very specific: line 25
Never bare tokio::spawn. See async.md.
- Very specific: line 34
Constants: every number has a name. A value shared by more than one crate goes in xmtp_configuration.
- Distinctive: line 102
Read one when you start that kind of work.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Writing Rust in libxmtp
Search for an existing helper before adding one. Shared code lives in a shared
crate (xmtp_common, xmtp_proto, xmtp_configuration, xmtp_mls_group_state,
xmtp_mls_group_keys, xmtp_mls_tls_collections).
Never copy a function between crates; move it and import it. Read the package's
AGENTS.md before working in it.
Essentials
- Errors:
thiserrorenums,#[from]on sub-errors,#[derive(ErrorCode)]when the code crosses FFI, a hand-writtenRetryableError. Never stringify an inner error. See errors.md. - Error control flow: use typed error variants or stable error codes.
Never branch on
Display,Debug, or error message text. Preserve the typed cause and code across FFI and transport boundaries. Messages are for diagnostics only. - Time:
xmtp_common::time::{now_ns, sleep, timeout, Duration, Instant}. Neverstd::time::Instantortokio::timein shared code. - Tasks:
xmtp_common::spawn(ready, fut)and keep theStreamHandle. Never baretokio::spawn. See async.md. - Send bounds:
MaybeSend + MaybeSync,BoxDynError,#[xmtp_common::async_trait]. Wasm has noSend. - Logging:
tracingonly.#[xmtp_common::rpc_span]/db_span/mls_span/err_span;log_event!for product events. Never log keys, credentials, payloads, or full ids, except full inbox and installation ids in client logs; backend telemetry follows OPS-014. See logging.md. - Ids:
GroupId,InstallationId,Topic,Cursorfromxmtp_proto::types, notVec<u8>. See types.md. - Constants: every number has a name. A value shared by more than one crate
goes in
xmtp_configuration. - Client database:
XmtpMlsStorageProvider::transactionreturningTransactionOutcome;impl_store!/impl_fetch!for plain tables. See database.md. - Tests:
#[xmtp_common::test(unwrap_try = true)]. See thewriting-rust-testsskill.
Reach for the helper
| Need | Use | Path |
| Retry with backoff | retry_async!(Retry::default(), (async { .. })) | xmtp_common |
| Is this error retryable | retryable!(err) | xmtp_common |
| Wall clock | time::now_ns() -> i64, now_secs() -> i64 | xmtp_common::time |
| Bounded wait | time::timeout(d, fut) -> Result<_, Expired> | xmtp_common::time |
| Periodic worker | time::jittered_interval_stream(base, jitter) | xmtp_common::time |
| Spawn a task | spawn(None, fut) -> impl StreamHandle | xmtp_common |
| Platform split | if_native! {} / if_wasm! {} / wasm_or_native! {} | xmtp_common |
| Test-only item | if_test! {} (also test-utils), if_only_test! {} | xmtp_common |
| HTTP client | http::client() / http::client_builder() | xmtp_common::http |
| Token bucket | rate_limit::Bucket::new(rate, burst) | xmtp_common::rate_limit |
| Log an error, keep going | optify!(result, "msg") -> Option<T> | xmtp_common |
| Short id in a log line | id.short_hex() | xmtp_proto::ShortHex |
| SHA-256 | sha256_array(&[u8]) -> [u8; 32] | xmtp_common |
| Normalize a hex id | s.normalize_hex() | xmtp_common::hex::NormalizeHex |
| Nanosecond constants | NS_IN_SEC, NS_IN_HOUR, NS_IN_DAY | xmtp_common |
| Group or installation id | GroupId::try_from(&[u8]), InstallationId::from([u8; 32]) | xmtp_proto::types |
| Topic bytes | Topic::new_group_message(id), Topic::parse(bytes) | xmtp_proto::types |
| MLS message id | utils::id::calculate_message_id(..) | xmtp_mls |
| Own secret bytes (field, return, binding) | Secret::new(bytes), .as_slice() | xmtp_cryptography::secret |
| Transient secret buffer | Zeroizing::new(buf) where the buffer is made | xmtp_cryptography::secret |
| Database column that holds a secret | StoredSecret (BLOB, serde as a byte string) | xmtp_db |
| Byte field of a stored libxmtp type | #[serde(with = "xmtp_db::byte_string")] (byte_strings, optional_byte_strings) | xmtp_db |
| Hand-written secret holder | Zeroize, ZeroizeOnDrop | xmtp_cryptography::secret |
| Prove a wipe in a test | install_heap_canary!(), Canary, scan / scan_async | xmtp_cryptography::secret::heap_canary |
xmtp_common re-exports retry, wasm, stream_handles, const,
event_logging, hash, and rand at its root. time, fmt, hex, http,
snippet, types, and rate_limit need the module path.
Anti-patterns
| Do not | Use instead |
loop { .. sleep .. } retry | retry_async!(Retry::default(), (async { .. })) |
reqwest::Client::new() / Client::builder() | xmtp_common::http::client() / client_builder() |
std::time::SystemTime::now() / Instant::now() in shared code | xmtp_common::time::{now_ns, Instant} |
tokio::spawn in a shared crate | xmtp_common::spawn(None, fut); keep the handle |
#[xmtp_common::timeout] in production (it panics) | xmtp_common::time::timeout(..); handle Expired |
Bare #[tracing::instrument] on an API, DB, MLS, or FFI fn | rpc_span / db_span / mls_span / err_span |
| Logging a full id, key, or payload | omit keys and payloads; short_hex() ids, except client inbox and installation ids; skip_all |
Vec<u8> for a group or installation id | GroupId / InstallationId |
Raw conn.transaction(..) in a client crate | XmtpMlsStorageProvider::transaction(|tx| ..) |
Hand-written Store / Fetch for a plain table | impl_store! / impl_fetch! |
| TLS client before provider install (native) | install_crypto_provider() at the entry point |
| Re-deriving an MLS message id | utils::id::calculate_message_id(..) |
| Renaming an error variant silently | #[error_code("Type::OldName")] |
Editing schema_gen.rs, dist/, or src/gen/** | the regeneration command in the owning AGENTS.md |
println! in a library crate | tracing; println! only in apps/* binaries |
.ok() that hides an error | optify!(result, "why") |
Secret bytes in a bare Vec<u8>, [u8; N] or String | Secret, or Zeroizing<T> for a transient buffer. See types.md |
#[derive(Debug)] on a type with a secret field whose Debug prints it (bare bytes, Zeroizing<T>, an OpenMLS HPKE key) | a hand-written Debug that shows only the length, or a redacting field type (Secret, StoredSecret) |
Formatting HpkePrivateKey, HpkeKeyPair, KeyPackageBundle, SecretVLBytes or Zeroizing<T> ({:?}, ?field) | log a public value or a length; these Debug impls print the key |
SigningKey::to_bytes() and other key-copying accessors | borrow (as_bytes()) or move (from_signing_key); clippy bans them |
Reference
Read one when you start that kind of work.
- errors.md —
ErrorCodederive forms,RetryableError, the glossary, binding error wrappers - async.md — time, spawn and
StreamHandle, retry,Sendbounds, platform macros, locks, HTTP - logging.md — span macros,
log_event!, the never-log list, id truncation - types.md — newtypes, configuration layout, cryptography, secrets and identity entry points
- database.md — client transactions and table macros, backend storage rules
- crates.md — crate layout, features, workspace deps, hakari, RustDoc, bindings
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

