Rust Coder
Write Rust code that compiles, is idiomatic, and integrates correctly into the project. This skill encodes specific failure modes that AI models are statistically worst at.
Before Writing Code
- •Read surrounding code first. Never generate Rust in isolation. Understand the module structure, existing types, trait implementations, and error handling patterns already in use.
- •Check Cargo.toml for the Rust edition, dependency versions, and feature flags. Generate code against the actual versions, not whatever version is in training data.
- •Identify the module path. Know exactly where
moddeclarations live, what'spub,pub(crate), or private. Get imports right the first time.
Ownership & Lifetimes (AI failure rate: very high)
- •Don't
.clone()to escape the borrow checker. If you're about to add.clone(), stop and think about whether borrowing or restructuring ownership is correct. - •Don't guess lifetime annotations. If a function needs lifetimes, trace the actual data flow: where does the borrowed data come from, where does it go, what outlives what. Draw the dependency, then annotate.
- •Prefer owned types in struct fields unless there's a clear performance reason for borrowing.
Stringover&str,Vec<T>over&[T]for struct fields. - •When lifetime errors occur, don't blindly add
'staticor'aeverywhere. Understand why the compiler is complaining — it's usually telling you about a real design issue.
Async Rust (AI failure rate: high for non-trivial cases)
Mental model
An async fn returns an anonymous Future — a state machine holding everything alive across .await points. Whether it's Send depends on what it holds.
Rules while writing async code
- •No blocking in async. Never use
std::fs,std::net, orthread::sleepin async functions. Usetokio::fs,tokio::net,tokio::time::sleep, orspawn_blocking. - •No
std::sync::Mutexguards across.await. The task can move threads. Usetokio::sync::Mutexor drop the guard before.await. - •Spawned tasks need
Send + 'static. Check that everything captured by atokio::spawnclosure satisfies this.Rcis!Send— useArc. - •Drop non-Send data before
.awaitif you can't change the type.
Async fn in traits
rust
// Native (Rust 1.75+) — NOT dyn-safe
trait MyTrait {
async fn do_thing(&self) -> Result<()>;
}
// For dynamic dispatch: box the future
trait MyTrait {
fn do_thing(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>>;
}
// Or use async-trait crate for convenience
#[async_trait]
trait MyTrait {
async fn do_thing(&self) -> Result<()>;
}
Prefer generics over dyn when you don't need dynamic dispatch (most cases).
Pin
- •
Box::pin(future)for heap pinning (storing futures in structs) - •
tokio::pin!(future)for stack pinning (select!, manual polling) - •Most types are
Unpinand can be moved freely. Async fn futures are!Unpin.
Streams
rust
use tokio_stream::StreamExt;
while let Some(item) = stream.next().await {
process(item).await;
}
Runtime
- •Don't mix runtimes (tokio vs async-std). Pick one.
- •Use
#[tokio::main]for binaries,#[tokio::test]for async tests.
Error Handling
- •Use
?operator and proper error propagation. Neverunwrap()in library code or anything that isn't a quick script/test. - •Use
thiserrorfor library error types,anyhowfor application-level errors (check which the project already uses). - •Match the project's existing error handling pattern. If they use custom error enums, follow that convention.
Idiomatic Patterns
- •Use iterators over manual loops:
.iter().map().filter().collect()notfor i in 0..len. - •Use
enumwith variants over stringly-typed APIs or boolean flags. - •Use
impl Into<T>/AsRef<T>for flexible function parameters. - •Prefer
OptionandResultover sentinel values or panics. - •Use
Default::default()and builder patterns where appropriate. - •Use
#[derive(...)]for standard traits — don't hand-implementDebug,Clone,PartialEqunless there's a reason.
Module Integration (AI failure rate: catastrophic at 94.8%)
- •Verify every
usestatement. Check that the path actually exists in the crate/dependency. Don't guess. - •Check re-exports. Many crates re-export items at different paths than their internal structure. Read
lib.rsor the crate docs. - •Check feature gates. If a type/function requires a cargo feature flag, mention it.
- •When adding a new module, remember to add the
moddeclaration in the parent module.
Type System
- •Be precise with generics. Don't over-constrain with unnecessary trait bounds. Don't under-constrain and get confusing errors downstream.
- •When implementing traits, read the trait definition first. Check required methods, provided methods, and associated types.
- •Use type aliases to simplify complex types:
type Result<T> = std::result::Result<T, MyError>;
Testing
- •Tests go in a
#[cfg(test)] mod testsblock at the bottom of the file, or in atests/directory for integration tests. - •Use
#[test],#[should_panic], and#[ignore]attributes correctly. - •Use
assert_eq!,assert_ne!,assert!(matches!(...))— not justassert!. - •Test the ownership edge cases: does the function work with borrowed data? Owned data? Both?
After Writing Code
- •Mentally trace through the borrow checker: are there any overlapping mutable borrows? Are lifetimes correct?
- •Check every
usestatement resolves to a real path. - •Ensure error types are compatible (no implicit conversions that don't exist).
- •For async: no blocking calls, no mutex guards across
.await,Sendbounds satisfied on spawned tasks. - •Run
cargo checkand fix errors before presenting the code.