needs some way to refer to the pinned future &mut self
in order to wake it up.
No, it doesn't. The key thing to understand is that a “task” is not the future: it is the future and what the executor knows about it. What exactly the waker mutates is up to the executor, but it must be something that isn't the Future
. I say “must” not just because of Rust mutability rules, but because Future
s don't contain any state that says whether they have been woken. So, there isn't anything there to usefully mutate; 100% of the bytes of the future's memory are dedicated to the specific Future
implementation and none of the executor's business.
Well on the very next page if the book you will notice that task contains a boxed future and the waker is created from a reference to task. So there is a reference to future held from task albeit indirect.
OK, let's look at those data structures. Condensed:
struct Executor {
ready_queue: Receiver<Arc<Task>>,
}
struct Task {
future: Mutex<Option<BoxFuture<'static, ()>>>,
task_sender: SyncSender<Arc<Task>>,
}
The reference to the task is an Arc<Task>
, and the future itself is inside a Mutex
(interior mutability) in the task. Therefore,
- It is not possible to get an
&mut Task
from the Arc<Task>
, because Arc
doesn't allow that.
- The
future
is in a Mutex
which does run-time checking that there is at most one mutable reference to it.
The only things you can do with an Arc<Task>
are
- clone it and send it
- get
&
access to the future
in a Mutex
(which allows requesting run-time-checked mutation access to the Future
)
- get
&
access to the task_sender
(which allows sending things to ready_queue
).
So, in this case, when the waker is called, it sort-of doesn't even mutate anything specific to the Task
at all: it makes a clone of the Arc<Task>
(which increments an atomic reference count stored next to the Task
) and puts it on the ready_queue
(which mutates storage shared between the Sender
and Receiver
).
Another executor might indeed have task-specific state in the Task
that is mutated, such as a flag marking that the task is already woken and doesn't need to be woken again. That flag might be stored in an AtomicBoolean
field in the task. But still, it does not alias with any &mut
of the Future
because it's not part of the Future
, but the task.
All that said, there actually is something special about Future
s and noalias — but it's not about executors, it's about Pin
. Pinning explicitly allows the pinned type to contain “self-referential” pointers into itself, so Rust does not declare noalias for Pin<&mut T>
. However, exactly what the language rules around this are is still not quite rigorously specified; the current situation is just considered a kludge so that async
functions can be correctly compiled, I think.