home / skills / huiali / rust-skills / rust-pin
This skill helps Rust engineers reason about Pin, Unpin, and self-referential patterns to design memory-safe async state machines.
npx playbooks add skill huiali/rust-skills --skill rust-pinReview the files below or copy the command above to add this skill to your agents.
---
name: rust-pin
description: Pin and self-referential types expert covering Pin, Unpin, Future, async state machines, pinning projection, and memory stability guarantees.
metadata:
triggers:
- Pin
- Unpin
- self-referential
- Future
- async
- Generator
- pinning
- memory stability
---
## When Pin is Needed
### 1. async/await Futures
```rust
use std::pin::Pin;
use std::task::{Context, Poll};
use std::future::Future;
struct MyFuture {
state: State,
}
impl Future for MyFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// self is pinned, guaranteed not to move
let this = self.get_mut();
Poll::Ready(())
}
}
```
### 2. Self-Referential Structures
```rust
use std::pin::Pin;
struct Node {
value: i32,
// Self-reference: pointer to field within same struct
next: Option<Pin<Box<Node>>>,
}
```
## Solution Patterns
### Pattern 1: Pinning on Heap
```rust
use std::pin::Pin;
let future = async {
// async block creates a Future
};
// Pin future on heap
let pinned: Pin<Box<dyn Future<Output = ()>>> = Box::pin(future);
// Now safe to poll
```
### Pattern 2: Pinning with Pin::new_unchecked
```rust
use std::pin::Pin;
struct SelfReferential {
data: String,
ptr: *const String, // Points to data field
}
impl SelfReferential {
fn new(data: String) -> Pin<Box<Self>> {
let mut boxed = Box::new(SelfReferential {
data,
ptr: std::ptr::null(),
});
let ptr = &boxed.data as *const String;
boxed.ptr = ptr;
// SAFETY: boxed is on heap and won't move
unsafe { Pin::new_unchecked(boxed) }
}
fn data(&self) -> &str {
// SAFETY: ptr still valid because we're pinned
unsafe { &*self.ptr }
}
}
```
### Pattern 3: Pin Projection
```rust
use std::pin::Pin;
struct Wrapper<T> {
inner: T,
extra: String,
}
impl<T: Unpin> Wrapper<T> {
// Safe projection: T is Unpin
fn project(self: Pin<&mut Self>) -> Pin<&mut T> {
Pin::new(&mut self.get_mut().inner)
}
}
impl<T> Wrapper<T> {
// Unsafe projection: must maintain invariants
fn project_unchecked(self: Pin<&mut Self>) -> Pin<&mut T> {
// SAFETY: if Self is pinned, inner field is also pinned
unsafe {
Pin::new_unchecked(&mut self.get_unchecked_mut().inner)
}
}
}
```
### Pattern 4: Pinning in Async Context
```rust
use std::pin::Pin;
use futures::Future;
async fn process_data() {
let mut state = String::new();
// This reference is held across await
let state_ref = &mut state;
some_async_operation().await;
// state_ref must remain valid
state_ref.push_str("data");
}
// Compiler ensures state doesn't move by pinning the Future
```
## Pin Types
| Type | Use Case | Example |
|------|----------|---------|
| `Pin<&T>` | Borrowed, immutable | `Pin<&Foo>` |
| `Pin<&mut T>` | Borrowed, mutable | `Pin<&mut Foo>` |
| `Pin<Box<T>>` | Owned on heap | `Pin<Box<Foo>>` |
| `Pin<Arc<T>>` | Shared ownership | `Pin<Arc<Foo>>` |
## Unpin Marker Trait
```rust
// Most types implement Unpin (safe to move)
struct MyType {
data: Vec<u8>,
}
// Unpin auto-implemented
// Which types DON'T implement Unpin?
// - Futures (from async/await)
// - Generators
// - Manually marked with PhantomPinned
use std::marker::PhantomPinned;
struct NotUnpin {
data: String,
_pin: PhantomPinned, // Opts out of Unpin
}
```
## Workflow
### Step 1: Determine if Pin Needed
```
Need Pin when:
→ async/await (Future trait)
→ Self-referential struct
→ Implementing custom Future
→ Working with generators
Don't need Pin when:
→ Synchronous code
→ No self-references
→ Stack-allocated temporaries
→ Type is Unpin
```
### Step 2: Choose Pinning Strategy
```
Heap pinning:
→ Box::pin(value)
→ Safe, most common
Stack pinning:
→ pin!(value) // macro in std
→ More complex, zero allocation
Unsafe pinning:
→ Pin::new_unchecked()
→ Require SAFETY comments
```
### Step 3: Handle Projections
```
Projecting to field:
→ If T: Unpin → Safe with Pin::new
→ If !Unpin → Unsafe, need Pin::new_unchecked
→ Use pin-project crate for safety
```
## Common Use Cases
| Scenario | Need Pin? |
|----------|-----------|
| `async {}` block | ✅ Yes (Future) |
| `Box<dyn Future>` | ✅ Yes |
| Self-referential struct | ✅ Yes |
| Regular Vec/HashMap | ❌ No |
| Stack variables | ❌ No |
| No self-references | ❌ No |
## Review Checklist
When working with Pin:
- [ ] Pin actually necessary (async or self-ref)
- [ ] Correct pinning strategy chosen (heap vs stack)
- [ ] Unsafe projections have SAFETY comments
- [ ] Type correctly implements/opts-out of Unpin
- [ ] No accidental moves after pinning
- [ ] Projection maintains structural pinning
- [ ] Drop implementation respects pinning
- [ ] Documentation explains why pinned
## Verification Commands
```bash
# Check if type is Unpin
cargo expand
# Verify async state machine
cargo expand --lib my_async_fn
# Test with miri
cargo +nightly miri test
```
## Common Pitfalls
### 1. Forgetting to Pin Future
**Symptom**: Compilation error about poll signature
```rust
// ❌ Bad: Future not pinned
fn poll_future(mut future: impl Future) {
future.poll(); // Error: no poll method
}
// ✅ Good: Pin the Future
fn poll_future(mut future: Pin<&mut impl Future>) {
future.as_mut().poll(cx); // OK
}
```
### 2. Moving Pinned Value
**Symptom**: Undefined behavior
```rust
// ❌ Bad: moving after pinning
let pinned = Box::pin(value);
let moved = *pinned; // Error: cannot move out of pinned
// ✅ Good: work with pinned reference
let pinned = Box::pin(value);
let pinned_ref: Pin<&mut Value> = pinned.as_mut();
```
### 3. Incorrect Projection
**Symptom**: Unsoundness in self-referential types
```rust
// ❌ Bad: unsafe projection without guarantee
impl<T> Wrapper<T> {
fn bad_project(self: Pin<&mut Self>) -> &mut T {
&mut self.get_mut().inner // Unsound if T: !Unpin
}
}
// ✅ Good: safe projection with Unpin bound
impl<T: Unpin> Wrapper<T> {
fn safe_project(self: Pin<&mut Self>) -> Pin<&mut T> {
Pin::new(&mut self.get_mut().inner)
}
}
```
## Related Skills
- **rust-async** - Async/await and Future trait
- **rust-unsafe** - Unsafe code for Pin::new_unchecked
- **rust-ownership** - Lifetime and borrowing
- **rust-type-driven** - PhantomPinned and marker types
- **rust-performance** - Zero-cost abstractions with Pin
## Localized Reference
- **Chinese version**: [SKILL_ZH.md](./SKILL_ZH.md) - 完整中文版本,包含所有内容
This skill is an expert guide to Rust pinning and self-referential types, covering Pin, Unpin, Future, async state machines, pin projection, and memory stability guarantees. It provides diagnosis, patterns, and concrete workflows to choose safe pin strategies and avoid UB in async and self-referential code. Use it to reason about when pinning is necessary and how to implement correct projections and drops.
The skill inspects code patterns that require pinning (async/await futures, custom Future implementations, generators, and self-referential structs) and recommends a safe pinning strategy: heap pinning (Box::pin), stack pinning, or careful use of Pin::new_unchecked with SAFETY notes. It explains how to project pinned structs to fields, when Unpin bounds make projections safe, and when unsafe projections are required. It also provides a checklist and verification commands to validate correctness.
When is Pin actually required?
Pin is required for async/await futures, generators, custom Future implementations, and self-referential structs where a field holds a pointer into the same value. If the type is Unpin and has no self-references, pin is not needed.
Is Box::pin always safe?
Box::pin is safe for creating heap-stable ownership, but soundness still depends on how you project fields and whether you use unsafe Pin::new_unchecked elsewhere. Always ensure you do not move pinned data and document safety for unsafe operations.