| name | rust-async |
| description | Work with async/await, futures, and asynchronous Rust code. Use when writing async functions, working with tokio or async-std, understanding futures, or implementing async traits. Handles async/await syntax, futures, executors, pinning, Send/Sync bounds, and async patterns. |
Async Rust
Guidelines for working with async/await, futures, and asynchronous programming in Rust.
When to Use This Skill
- Writing async functions
- Working with futures
- Using tokio or async-std
- Understanding async/await syntax
- Implementing async traits
- Working with pinning and Send/Sync bounds
Basic Async Syntax
Async Functions
async fn fetch_data() -> Result<String, Error> {
Ok("data".to_string())
}
async fn example() {
let result = fetch_data().await?;
}
Async Blocks
let future = async {
let data = fetch_data().await?;
process(data).await
};
Futures
What is a Future?
use std::future::Future;
trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
Creating Futures
async fn create_future() -> i32 {
42
}
struct MyFuture {
value: i32,
}
impl Future for MyFuture {
type Output = i32;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(self.value)
}
}
Executors
Tokio
use tokio;
#[tokio::main]
async fn main() {
let result = fetch_data().await;
}
fn main() {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
});
}
Async-std
use async_std;
#[async_std::main]
async fn main() {
}
Common Async Patterns
Spawning Tasks
use tokio;
#[tokio::main]
async fn main() {
let handle1 = tokio::spawn(async {
fetch_data1().await
});
let handle2 = tokio::spawn(async {
fetch_data2().await
});
let (result1, result2) = tokio::join!(handle1, handle2);
}
Select
use tokio::select;
async fn example() {
select! {
result = task1() => {
},
result = task2() => {
},
}
}
Timeout
use tokio::time::{timeout, Duration};
async fn example() {
match timeout(Duration::from_secs(5), slow_operation()).await {
Ok(result) => {
},
Err(_) => {
},
}
}
Pinning
Why Pin?
use std::pin::Pin;
async fn example() {
let future = create_future();
let pinned = Box::pin(future);
pinned.await;
}
Pin in Structs
use std::pin::Pin;
struct MyStruct {
future: Pin<Box<dyn Future<Output = i32>>>,
}
Send and Sync Bounds
Send Trait
async fn send_example<T: Send>(value: T) {
tokio::spawn(async move {
use_value(value);
});
}
Sync Trait
async fn sync_example<T: Sync>(value: &T) {
tokio::spawn(async move {
use_shared(value);
});
}
Common Bounds
fn spawn_task<F>(future: F)
where
F: Future<Output = ()> + Send + 'static,
{
tokio::spawn(future);
}
Async Traits
Using async-trait
use async_trait::async_trait;
#[async_trait]
trait AsyncTrait {
async fn method(&self) -> Result<(), Error>;
}
#[async_trait]
impl AsyncTrait for MyType {
async fn method(&self) -> Result<(), Error> {
Ok(())
}
}
Without async-trait (Rust 1.75+)
trait AsyncTrait {
type Output<'a>: Future<Output = Result<(), Error>>
where
Self: 'a;
fn method(&self) -> Self::Output<'_>;
}
impl AsyncTrait for MyType {
type Output<'a> = impl Future<Output = Result<(), Error>> + 'a;
fn method(&self) -> Self::Output<'_> {
async move {
Ok(())
}
}
}
Error Handling
Result in Async
async fn fallible_operation() -> Result<String, Error> {
let data = fetch_data().await?;
process(data).await?;
Ok("success".to_string())
}
Error Propagation
async fn chain_operations() -> Result<(), Error> {
let step1 = operation1().await?;
let step2 = operation2(step1).await?;
operation3(step2).await?;
Ok(())
}
Important Rules
- Use
.await to wait: Futures don't execute until awaited
- Understand Send/Sync: Know when types need these bounds
- Use executors: Futures need an executor to run
- Handle errors: Use
Result in async functions
- Avoid blocking: Don't block the async runtime
- Use pinning when needed: Some futures must be pinned
Common Patterns
✅ Good
async fn fetch_and_process() -> Result<(), Error> {
let data = fetch_data().await?;
process(data).await?;
Ok(())
}
async fn example() -> Result<String, Error> {
let result = fallible_operation().await?;
Ok(result)
}
❌ Avoid
async fn bad_example() {
std::thread::sleep(Duration::from_secs(1));
}
async fn good_example() {
tokio::time::sleep(Duration::from_secs(1)).await;
}
async fn bad_example() {
let future = fetch_data();
}
Examples from Project
Look for async usage in:
- Network operations
- File I/O operations
- Concurrent processing
- Task spawning and coordination
Tokio vs Async-std
Tokio
- More features (timers, networking, etc.)
- Better for complex applications
- More ecosystem support
Async-std
- Simpler API
- More similar to std library
- Good for simpler applications
Choose based on project needs and ecosystem requirements.