1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-03-06 08:49:17 +02:00

Better explanation why futures need to be pinned (#1687)

Attempt to address #1677.

Expert review is needed. The new text is my best guess based on the
original text and other explanations I could find online.

A few things to note:

* I'm trying to distinguish the future we return and the future we
await. My assumption is that the stack contents goes to the future the
code returns, not the future the code is awaiting.
* Readers could be worried if they need to pin the code they write. I'm
reassuring them that the borrow checks would normally catch bad
references.
* I'm intentionally avoiding the words that something is unsafe (or
would be unsafe). The async Rust is safe.
* I'm trying to be clear that `Pin` is a protective wrapper around a
pointer, not a mechanism that changes the pointer or the pointed object.
* Likewise, I don't want to give an impression that an unpinned pointer
to a future is inherently unsafe or invalid. It just cannot be used to
poll the future.
* I dropped the vague mention of the "issues", as it probably refers to
the issue with replacing a future (as opposed to resetting it in place).
It's already mentioned in the notes further on this page. It affects
pinning on stack only, `Box::pin()` can be replaced.

Co-authored-by: Martin Geisler <martin@geisler.net>
This commit is contained in:
Pavel Roskin 2024-01-16 06:05:33 -08:00 committed by GitHub
parent f23aa10d32
commit 0a1c30ef87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,13 +1,17 @@
# `Pin`
When you await a future, all local variables (that would ordinarily be stored on
a stack frame) are instead stored in the Future for the current async block. If
your future has pointers to data on the stack, those pointers might get
invalidated. This is unsafe.
Async blocks and functions return types implementing the `Future` trait. The
type returned is the result of a compiler transformation which turns local
variables into data stored inside the future.
Therefore, you must guarantee that the addresses your future points to don't
change. That is why we need to "pin" futures. Using the same future repeatedly
in a `select!` often leads to issues with pinned values.
Some of those variables can hold pointers to other local variables. Because of
that, the future should never be moved to a different memory location, as it
would invalidate those pointers.
To prevent moving the future type in memory, it can only be polled through a
pinned pointer. `Pin` is a wrapper around a reference that disallows all
operations that would move the instance it points to into a different memory
location.
```rust,editable,compile_fail
use tokio::sync::{mpsc, oneshot};
@ -107,4 +111,18 @@ async fn main() {
- Another alternative is to not use `pin` at all but spawn another task that
will send to a `oneshot` channel every 100ms.
- Data that contains pointers to itself is called self-referential. Normally,
the Rust borrow checker would prevent self-referential data from being moved,
as the references cannot outlive the data they point to. However, the code
transformation for async blocks and functions is not verified by the borrow
checker.
- `Pin` is a wrapper around a reference. An object cannot be moved from its
place using a pinned pointer. However, it can still be moved through an
unpinned pointer.
- The `poll` method of the `Future` trait uses `Pin<&mut Self>` instead of
`&mut Self` to refer to the instance. That's why it can only be called on a
pinned pointer.
</details>