Confusing Error Message Involving Pin and Borrow Splitting

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