Skip to content

Async Send inference fails with PhantomData<*mut ()> + unsafe impl Send when T is a trait object #154397

@sandersaares

Description

@sandersaares

I tried this code:

use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};

struct Receiver<T: Send + 'static> {
    _value: Option<T>,
    _not_sync: PhantomData<*mut ()>,
}

unsafe impl<T: Send + 'static> Send for Receiver<T> {}

impl<T: Send + 'static> Future for Receiver<T> {
    type Output = T;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
        Poll::Pending
    }
}

trait MyTrait: Send {}

fn require_send<F: Future + Send>(_f: F) {}

fn main() {
    // Static assertion works:
    fn assert_send<T: Send>() {}
    assert_send::<Receiver<Box<dyn MyTrait>>>();

    // Async block with concrete type works:
    require_send(async {
        let r = Receiver::<u32> { _value: None, _not_sync: PhantomData };
        let _ = r.await;
    });

    // Async block with dyn Trait FAILS:
    require_send(async {
        let r = Receiver::<Box<dyn MyTrait>> { _value: None, _not_sync: PhantomData };
        let _ = r.await;
    });
}

I expected this to compile, because Receiver<Box<dyn MyTrait>>: Send is established by the unsafe impl (and the static assertion confirms it).

Instead, I got:

error: higher-ranked lifetime error
   = note: could not prove `{async block@...}: Send`

Pattern

This is the common pattern of using PhantomData<*mut ()> to opt out of Sync while restoring Send via unsafe impl. It works for static assertions and for concrete type parameters, but fails when T is a trait object (Box<dyn Trait>) and the type is captured across an .await.

A workaround is to use PhantomData<Cell<()>> instead of PhantomData<*mut ()>, since Cell<()> is natively Send + !Sync and avoids the need for unsafe impl Send.

Versions

  • Stable: rustc 1.93.1 (01f6ddf 2026-02-11)
  • Nightly: rustc 1.96.0-nightly (fd0c901 2026-03-18)

Relationship to #110338

This is an instance of #110338. The code compiles successfully with -Zhigher-ranked-assumptions on nightly.

@rustbot label +A-async-await +A-auto-traits +A-coroutines +fixed-by-higher-ranked-assumptions

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions