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:
- Unsafely.
- If the data is
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 gurantees 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 Lazy 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.