Reputation: 553
I understood that pin
is used to pin data to one memory. When I use the poll()
method in Future
trait, it is called continuously until it returns Poll::Ready
. Is using a pin
to ensure that the data is placed in the same memory while poll()
is called? In other words, is it used to prevent the compiler from moving code that may have memory movement while poll
is called (generating a compile error)?
Upvotes: 2
Views: 1441
Reputation: 71300
No. The compiler never moves data behind your back. Pin
is not a language guarantee, it is a library guarantee.
There are two ways to construct a Pin
:
Unpin
.This ensures that unsafe code can rely on the pinning guarantees. The rule of thumb is: Unsafe code can never trust foreign safe code. It can only trust known safe code (such as std, or code inside its crate), or (even) foreign unsafe code. This is because if unsafe code rely on guarantees of foreign safe code, it can cause UB from safe code. An example (brought in the linked nomicon) is BTreeMap
and Ord
. BTreeMap
requires item to have total ordering, but its unsafe code cannot rely on that and have to behave well even in the presence of non-total ordering. This is because Ord
is safe to implement, and so one can implement it with safe code that does not obey to the total ordering rules, and together with BTreeMap
cause undefined behavior using safe code only. If the type was known, not foreign (such as i32
that we know that implements Ord
correctly), or BTreeMap
would require a unsafe trait UnsafeOrd
instead of Ord
we could rely on that, because violating the contract of UnsafeOrd
is undefined behavior as the trait as unsafe
to implement.
Suppose we are a self-referential future. We must be sure we stay in the same place in memory, because otherwise our self-references will be dangling. Because dangling references are UB, this has to include unsafe code. We can make poll()
unsafe fn
, but that is incovenient - it means polling a future is unsafe. Instead, we require Pin<&mut Self>
.
Now remember there are two ways to construct a Pin
. If we are Unpin
, that means we are not self-referential - that is, can be moved safely - and thus we can construct the Pin
safely. On the other hand, if we are self referential we should not be Unpin
. Now, the only way to construct the Pin
is with the unsafe method new_unchecked()
, which its safety preconditions require the pinned data will never be moved. Because this method is unsafe, unsafe code is required to use it, and so we can rely on its guarantees (remember we can trust foreign unsafe code).
It doesn't mean new_unchecked()
is the only way to construct a Pin<NonUnpin>
. A common pattern in Rust is to have an underlying unsafe mechanism that allows everything (as long as it is sound) but validates nothing, and then build various safe abstractions on top of it by restricting some abilities. A common example is interior mutability: we have UnsafeCell
that is unsafe and allows everything as long as you obey the aliasing rules, and we have multiple safe abstractions on top of it, each guaranteeing safety by some restriction:
Cell
for Copy
types and being non thread safe, and the atomic types that guarantees safety by being restricted to a specific set of types and atomic operations.RefCell
that guarantees safety by runtime check, being as flexible as UnsafeCell
but with a runtime cost.Mutex
and RwLock
that guarantee safety by blocking.OnceCell
and LazyCell
that guarantee safety by being writable only once (and possibly blocking, for the thread safe versions).The same pattern is used with Pin
: we have Pin::new_unchecked()
that is unsafe
, but multiple abstractions such as Box::pin()
(requires boxing) or the pin!()
macro (or stable versions in crates) that guarantees safety by only allow local pinning.
Upvotes: 8
Reputation: 3312
The Rust std library authors pinned the future mutable reference in Futures::poll because of the soundness guarantee they want to provide on their own libraries and to those libraries closely tied to their libraries. They want the definition of poll to help in their soundness guarantee. Memory will not be corrupted unless unsafe is used incorrectly somewhere along the line.
When a future is self referential because it wants to set a self reference to later be used by a subsequent poll, this works because the async runtimes built so far understand they may not move a future once it has been polled at least once.
But if the address of the future wasn't enforced at compile time to be pinned, a naive user might create a library's future and then repeatedly call the future's poll method themself. And if their code had moved the future in between calls to poll, they would have created the chance for the poll method to dereference an address that was no longer part of the future; they would be getting undefined behavior without every calling unsafe themself.
The fact the future address has to be pinned to call the poll method means the caller has to use unsafe. Pinning an address, by definition, involves using unsafe - that's why the idea of pinning exists.
So a user, naive or not, won't get their code to compile when they write something that calls poll unless they have used unsafe themselves. They may have used unsafe incorrectly so there could still be a compromised memory address within the poll logic, but the soundness guarantee would not have been violated - memory unsoundness was created through the misuse of unsafe, not by the misuse of a safe function or method.
Upvotes: 1