Incomplete lifetimes error message?

Hi,

I have this pretty simple piece of code and the borrowck error message I'm getting is confusing me:

Rust Playground link

use std::sync::Arc;
use tokio::sync::RwLock;

struct Context {
    field: Arc<RwLock<()>>,
}

fn blocking_fn(_guard: &mut tokio::sync::RwLockWriteGuard<()>) {}

async fn async_fn(ctx: Context) {
    let mut field_guard: tokio::sync::RwLockWriteGuard<_> = ctx.field.write().await;
    tokio::task::spawn_blocking(move || blocking_fn(&mut field_guard)).await;
}

fn main() {}
   Compiling playground v0.0.1 (/playground)
error[E0597]: `ctx.field` does not live long enough
  --> src/main.rs:12:65
   |
12 |         let mut field_guard: tokio::sync::RwLockWriteGuard<_> = ctx.field.write().await;
   |                                                                 ^^^^^^^^^^^^^^^^^------ argument requires that `ctx.field` is borrowed for `'static`
   |                                                                 |
   |                                                                 borrowed value does not live long enough
...
15 | }
   | - `ctx.field` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error

I understand why it won't compile, what's confusing to me is the argument requires that `ctx.field` is borrowed for `'static part specifically. What "argument" is meant here? Is it the argument to tokio::task::spawn_blocking() because it requires in its signature for the closure to be 'static?

If I replace the call to spawn_blocking() with

Rust Playground link.

fn fn_taking_closure<F>(_f: F)
where
    F: FnOnce() + 'static,
{
}

[...]

fn_taking_closure(move || blocking_fn(&mut field_guard));

I get a more informative error message:

   Compiling playground v0.0.1 (/playground)
error[E0597]: `ctx.field` does not live long enough
  --> src/main.rs:17:61
   |
17 |     let mut field_guard: tokio::sync::RwLockWriteGuard<_> = ctx.field.write().await;
   |                                                             ^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
18 |     fn_taking_closure(move || blocking_fn(&mut field_guard));
   |     -------------------------------------------------------- argument requires that `ctx.field` is borrowed for `'static`
19 | }
   | - `ctx.field` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error

The message is pointing directly at fn_taking_closure() (although I'd have preferred for it to underline just the argument of fn_taking_closure(), not the entire call).

Is the reason that rustc won't show the signature of tokio::task::spawn_blocking() that it's a seperate crate?

I copied tokio's spawn_blocking into a new crate in a workspace and still got basically what your second error looks like, so I don't think it's just a matter of being in a different crate.

lib crate

use std::future::Future;

#[track_caller]
pub fn spawn_blocking<F, R>(f: F) -> impl Future<Output = R>
where
    F: FnOnce() -> R + Send + 'static,
    R: Send + 'static,
{
    async { f() }
}

main.rs

use std::sync::Arc;
use tokio::sync::RwLock;

struct Context {
    field: Arc<RwLock<()>>,
}

fn blocking_fn(_guard: &mut tokio::sync::RwLockWriteGuard<()>) {}

async fn async_fn(ctx: Context) {
    let mut field_guard: tokio::sync::RwLockWriteGuard<_> = ctx.field.write().await;
    lib::spawn_blocking(move || blocking_fn(&mut field_guard)).await;
}

fn main() {}

Output

error[E0597]: `ctx.field` does not live long enough
  --> src/main.rs:11:61
   |
11 |     let mut field_guard: tokio::sync::RwLockWriteGuard<_> = ctx.field.write().await;
   |                                                             ^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
12 |     lib::spawn_blocking(move || blocking_fn(&mut field_guard)).await;
   |     ---------------------------------------------------------- argument requires that `ctx.field` is borrowed for `'static`
13 | }
   | - `ctx.field` dropped here while still borrowed

Maybe something about JoinHandle is throwing things off in a way that my trivial impl Future isn't? But that seems kind of unlikely.

1 Like

This code can't work. The compiler is just confused how to explain that. Typically when Rust mentions 'static it tries to tell you that references are forbidden (RwLockWriteGuard contains a reference inside).

The problem is that spawn_blocking can't refer to any temporary variables outside of itself. ctx belongs to inside of async_fn, which is a temporary context. The code in the closure may outlive the outer async_fn function, and therefore run after ctx has been destroyed.

You will have to move all of ctx into the spawn_blocking closure first, and lock it later inside the clousre. You can't split it between ctx that belongs to the outer function and a temporary field_guard inside the closure, because field_guard is tied to ctx no matter what.

2 Likes

Thank you for taking the time to take a look @kornel. I stated in the first post I'm aware why it doesn't work :wink:. What I'm having issue with, is just the error message. Specifically the mention of the word "argument". What "argument" exactly is meant in that error message?

Is it fair to say that this part of the error message is misleading because the word "argument" refers to the argument passed to spawn_blocking() and not to ctx.field?

12 |         let mut field_guard: tokio::sync::RwLockWriteGuard<_> = ctx.field.write().await;
   |                                                                 ^^^^^^^^^^^^^^^^^------ argument requires that `ctx.field` is borrowed for `'static`

Every time I get that error message, I'm always confused by it because I'm never sure if "argument" doesn't refer to the implicit self in ctx.field.write() call maybe?

I'd think a clearer error message would look something like this (still not ideal but definitely less confusing):

12 |         let mut field_guard: tokio::sync::RwLockWriteGuard<_> = ctx.field.write().await;
   |                                                                 ^^^^^^^^^^^^^^^^^------ `ctx.field` needs to be valid for `'static`
18 |     fn_taking_closure(move || blocking_fn(&mut field_guard));
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument is required to be valid for `'static`

Hope it doesn't come off as nitpicking. I'm genuinely trying to understand it completely and maybe attempt improving the error message in rustc.

Paging @ekuber

1 Like

This is speculation, I haven't checked it is the case.

The argument might be referring to the &self in fn write, or it could be thrown off by the desugaring. There are cases where the diagnostic is interpreting things in a way that is locally incorrect, because rustc doesn't always have the data the need for accurate diagnostics, so we have to rely on heuristics. Please file a ticket if you don't want this to be dropped/not addressed by accident.

1 Like

Created an issue: argument requires that [...] is borrowed for [...] · Issue #101120 · rust-lang/rust · GitHub

1 Like