Debug School

rakesh kumar
rakesh kumar

Posted on

why Rust futures must be pinned

.What is Box in Rust?
Why Heap Memory is Useful
What is Pin?
What is Box::pin?
Why Box::pin is Used in Async Code
Why Box::pin is Needed in Middleware
Objective and mcq

What is Box

What is Pin

Why Box::pin is needed
Enter fullscreen mode Exit fullscreen mode
  1. What is Box in Rust?

A Box stores a value on the heap memory instead of the stack.

Example

fn main() {

    let a = 10;              // stored on stack
    let b = Box::new(20);    // stored on heap

    println!("a = {}", a);
    println!("b = {}", b);

}
Enter fullscreen mode Exit fullscreen mode

Output

a = 10
b = 20
Enter fullscreen mode Exit fullscreen mode

Here:

a → stack memory
b → heap memory
Enter fullscreen mode Exit fullscreen mode

Rust moves large or dynamic data to the heap using Box.

  1. Why Heap Memory is Useful

Stack memory must know exact size at compile time.

But sometimes Rust needs dynamic memory.

Example:

async futures

recursive types

large data structures
Enter fullscreen mode Exit fullscreen mode

So we use:

Box<T>
Enter fullscreen mode Exit fullscreen mode
  1. What is Pin?

Pin means a value cannot move in memory after being placed somewhere.

Why?

Some async operations depend on memory not changing location.

Think of it like:

Pin = Fix the object in memory
Enter fullscreen mode Exit fullscreen mode

Example analogy:

Stack variable → movable
Pinned variable → fixed position
Enter fullscreen mode Exit fullscreen mode
  1. What is Box::pin?
Box::pin does two things together:
Enter fullscreen mode Exit fullscreen mode

Stores the value in heap memory

Pins the value so it cannot move

So:

Box::pin(value)

Enter fullscreen mode Exit fullscreen mode

means:

Put value on heap
and fix its memory location

  1. Basic Example of Box::pin Code use std::pin::Pin;
fn main() {

    let value = Box::pin(100);

    println!("Pinned value = {}", value);

}
Enter fullscreen mode Exit fullscreen mode
`Output`
Pinned value = 100
Enter fullscreen mode Exit fullscreen mode

Here:

100 → stored on heap
and pinned

  1. Why Box::pin is Used in Async Code

Async functions create Future objects.

Example async function:

async fn say_hello() {
    println!("Hello from async function");
}
Enter fullscreen mode Exit fullscreen mode

This function actually returns a Future.

But Rust cannot easily store unknown-sized futures.

So we wrap them using:

Box::pin(future)

  1. Simple Async Example Code
use std::future::Future;
use std::pin::Pin;

async fn greet() {
    println!("Hello Rust");
}

fn main() {

    let future: Pin<Box<dyn Future<Output = ()>>> = Box::pin(greet());

}
Enter fullscreen mode Exit fullscreen mode

Explanation

Box → store future on heap
Pin → future cannot move
Enter fullscreen mode Exit fullscreen mode
  1. Example Similar to Actix Middleware

Actix middleware returns this type:

LocalBoxFuture<'static, Result>

This is usually created like:

Box::pin(async move {

    println!("Middleware running");

    Ok(response)

})
Enter fullscreen mode Exit fullscreen mode

Example

use std::future::Future;
use std::pin::Pin;

fn main() {

    let future = Box::pin(async {

        println!("Async task running");

    });

}
Enter fullscreen mode Exit fullscreen mode

Output (when executed)
Async task running

  1. Why Box::pin is Needed in Middleware

In Actix middleware we often see:

fn call(&self, req: ServiceRequest) -> Self::Future {

    Box::pin(async move {

        println!("Middleware executing");

    })
}
Enter fullscreen mode Exit fullscreen mode

Why?

Because:

async block → creates a Future
Future size → unknown at compile time
Enter fullscreen mode Exit fullscreen mode

So Rust requires:

Box::pin(async block)
Enter fullscreen mode Exit fullscreen mode

to place it safely on heap.

  1. Simple Visualization

Without Box::pin

async block
   ↓
Unknown size future
   ↓
Rust cannot store it

With Box::pin

async block
   ↓
Future
   ↓
Box (heap storage)
   ↓
Pinned memory location
Enter fullscreen mode Exit fullscreen mode
  1. Real World Analogy

Think of memory like a parking lot.

Normal variable:

Car parked temporarily
It can move anytime
Enter fullscreen mode Exit fullscreen mode

Pinned variable:

Car fixed in a reserved parking slot
Cannot move
Enter fullscreen mode Exit fullscreen mode

Box::pin does exactly this.

  1. Small Practical Example
use std::pin::Pin;

fn main() {

    let data = Box::pin(String::from("Rust Programming"));

    println!("{}", data);

}
Enter fullscreen mode Exit fullscreen mode

Output
Rust Programming

Here:

String stored in heap
Pinned to fixed location

Enter fullscreen mode Exit fullscreen mode

