Skip to content

Send is not general enough misleadingly reported, covering actual issue of captured reference lifetime #146246

@jberkenbilt

Description

@jberkenbilt

This code is incorrect because the call to thing captures the buf reference, which doesn't live long enough. The bug here is that the compiler fails to surface the error. Below is as minimal as I can get it to show the complete loss of information about the actual error. This code:

async fn do_nested<F: AsyncFn()>(f: F) {
    f().await;
}

async fn thing(_: &[u8]) {}

async fn do_thing_nested(buf: &[u8]) {
    do_nested(async || thing(buf).await).await;
}

async fn do_call() {
    let v = vec![0u8];
    do_thing_nested(&v).await;
}

struct X;
impl X {
    pub async fn do_call1(&self) {
        do_call().await;
    }
}

#[tokio::main]
async fn main() {
    let x = X;
    tokio::spawn(async move { x.do_call1().await });
}

produces this output when compiled:

error: implementation of `Send` is not general enough
  --> src/main.rs:26:5
   |
26 |     tokio::spawn(async move { x.do_call1().await });
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Send` is not general enough
   |
   = note: `Send` would have to be implemented for the type `&X`
   = note: ...but `Send` is actually implemented for the type `&'0 X`, for some specific lifetime `'0`

It would have been much better if it told me that buf didn't live long enough.

In this minimal example:

async fn thing(_: &[u8]) {}

#[tokio::main]
async fn main() {
    let v = Vec::new();
    tokio::spawn(thing(&v));
}

you get a helpful and correct error message:

error[E0597]: `v` does not live long enough
 --> src/main.rs:6:24
  |
5 |     let v = Vec::new();
  |         - binding `v` declared here
6 |     tokio::spawn(thing(&v));
  |     -------------------^^--
  |     |                  |
  |     |                  borrowed value does not live long enough
  |     argument requires that `v` is borrowed for `'static`
7 | }
  | - `v` dropped here while still borrowed

Even here:

async fn do_nested<F: AsyncFn()>(f: F) {
    f().await;
}

async fn thing(_: &[u8]) {}

#[tokio::main]
async fn main() {
    let v = Vec::new();
    tokio::spawn(do_nested(async || thing(&v).await));
}

you see the correct error:

error[E0597]: `v` does not live long enough
  --> src/main.rs:10:28
   |
10 |     tokio::spawn(do_nested(async || thing(&v).await));
   |     -----------------------^^^^^^^^^^^^^^^^^^^^^^^^--
   |     |                      |
   |     |                      borrowed value does not live long enough
   |     argument requires that `v` is borrowed for `'static`
11 | }
   | - `v` dropped here while still borrowed

When you build up to this point:

async fn do_nested<F: AsyncFn()>(f: F) {
    f().await;
}

async fn thing(_: &[u8]) {}

async fn do_thing_nested(buf: &[u8]) {
    do_nested(async || thing(buf).await).await;
}

async fn do_call() {
    let v = vec![0u8];
    do_thing_nested(&v).await;
}

#[tokio::main]
async fn main() {
    tokio::spawn(async move { do_call().await });
}

you start to see the error being lost, with this, where the HRTB Send error starts to appear but you still reference to &[u8]:

error: implementation of `Send` is not general enough
  --> src/main.rs:18:5
   |
18 |     tokio::spawn(async move { do_call().await });
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Send` is not general enough
   |
   = note: `Send` would have to be implemented for the type `&[u8]`
   = note: ...but `Send` is actually implemented for the type `&'0 [u8]`, for some specific lifetime `'0`

Wrapping it yet one additional level of indirection as in the original report shows the incorrect message referring to the incorrect type (X) which is irrelevant.

The code works as expected with a correct fix to the underlying issue, but it would be really hard to see that with the original error message.

iff --git a/bugs/send-not-general-enough/src/main.rs b/bugs/send-not-general-enough/src/main.rs
index 638173f..b06d8b0 100644
--- a/bugs/send-not-general-enough/src/main.rs
+++ b/bugs/send-not-general-enough/src/main.rs
@@ -1,11 +1,11 @@
-async fn do_nested<F: AsyncFn()>(f: F) {
-    f().await;
+async fn do_nested<F: AsyncFn(&[u8])>(buf: &[u8], f: F) {
+    f(buf).await;
 }
 
 async fn thing(_: &[u8]) {}
 
 async fn do_thing_nested(buf: &[u8]) {
-    do_nested(async || thing(buf).await).await;
+    do_nested(buf, async |buf| thing(buf).await).await;
 }
 
 async fn do_call() {

Along the path to figuring out what was wrong in my code, I came across -Zhigher-ranked-assumptions. Interestingly, with this:

RUSTFLAGS=-Zhigher-ranked-assumptions cargo build

there are no errors even though the code is in fact incorrect.

Meta

This is reproducible with nightly.

rustc --version --verbose:

rustc 1.91.0-nightly (a1208bf76 2025-09-03)
binary: rustc
commit-hash: a1208bf765ba783ee4ebdc4c29ab0a0c215806ef
commit-date: 2025-09-03
host: aarch64-apple-darwin
release: 1.91.0-nightly
LLVM version: 21.1.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions