/rust-async-internals
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
$ npx -y skills add mohitmishra786/low-level-dev-skills --skill rust-async-internals --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/rust-async-internals
Context preview
The summary Claude sees to decide when to auto-load this skill.
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
SKILL.md
rust-async-internals.SKILL.mdname: 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
// std::future::Future (simplified)
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
pub enum Poll<T> {
Ready(T), // computation done, T is the result
Pending, // not ready yet, waker registered, will be polled again
}Execution model: 1. Calling `.await` calls `poll()` on the future 2. If `Pending`: current task registers its waker and yields to the runtime 3. When the waker is triggered (I/O ready, timer fired), the runtime re-polls 4. 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 {
// Register the waker — runtime calls waker.wake() to re-poll
// In production: register with I/O reactor or timer wheel
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.wake(); // notify runtime to re-poll
});
Poll::Pending
}
}
}
// Usage
async fn main() {
Delay::new(Duration::from_secs(1)).await;
println!("Done");
}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):
// Why Pin is needed: async fn compiles to a state machine struct
// that may have self-references across await points
async fn example() {
let data = vec![1, 2, 3];
let ref_to_data = &data; // reference into same stack frame
some_async_op().await; // suspension point
println!("{:?}", ref_to_data); // reference still used after suspend
}
// The state machine stores both `data` and `ref_to_data`.
// If the struct were moved, `ref_to_data` would dangle.
// Pin<&mut State> prevents moving the state machine.
// Unpin: a marker trait for types that are safe to move even when pinned
// Most types implement Unpin automatically
// Futures generated by async/await do NOT implement Unpin
// Creating a Pin from Box (heap allocation → safe)
let boxed: Pin<Box<dyn Future<Output = ()>>> = Box::pin(my_future);
// Pinning to stack (unsafe, use pin! macro)
use std::pin::pin;
let fut = pin!(my_future);
fut.await; // or poll it directly4. tokio task model
use tokio::task;
// Spawn a task (runs concurrently on the runtime thread pool)
let handle = tokio::spawn(async {
// ... async work ...
42
});
let result = handle.await.unwrap(); // wait for completion
// spawn_blocking — for CPU-bound or blocking I/O
let result = task::spawn_blocking(|| {
// runs on a dedicated blocking thread pool
std::fs::read_to_string("big_file.txt")
}).await.unwrap();
// yield to runtime (cooperative multitasking)
tokio::task::yield_now().await;
// LocalSet — for !Send futures (single-threaded)
let local = task::LocalSet::new();
local.run_until(async {
task::spawn_local(async { /* !Send future */ }).await.unwrap();
}).await;5. tokio-console — async task inspector
# Cargo.toml
[dependencies]
console-subscriber = "0.3"
tokio = { version = "1", features = ["full", "tracing"] }// main.rs
fn main() {
console_subscriber::init(); // must be called before tokio runtime
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async_main());
}# Install tokio-console CLI
cargo install --locked tokio-console
# Run your app with tracing enabled
RUSTFLAGS="--cfg tokio_unstable" cargo run
# In another terminal, connect tokio-console
tokio-console
# tokio-console shows:
# - Running tasks with their names, poll times, and wakeup counts
# - Slow tasks (high poll duration = blocking in async!)
# - Tasks that have been pending for a long time (stuck?)
# - Resource contention (mutex/semaphore wait times)
6. Blocking in async — common mistake
// WRONG: blocking call in async context blocks entire thread
async fn bad() {
std::thread::sleep(Duration::from_secs(1)); // blocks runtime thread!
std::fs::read_to_string("file.txt").unwrap(); // blockingRead more
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
// std::future::Future (simplified)
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
pub enum Poll<T> {
Ready(T), // computation done, T is the result
Pending, // not ready yet, waker registered, will be polled again
}Execution model: 1. Calling `.await` calls `poll()` on the future 2. If `Pending`: current task registers its waker and yields to the runtime 3. When the waker is triggered (I/O ready, timer fired), the runtime re-polls 4. 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 {
// Register the waker — runtime calls waker.wake() to re-poll
// In production: register with I/O reactor or timer wheel
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.wake(); // notify runtime to re-poll
});
Poll::Pending
}
}
}
// Usage
async fn main() {
Delay::new(Duration::from_secs(1)).await;
println!("Done");
}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):
// Why Pin is needed: async fn compiles to a state machine struct
// that may have self-references across await points
async fn example() {
let data = vec![1, 2, 3];
let ref_to_data = &data; // reference into same stack frame
some_async_op().await; // suspension point
println!("{:?}", ref_to_data); // reference still used after suspend
}
// The state machine stores both `data` and `ref_to_data`.
// If the struct were moved, `ref_to_data` would dangle.
// Pin<&mut State> prevents moving the state machine.
// Unpin: a marker trait for types that are safe to move even when pinned
// Most types implement Unpin automatically
// Futures generated by async/await do NOT implement Unpin
// Creating a Pin from Box (heap allocation → safe)
let boxed: Pin<Box<dyn Future<Output = ()>>> = Box::pin(my_future);
// Pinning to stack (unsafe, use pin! macro)
use std::pin::pin;
let fut = pin!(my_future);
fut.await; // or poll it directly4. tokio task model
use tokio::task;
// Spawn a task (runs concurrently on the runtime thread pool)
let handle = tokio::spawn(async {
// ... async work ...
42
});
let result = handle.await.unwrap(); // wait for completion
// spawn_blocking — for CPU-bound or blocking I/O
let result = task::spawn_blocking(|| {
// runs on a dedicated blocking thread pool
std::fs::read_to_string("big_file.txt")
}).await.unwrap();
// yield to runtime (cooperative multitasking)
tokio::task::yield_now().await;
// LocalSet — for !Send futures (single-threaded)
let local = task::LocalSet::new();
local.run_until(async {
task::spawn_local(async { /* !Send future */ }).await.unwrap();
}).await;5. tokio-console — async task inspector
# Cargo.toml
[dependencies]
console-subscriber = "0.3"
tokio = { version = "1", features = ["full", "tracing"] }// main.rs
fn main() {
console_subscriber::init(); // must be called before tokio runtime
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async_main());
}# Install tokio-console CLI cargo install --locked tokio-console # Run your app with tracing enabled RUSTFLAGS="--cfg tokio_unstable" cargo run # In another terminal, connect tokio-console tokio-console # tokio-console shows: # - Running tasks with their names, poll times, and wakeup counts # - Slow tasks (high poll duration = blocking in async!) # - Tasks that have been pending for a long time (stuck?) # - Resource contention (mutex/semaphore wait times)
6. Blocking in async — common mistake
// WRONG: blocking call in async context blocks entire thread
async fn bad() {
std::thread::sleep(Duration::from_secs(1)); // blocks runtime thread!
std::fs::read_to_string("file.txt").unwrap(); // blockingA curated suite of AI agent skills for systems and low-level programming — C/C++, Rust, Zig, GPU, bare-metal firmware, Linux kernel/driver development, computer architecture, compiler internals, HPC, and more.
Repo: mohitmishra786/low-level-dev-skills
Other skills on low-level-dev-skills.
- /custom-allocators
Custom allocator skill for memory allocation strategies. Use when implementing pool/slab/arena allocators, tuning jemalloc/mimalloc, writing Rust GlobalAlloc, or benchmarking allocator performance. Activates on queries about jemalloc, mimalloc, tcmalloc, arena allocator,
Open skill - /numa-programming
NUMA programming skill for multi-socket memory locality. Use when detecting NUMA topology, binding processes with numactl, using libnuma API, building NUMA-aware data structures, or measuring remote access penalties. Activates on queries about numactl, libnuma, NUMA topology,
Open skill - /af-xdp
AF_XDP skill for high-performance XDP sockets. Use when creating AF_XDP sockets, configuring UMEM and XSK rings, XDP_REDIRECT programs, copy vs zero-copy mode, or comparing with DPDK. Activates on queries about AF_XDP, xsk_umem, XDP_REDIRECT, libbpf xsk, or zero-copy XDP.
Open skill - /dpdk
DPDK skill for userspace packet I/O. Use when initializing EAL, configuring PMD drivers, using mbuf pools and rte_ring, setting up huge pages, RSS, or testpmd validation. Activates on queries about DPDK, EAL, rte_eth_rx_burst, hugepages, PMD, or testpmd.
Open skill - /io-uring
io_uring skill for Linux async I/O. Use when building high-performance servers with liburing, multi-shot operations, provided buffers, fixed files, zero-copy send, or tokio-uring. Activates on queries about io_uring, SQE/CQE, liburing, IORING_OP_PROVIDE_BUFFERS, or io_uring vs
Open skill - /adc-dac-baremetal
Bare-metal ADC and DAC skill. Use when configuring analog sampling, DMA-driven ADC, calibration, or DAC output on MCUs. Activates on queries about ADC bare-metal, sampling time, DMA ADC, or DAC channel setup.
Open skill