When working with async programming in Rust, you often see concepts like:

Pin

Box::pin

Future
Enter fullscreen mode Exit fullscreen mode

Many developers ask:

Why do Rust futures need to be pinned?

The reason is connected to how async functions work internally.

  1. What an Async Function Actually Returns

When you write this:

async fn hello() {
    println!("Hello Rust");
}
Enter fullscreen mode Exit fullscreen mode

Rust does not immediately run the function.

Instead, Rust converts it into a Future.

Conceptually it becomes something like:

fn hello() -> impl Future<Output = ()>
Enter fullscreen mode Exit fullscreen mode

So an async function returns a Future object.

  1. Futures Work Like State Machines

Rust transforms async code into a state machine.

Example async code:

async fn example() {
    step1().await;
    step2().await;
}
Enter fullscreen mode Exit fullscreen mode

Internally Rust converts this into something like:

State 0 → Start
State 1 → After step1
State 2 → After step2
Enter fullscreen mode Exit fullscreen mode

Each await point stores data inside the future struct.

So the future keeps internal references to its own memory.

  1. The Problem: Moving Futures in Memory

Normally in Rust, values can move in memory.

Example:

fn main() {

    let a = String::from("Rust");

    let b = a; // moved

}
Enter fullscreen mode Exit fullscreen mode

When values move:

Old memory → invalid
New memory → new location
Enter fullscreen mode Exit fullscreen mode

This is normally safe.

But for futures it creates a problem.

  1. The Self-Reference Problem

Async futures may contain references to their own internal fields.

Example conceptually:

Future Struct


field1: String
field2: reference to field1
Enter fullscreen mode Exit fullscreen mode

Visual example:

Memory location: 0x1000

field1 → "hello"
field2 → reference to 0x1000
Enter fullscreen mode Exit fullscreen mode

Now imagine the future moves to another location:

New memory: 0x2000
Enter fullscreen mode Exit fullscreen mode

But the reference still points to:

0x1000
Enter fullscreen mode Exit fullscreen mode

Which is now invalid.

This causes memory safety issues.

Rust prevents this by pinning the future.

  1. What Pinning Does

Pinning means:

Once placed in memory → cannot move

So if a future is pinned:

Future memory address stays fixed

This guarantees that internal references remain valid.

  1. Example Without Pin (Conceptual Problem)

Consider this conceptual future:

struct MyFuture {
    data: String,
    reference: *const String,
}
Enter fullscreen mode Exit fullscreen mode

If the struct moves:


data moves to new location
reference still points to old location

Enter fullscreen mode Exit fullscreen mode

This breaks memory safety.

Rust avoids this by requiring:

Pinned Futures

  1. Simple Example of Pin
use std::pin::Pin;

fn main() {

    let value = Box::pin(10);

    println!("Pinned value: {}", value);

}
Enter fullscreen mode Exit fullscreen mode

Output

Pinned value: 10
Enter fullscreen mode Exit fullscreen mode

Here:

10 is stored on heap
and pinned so it cannot move

  1. Async Example with Pin
use std::future::Future;
use std::pin::Pin;

async fn say_hello() {
    println!("Hello Rust Async");
}

fn main() {

    let future: Pin<Box<dyn Future<Output = ()>>> =
        Box::pin(say_hello());

}
Enter fullscreen mode Exit fullscreen mode

Here:

Future stored on heap
Future pinned
Memory location fixed
Enter fullscreen mode Exit fullscreen mode
  1. Why Middleware Uses Box::pin

A Box stores a value on the heap memory instead of the stack.

Example

fn main() {

    let a = 10;              // stored on stack
    let b = Box::new(20);    // stored on heap

    println!("a = {}", a);
    println!("b = {}", b);

}
Enter fullscreen mode Exit fullscreen mode

Output

a = 10
b = 20
Enter fullscreen mode Exit fullscreen mode

Here:

a → stack memory
b → heap memory
Enter fullscreen mode Exit fullscreen mode

Rust moves large or dynamic data to the heap using Box.

In Actix middleware you often see:

Box::pin(async move {

    println!("Middleware running");

})
Enter fullscreen mode Exit fullscreen mode

Why?

Because:

async block → creates a Future
Future size → unknown
Future may contain self references
Enter fullscreen mode Exit fullscreen mode

So Rust requires:

Box::pin(async block)
Enter fullscreen mode Exit fullscreen mode

to safely store and pin it.

Why Heap Memory is Useful

Stack memory must know exact size at compile time.

But sometimes Rust needs dynamic memory.
Enter fullscreen mode Exit fullscreen mode

Example:

async futures

recursive types

large data structures

So we use:
Enter fullscreen mode Exit fullscreen mode
Box<T>
Enter fullscreen mode Exit fullscreen mode
  1. Visual Summary

Without pinning:

Future
  │
  ▼
Memory moves
  │
  ▼
Internal references break

`With pinning:`

Future
  │
  ▼
Pinned memory location
  │
  ▼
