Basically it's because the future objects that async/await syntax generate are going to contain pointers that point into other parts of the same future object. If the future object is moved, those pointers would still point at the future's old address, and next time you polled the future, it would access the wrong memory locations.
For example, consider this:
async fn my_fn() {
let my_var = 10;
let my_ref = &my_var;
tokio::task::yield_now().await;
println!("{my_ref}");
}
Here, when you call poll
on that future object, the first call to poll
will create the my_var
and my_ref
values, but then it has to return due to the yield in yield_now
. This is handled by storing the local variables inside the future object, so they are ready next time the future is polled. However, storing both my_var
and my_ref
in the future object results in the situation I described.
Pin is the answer because it prevents moving the future object once the future has been polled, ensuring that internal references remain valid.