Interaction of DerefMut with async

I have a tokio::sync::Mutex of an enum and I noticed I have to use deref_mut to pattern match the enum else the compiler requires the enum to be Sync. Using deref or &* does not compile:

use tokio::sync::{
    mpsc::{channel, Sender},
    Mutex,
};

enum Foo {
    A(Bar),
    B(Sender<i32>),
}

struct Bar(*const i32);

unsafe impl Send for Bar {}
// unsafe impl Sync for Bar {}

async fn process(foo: Mutex<Foo>) {
    let mut locked = foo.lock().await;

    match locked.deref_mut() {
        Foo::A(_) => (),
        Foo::B(tx) => {
            let _unused = tx.send(42).await;
        }
    }

    // Doesn't compile - requires `Bar` to be `Sync`
    //
    // match locked.deref() {
    //     Foo::A(_) => (),
    //     Foo::B(tx) => {
    //         let _unused = tx.send(42).await;
    //     }
    // }
}

Playground link.

Why is that the case?

Well, the equivalent of locked.deref_mut() would be &mut *locked, not &*locked (and you should use that one instead since it’s more idiomatic not to call the deref_mut() method manually).

As to why mutable access works here and immutable one doesn’t: Your type Bar doesn’t implement Sync. So &Bar cannot be sent between threads, but &mut Bar still can. The former requires Bar: Sync, the latter only Bar: Send in order to be safe to be sent between threads.

Now, why does this even matter, who could send &Bar between threads? It’s because at .await points, the process function may be sent to another thread by the async runtime of tokio, so all values of local variables or temporary variables that exist at that point must implement Send. This includes the &Foo or &mut Foo reference that you were matching on, and that is held in a temporary variable until the end of the match statement. In this case, arguably the compiler analysis on this matter is still overly cautions. After all, all that’s really being held onto is tx which only references the Sender<i32> which is Sync, but as the Rust compiler follows the principle of better being too cautious than allowing safety to be circumvented in corner cases, we’ll have to live with the more cautions analysis of the compiler here and we can only hope for future improvements.

1 Like

Thanks for the reminder. I was using &*locked before I decided to trim out the Sync, somehow arrived at deref_mut() in all the confusion.

Thanks, that really cleared things up for me. I wish the docs for Send would mention that, it's rather easy to forget the implications of &mut exclusivity.