Enter fullscreen mode Exit fullscreen mode

References remain valid

  1. Real-World Analogy

Imagine a map with a location marker.

Normal variable:

House moves → map still points to old place
Enter fullscreen mode Exit fullscreen mode

Pinned variable:

House fixed → map always correct
Enter fullscreen mode Exit fullscreen mode

Pinning ensures that the address never changes.

  1. When Futures Must Be Pinned

Futures must be pinned when:

they contain self-references

they are polled multiple times

they are stored in heap structures

they are used in async frameworks
Enter fullscreen mode Exit fullscreen mode

Examples:

Actix Web
Tokio
Hyper
Async executors
Enter fullscreen mode Exit fullscreen mode
  1. Key Takeaway

Rust futures must be pinned because:

Async futures may contain references to their own internal memory.
If the future moves, those references break.
Pinning prevents movement and keeps memory safe.
Enter fullscreen mode Exit fullscreen mode

What is the main purpose of pinning a future in Rust?

A. Increase execution speed
B. Prevent the future from moving in memory
C. Allow multiple threads to run
D. Reduce compilation time

✅ Answer: B
Pinning guarantees the value will not move in memory.

  1. Why do many Rust futures require pinning?

A. Futures require heap allocation
B. Futures often contain self-references
C. Futures cannot use references
D. Futures always run in parallel

✅ Answer: B
Many futures are self-referential, so they must stay in a fixed memory location.

  1. What happens if a self-referential future moves in memory?

A. Execution becomes faster
B. References become invalid
C. Memory is automatically fixed
D. Nothing happens

✅ Answer: B
Moving such a structure breaks internal references.

  1. Which Rust type ensures a value cannot move after being pinned?

A. Future
B. Pin
C. Box
D. Ref

✅ Answer: B
Pin

guarantees the pointed value will not move.

  1. W*hich trait indicates that a type can safely move even if pinned?*

A. Copy
B. Send
C. Unpin
D. Clone

✅ Answer: C
Unpin means the value can move safely even when wrapped in Pin.

  1. What does the Future::poll() method require as its first parameter?

A. &Self
B. Pin<&mut Self>
C. Box
D. Self

✅ Answer: B
The future must be pinned before polling.

  1. Why does poll() require Pin<&mut Self>?

A. To allow multiple threads
B. To guarantee the future will not move
C. To increase performance
D. To reduce memory

✅ Answer: B
Pinning ensures safe polling of self-referential futures.

  1. What does async/await compile into internally?

A. Threads
B. State machines
C. Processes
D. Channels

✅ Answer: B
Async functions are compiled into state machines.

  1. Why can async state machines become self-referential?

A. They store references across .await points
B. They allocate memory on heap
C. They use threads
D. They call external libraries

✅ Answer: A
Variables stored across await points may reference each other.

  1. What does pinning guarantee about a future’s memory address?

A. It changes frequently
B. It remains constant
C. It moves automatically
D. It becomes static

✅ Answer: B
Pinning keeps the memory location fixed.

  1. Which function is commonly used to pin a future on the heap?

A. Box::new()
B. Box::pin()
C. Pin::wrap()
D. Future::pin()

✅ Answer: B

  1. What kind of data structure often requires pinning?

A. Numeric values
B. Self-referential structures
C. Static variables
D. Boolean flags

✅ Answer: B
Self-referential data must remain at a fixed location.

  1. Which Rust feature generates futures automatically?

A. match
B. async fn
C. loop
D. impl

✅ Answer: B

  1. Which keyword waits for a future to complete?

A. wait
B. future
C. await
D. async

✅ Answer: C

15.** What problem does pinning primarily prevent?**

A. CPU overload
B. Dangling references
C. Thread starvation
D. Compilation errors

✅ Answer: B
Moving self-referential futures could create dangling pointers.

  1. Which pointer types can be wrapped by Pin?

A. Only Box
B. Only &T
C. Pointer types like &mut T or Box
D. Only Vec

✅ Answer: C
Pin wraps pointer types to prevent movement.

  1. When should a future typically be pinned?

A. Before it is polled
B. After execution completes
C. After compilation
D. Before writing code

✅ Answer: A
Futures must be pinned before polling begins.

  1. Which scenario allows a pinned value to still move safely?

A. If the type implements Unpin
B. If the future is async
C. If the value is boxed
D. If the runtime moves it

✅ Answer: A
Unpin indicates movement is safe even when pinned.

  1. What is the relationship between Pin and async runtimes?

A. Runtimes require pinned futures for safe polling
B. Runtimes ignore pinning
C. Pinning disables async runtime
D. Pinning removes futures

✅ Answer: A
Executors rely on pinned futures for safe execution.

  1. What is the key concept behind pinning in Rust async programming?

A. Memory stability for asynchronous tasks
B. Faster execution
C. Smaller binaries
D. Thread synchronization

✅ Answer: A
Pinning ensures memory stability and safe async execution.

Top comments (0)