rust-async
Skill47 starsChanged 9 months agoMITView on GitHub
Advanced async patterns expert. Handles Stream processing, backpressure control, select/join operations, cancellation, Future trait implementation, and async runtime optimization.
We split this skill into the rules it gives an agent
5 rules. 0 are common, 5 are distinctive, and 0 are very specific to this project.
0
Common
Most of the 3 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 91
Gotcha: All branches must be cancellation-safe.
- Distinctive: line 185
Use when: All results needed regardless of individual failures.
- Distinctive: line 200
Use when: All operations must succeed, fail fast on errors.
- Distinctive: line 322
Gotcha: Must properly handle wakeup notifications.
- Distinctive: line 327
When reviewing async code:
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Solution Patterns
Pattern 1: Stream Processing
use tokio_stream::{self as stream, StreamExt};
async fn process_stream(stream: impl Stream<Item = Data>) {
stream
.chunks(100) // Batch processing
.for_each(|batch| async {
process_batch(batch).await;
})
.await;
}When to use: Processing continuous data flows (websockets, file streams, API pagination).
Key insight: Streams are async iterators - pull-based, lazy evaluation.
Pattern 2: Backpressure Control
use tokio::sync::Semaphore;
use std::sync::Arc;
let semaphore = Arc::new(Semaphore::new(10)); // Max 10 concurrent
let stream = tokio_stream::iter(0..1000)
.map(|i| {
let permit = semaphore.clone().acquire_owned();
async move {
let _permit = permit.await?;
process(i).await
}
})
.buffer_unordered(100); // Max 100 buffered futuresWhen to use: Prevent overwhelming downstream systems or resource exhaustion.
Trade-offs: Adds latency but prevents overload.
Pattern 3: Select Multiplexing
use tokio::select;
use tokio::time::{sleep, Duration};
async fn multiplex() {
loop {
select! {
msg = receiver.recv() => {
if let Some(msg) = msg {
handle(msg).await;
} else {
break; // Channel closed
}
}
_ = sleep(Duration::from_secs(5)) => {
// Timeout handling
check_health().await;
}
else => break, // All branches complete
}
}
}When to use: Waiting on multiple async operations, first-to-complete wins.
Gotcha: All branches must be cancellation-safe.
Pattern 4: Task Cancellation
use tokio::time::timeout;
use std::time::Duration;
async fn with_timeout() -> Result<Value, TimeoutError> {
timeout(Duration::from_secs(5), long_operation()).await
.map_err(|_| TimeoutError)?
}
// Cooperative cancellation
let mut task = tokio::spawn(async move {
loop {
// Check cancellation
tokio::task::yield_now().await; // Yield point
// Do work
if let Err(_) = work().await {
return;
}
}
});
// Cancel task
task.abort();
let _ = task.await; // Will return JoinError::CancelledWhen to use: Operations with time limits or user-requested cancellation.
Key insight: Cancellation is cooperative - requires yield points.
Workflow
Step 1: Choose Stream vs Iterator
Sync data source?
→ Use Iterator (more efficient)
Async data source (network, DB)?
→ Use Stream
Need backpressure?
→ Definitely StreamStep 2: Design Concurrency Strategy
Sequential processing?
→ for_each / fold
Limited concurrency?
→ buffer_unordered(N) + Semaphore
Unlimited (dangerous)?
→ Use with extreme cautionStep 3: Handle Cancellation
Long-running task?
→ Add timeout wrapper
User-initiated?
→ Implement abort signal
Resource cleanup?
→ Use Drop or explicit cleanupJoin vs Try_Join
Join - Wait for All
use tokio::join;
// All operations run concurrently, wait for all to complete
let (a, b, c) = join!(
fetch_user(),
fetch_posts(),
fetch_comments()
);
// All values available, even if some operations failedUse when: All results needed regardless of individual failures.
Try_Join - Fail Fast
use tokio::try_join;
// Stop on first error
let (a, b) = try_join!(
async_op_a(),
async_op_b()
)?;
// Both succeeded, or error from first failureUse when: All operations must succeed, fail fast on errors.
Combined Pattern
async fn fetch_dashboard() -> Result<Dashboard, Error> {
let (user, posts, comments) = try_join!(
fetch_user(),
fetch_posts(),
fetch_comments()
)?;
Ok(Dashboard { user, posts, comments })
}Common Errors & Solutions
| Error | Cause | Solution |
.await forgotten | Future not polled | Check all async fn calls have .await |
| Cancellation unhandled | Task aborted mid-operation | Implement cooperative cancellation |
| Missing backpressure | Unbounded concurrency | Use Semaphore or buffer_unordered |
| Deadlock | Lock held across .await | Minimize lock scope, drop before await |
| Async drop unsupported | Drop in async context | Use spawn for cleanup or blocking drop |
Backpressure Strategies
Strategy 1: Semaphore-Based
let sem = Arc::new(Semaphore::new(10));
stream
.map(|item| {
let sem = sem.clone();
async move {
let _permit = sem.acquire().await?;
process(item).await
}
})
.buffer_unordered(10)Pros: Precise control, easy to reason about Cons: Semaphore overhead
Strategy 2: Buffered Stream
stream
.chunks(100)
.for_each_concurrent(5, |batch| async move {
process_batch(batch).await
})
.awaitPros: Simple, built-in to StreamExt Cons: Less fine-grained control
Strategy 3: Channel-Based
let (tx, mut rx) = mpsc::channel(100); // Buffer size = backpressure
// Producer respects backpressure
tx.send(item).await?;
// Consumer pulls at own pace
while let Some(item) = rx.recv().await {
process(item).await;
}Pros: Natural backpressure from bounded channel Cons: Extra copy/move overhead
Performance Tips
| Pattern | Performance Insight |
select! | More lightweight than multiple tokio::spawn |
buffer_unordered | More flexible than for_each_concurrent |
.chunks() | Reduces per-item overhead for bulk operations |
| Lock-free at await | Never hold locks across .await points |
spawn_blocking | Use for CPU-bound work in async context |
Advanced: Future Trait
Implementing Future
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
struct Delay {
when: Instant,
}
impl Future for Delay {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if Instant::now() >= self.when {
Poll::Ready(())
} else {
// Wake me later
cx.waker().wake_by_ref();
Poll::Pending
}
}
}When to implement: Custom async primitives, advanced control flow.
Gotcha: Must properly handle wakeup notifications.
Review Checklist
When reviewing async code:
- All async functions are properly
.awaited - Backpressure mechanisms in place for streams
- Cancellation handled cooperatively (yield points)
- No locks held across
.awaitpoints - Stream processing uses appropriate concurrency limits
- Error propagation uses
?or proper handling select!branches are cancellation-safe- Long-running tasks have timeout protection
- Resource cleanup happens even on cancellation
- CPU-bound work uses
spawn_blocking
Verification Commands
# Check async code compilation
cargo check
# Run async tests
cargo test
# Check for common async mistakes
cargo clippy -- -W clippy::await_holding_lock
# Test with tokio-console for debugging
RUSTFLAGS="--cfg tokio_unstable" cargo run
# Profile async runtime
cargo flamegraph --bin your-appCommon Pitfalls
1. Forgotten Await
Symptom: Future never executes, unexpected behavior
// ❌ Bad: future not awaited
async fn bad() {
fetch_data(); // Returns Future, never runs!
}
// ✅ Good
async fn good() {
fetch_data().await; // Actually runs
}2. Unbounded Concurrency
Symptom: Resource exhaustion, system overload
// ❌ Bad: all operations run concurrently
let futures: Vec<_> = urls.iter()
.map(|url| fetch(url))
.collect();
let results = join_all(futures).await;
// ✅ Good: limited concurrency
use futures::stream::{self, StreamExt};
let results = stream::iter(urls)
.map(|url| fetch(url))
.buffer_unordered(10) // Max 10 concurrent
.collect::<Vec<_>>()
.await;3. Lock Across Await
Symptom: Deadlock, "future cannot be sent between threads safely"
// ❌ Bad: lock held during await
let guard = mutex.lock().await;
some_async_op().await; // DANGER
drop(guard);
// ✅ Good: drop lock before await
let value = {
let guard = mutex.lock().await;
guard.clone()
}; // lock dropped
some_async_op().await;4. Async Drop
Symptom: Cannot await in Drop impl
// ❌ Bad: async operation in Drop
impl Drop for Resource {
fn drop(&mut self) {
// Cannot await here!
self.cleanup().await; // Won't compile
}
}
// ✅ Good: explicit async cleanup
impl Resource {
async fn cleanup(self) {
// Async cleanup logic
}
}
// Or spawn cleanup task
impl Drop for Resource {
fn drop(&mut self) {
let handle = self.handle.take();
tokio::spawn(async move {
if let Some(h) = handle {
h.cleanup().await;
}
});
}
}Related Skills
- rust-concurrency - Thread safety, Send/Sync basics
- rust-async-pattern - Async architecture patterns
- rust-ownership - Lifetime issues in async contexts
- rust-pin - Pin and self-referential types
- rust-performance - Async performance optimization
- rust-web - Async web frameworks (axum, actix)
Localized Reference
- Chinese version: SKILL_ZH.md - 完整中文版本,包含所有内容
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- adxptived/Rust-Skills★ 1same name
- dawidpereira/rust-skills★ 0same name
- takurot/rust-skills-comprehensive★ 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 repository40 other files it gives its agents.
- .codex/AGENTS.mdAGENTS.md
- SKILL.mdSkill
- rust-actorSkill
- rust-anti-patternSkill
- rust-async-patternSkill
- rust-authSkill
- rust-cacheSkill
- rust-codingSkill
- rust-concurrencySkill
- rust-constSkill
- rust-coroutineSkill
- rust-databaseSkill
- rust-distributedSkill
- rust-dpdkSkill
- rust-ebpfSkill
- rust-ecosystemSkill
- rust-embeddedSkill
- rust-error-advancedSkill
- rust-errorSkill
- rust-ffiSkill
- rust-gpuSkill
- rust-learnerSkill
- rust-lifetime-complexSkill
- rust-linear-typeSkill
- rust-macroSkill
- rust-middlewareSkill
- rust-mutabilitySkill
- rust-observabilitySkill
- rust-ownershipSkill
- rust-performanceSkill
- rust-pinSkill
- rust-resourceSkill
- rust-skill-indexSkill
- rust-skillSkill
- rust-testingSkill
- rust-type-drivenSkill
- rust-unsafeSkill
- rust-webSkill
- rust-xacmlSkill
- rust-zero-costSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
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.

