| name | rust-async-internals |
| description | Rust async internals skill for understanding and debugging async Rust. Use when understanding the Future trait and poll model, Pin and Unpin, tokio task scheduling, debugging async stack traces with tokio-console, tracking waker leaks, using select! and join!, or avoiding blocking in async contexts. Activates on queries about Rust async internals, Future poll, Pin, Unpin, tokio-console, waker, async stack traces, select!, join!, or blocking in async. |
Rust Async Internals
Purpose
Guide agents through Rust async/await internals: the Future trait and poll loop, Pin/Unpin for self-referential types, tokio's task model, diagnosing async stack traces with tokio-console, finding waker leaks, and common select!/join! pitfalls.
Triggers
- "How does async/await actually work in Rust?"
- "What is Pin and Unpin in async Rust?"
- "My async code is slow — how do I profile it?"
- "How do I use tokio-console to debug async tasks?"
- "I have a blocking call in async — what do I do?"
- "How does select! work and what are the pitfalls?"
Workflow
1. The Future trait — poll model
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
pub enum Poll<T> {
Ready(T),
Pending,
}
Execution model:
- Calling
.await calls poll() on the future
- If
Pending: current task registers its waker and yields to the runtime
- When the waker is triggered (I/O ready, timer fired), the runtime re-polls
- If
Ready(val): the .await expression evaluates to val
2. Implementing a simple Future
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
time::{Duration, Instant},
};
struct Delay { deadline: Instant }
impl Delay {
fn new(dur: Duration) -> Self {
Delay { deadline: Instant::now() + dur }
}
}
impl Future for Delay {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if Instant::now() >= self.deadline {
Poll::Ready(())
} else {
let waker = cx.waker().clone();
let deadline = self.deadline;
std::thread::spawn(move || {
let now = Instant::now();
if deadline > now {
std::thread::sleep(deadline - now);
}
waker.();
});
Poll::Pending
}
}
}
() {
Delay::(Duration::()).;
();
}
3. Pin and Unpin
Pin<P> prevents moving the value behind pointer P. This matters because async state machines contain self-referential pointers (a reference into the same struct where the future lives):
async fn example() {
let data = vec![1, 2, 3];
let ref_to_data = &data;
some_async_op().await;
println!("{:?}", ref_to_data);
}
let boxed: Pin<Box<dyn Future<Output = ()>>> = Box::pin(my_future);
use std::pin::pin;
let fut = pin!(my_future);
fut.await;
4. tokio task model
use tokio::task;
let handle = tokio::spawn(async {
42
});
let result = handle.await.unwrap();
let result = task::spawn_blocking(|| {
std::fs::read_to_string("big_file.txt")
}).await.unwrap();
tokio::task::yield_now().await;
let local = task::LocalSet::new();
local.run_until(async {
task::spawn_local(async { }).await.unwrap();
}).await;
5. tokio-console — async task inspector
[dependencies]
console-subscriber = "0.3"
tokio = { version = "1", features = ["full", "tracing"] }
fn main() {
console_subscriber::init();
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async_main());
}
cargo install --locked tokio-console
RUSTFLAGS="--cfg tokio_unstable" cargo run
tokio-console
6. Blocking in async — common mistake
async fn bad() {
std::thread::sleep(Duration::from_secs(1));
std::fs::read_to_string("file.txt").unwrap();
}
async fn good() {
tokio::time::sleep(Duration::from_secs(1)).await;
tokio::fs::read_to_string("file.txt").await.unwrap();
}
async fn with_blocking() {
let content = tokio::task::spawn_blocking(|| {
heavy_cpu_computation()
}).await.unwrap();
}
7. select! and join! pitfalls
use tokio::select;
select! {
result = fetch_a() => println!("A: {:?}", result),
result = fetch_b() => println!("B: {:?}", result),
}
let (a, b) = tokio::join!(fetch_a(), fetch_b());
loop {
select! {
biased;
_ = shutdown_signal.recv() => break,
msg = queue.recv() => process(msg),
}
}
let mut fut = some_future().fuse();
loop {
select! {
val = &mut fut => { break; }
_ = interval.tick() => { }
}
}
Related skills
- Use
skills/rust/rust-debugging for GDB/LLDB debugging of async Rust programs
- Use
skills/rust/rust-profiling for cargo-flamegraph with async stack frames
- Use
skills/low-level-programming/cpp-coroutines for C++20 coroutine comparison
- Use
skills/low-level-programming/memory-model for memory ordering in async contexts