I'm running into something confusing that I'm still working on finding the root cause for. I can't do a playground demo because so far I've only been able to cause it by pulling in async_channel
.
struct TaskQueue {
// Commenting out this line fixes the issue.
new_tasks: async_channel::Receiver<()>,
pending: Vec<String>,
pending_temp: Vec<String>,
}
impl Future for TaskQueue {
type Output = ();
fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Self::Output> {
let Self {
pending,
pending_temp,
..
} = &mut *self;
Poll::Pending
}
}
The error message:
error[E0596]: cannot borrow data in dereference of `Pin<&mut TaskQueue>` as mutable
--> leaf-protocol/src/lib.rs:256:13
|
256 | } = &mut *self;
| ^^^^^^^^^^ cannot borrow as mutable
|
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<&mut TaskQueue>`
I'm confused as to what it is about Receiver
type that is somehow disabling the DerefMut
on Pin
for the whole struct. Receiver
isn't Sync
, but confusingly that isn't it because adding a raw pointer that is !Sync
and !Send
works just fine.
Pin<Ptr<T>>
is DerefMut
only if T
is Unpin
. The reason is simple - mutable reference would allow to swap the value, and this would violate Pin
guarantees for non-Unpin
types.
2 Likes
And to solve the issue, you can just implement Unpin for TaskQueue
.
1 Like
Whoa! OK, so that works, but why isn't Unpin auto-implemented just in that weird case where it has the Receiver
type ( which implements Unpin already ) in it??
Is there something mysterious unimplementing Unpin in just that case?
I think the design is this way as opt-in because it's an API promise to implement Unpin
more generally, and prevents your type from offering e. g. structural pinning of fields, without sever-breaking changes.
Ah, no, Receiver
does actually not implement Unpin, though the generated API docs don't do a good job in making this obvious. (It had even me confused for a while), feel free to look at the source code of Receiver
where a comment even calls out the !Unpin
.
1 Like
Strange... Good catch.
This is what made me think Receiver
was Unpin
:
Now I'm more confused, though. If Receiver
is !Unpin
, then why am I allowed to implement Unpin
for my struct. Wouldn't unpinning my struct unpin the receiver inside?
Maybe the pin_project
macro is confusing things here.
Unpin
has a subtle design for sure. Implementing Unpin
is safe, but calling API such as Pin::get_unchecked_mut
is unsafe, and it can be unsound to use whilst publicly exposing your type (and values of it) and providing a too lenient Unpin
implementation.
See also https://doc.rust-lang.org/std/pin/index.html#unpin and https://doc.rust-lang.org/std/pin/index.html#projections-and-structural-pinning.
1 Like