# Pin and async traits

Learn what pinning protects and how to return futures through a trait object.

Canonical: https://rust.robertdevore.com/course/21-pin/
Author: Robert DeVore
Technical baseline: Rust 1.98.1, edition 2024; verified 2026-09-06.

## What Pin keeps in place

Some values become address-sensitive, including futures that may contain references into their own suspended state. `Pin<P>` constrains how the pointee accessed through pointer `P` may be moved. It does not mean the pointer handle itself cannot move, and it does not automatically allocate anything.

For `T: Unpin`, moving the value does not violate a pinning invariant, so many pin restrictions become irrelevant. For a `!Unpin` value, safe APIs prevent operations that would violate the pin contract. The contract also involves destruction and storage validity; it is stronger than “do not call mem::swap right now”.

## Use safe constructors first

`pin!` pins a value in local storage. `Box::pin` owns a pinned allocation whose handle can move. Most async application code can use these or runtime helpers without implementing unsafe projection. Accessing a pinned struct's fields requires knowing which fields are structurally pinned. Do not use `get_unchecked_mut` just to bypass a compiler error.


### Run the example

```sh
cargo run --locked --example 21_pin
```

```rust
use std::{future::Future, pin::Pin};
trait Lookup {
    fn get(&self) -> Pin<Box<dyn Future<Output = usize> + '_>>;
}
struct Fixed(usize);
impl Lookup for Fixed {
    fn get(&self) -> Pin<Box<dyn Future<Output = usize> + '_>> {
        Box::pin(async move { self.0 })
    }
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
    let lookup = Fixed(42);
    let service: &dyn Lookup = &lookup;
    assert_eq!(service.get().await, 42);
}
```

[View the tested source](https://github.com/robertdevore/rust.robertdevore.com/blob/main/examples/21_pin.rs)


The interface returns a future in a pinned `Box`. The trait object hides its concrete type. Its lifetime is tied to `&self`, so it can borrow the service. The example uses a local executor and does not require `Send`. An API intended for movable spawned tasks may need `Send` on the returned future and appropriate bounds on the captured data.

## Calling async methods through trait objects

Native `async fn` in traits works on stable Rust for supported static-dispatch cases. That does not imply those methods can be dispatched directly through `dyn Trait`. Returning an explicitly erased future is a stable alternative, with allocation and indirection costs. The `async-trait` crate automates a related transformation; use it when you need this kind of interface.


**Intentionally fails on Rust 1.98.1.** Run `rustc --edition=2024 drills/async_dyn.rs` from the repository.

```rust
trait Fetch { async fn get(&self) -> usize; }
fn consume(_: &dyn Fetch) {}
fn main() {}
```

<details><summary>Actual compiler diagnostic · Rust 1.98.1</summary>

```text
error[E0038]: the trait `Fetch` is not dyn compatible
 --> drills/async_dyn.rs:2:16
  |
2 | fn consume(_: &dyn Fetch) {}
  |                ^^^^^^^^^ `Fetch` is not dyn compatible
  |
note: for a trait to be dyn compatible it needs to allow building a vtable
      for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
 --> drills/async_dyn.rs:1:24
  |
1 | trait Fetch { async fn get(&self) -> usize; }
  |       -----            ^^^ ...because method `get` is `async`
  |       |
  |       this trait is not dyn compatible...
  = help: consider moving `get` to another trait

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0038`.
```

</details>


The drill captures the current direct-dyn rejection. This shows what the pinned compiler supports today, not what Rust will support forever. Current async and field-projection initiatives aim to improve these awkward edges.

## Exercise

Explain why moving the `Box` handle is compatible with pinning its allocated future. Then remove the trait object entirely and call a concrete async method. Compare what the two designs require from allocation and the public signature.

<details><summary>Solution and acceptance check</summary>

The pointee stays in its allocation when the owning pointer moves. A concrete async method can return its compiler-generated future without the explicit box and vtable used here. Prefer the concrete method unless callers need to work with different implementations through one interface. Do not claim a speedup without measuring the relevant workload.

</details>

Sources: [`std::pin`](https://doc.rust-lang.org/std/pin/), [trait rules](https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility), and [the async trait RFC](https://rust-lang.github.io/rfcs/3185-static-async-fn-in-trait.html).

