-
Notifications
You must be signed in to change notification settings - Fork 13.7k
Description
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