`'static` requirement tracing help

heya, I'm having trouble finding where Rust is determining that one of my parameters needs to be borrowed for 'static.

This problem's really difficult to minimize (complex project), so here are the commands to reproduce the error:

git clone git@github.com:azriel91/peace.git --branch feature/166/type-parameter-revamp
cargo test --workspace --all-features

The Error

error[E0597]: `buffer` does not live long enough
   --> workspace_tests/src/rt/cmds/diff_cmd.rs:657:49
    |
656 |     let mut buffer = Vec::with_capacity(256);
    |         ---------- binding `buffer` declared here
657 |     let mut output = CliOutput::new_with_writer(&mut buffer);
    |                                                 ^^^^^^^^^^^ borrowed value does not live long enough
...
660 |     let mut cmd_ctx = CmdCtx::builder_single_profile_single_flow(&mut output, &workspace)
    |                       ------------------------------------------------------------------- argument requires that `buffer` is borrowed for `'static`
...
718 | }
    | - `buffer` dropped here while still borrowed

Background

  1. I build up a CmdCtx, which collects information used by *Cmd (e.g. DiffCmd).

  2. CmdCtx used to take in multiple type parameters.

  3. I changed that to take in one type parameter CmdCtxTypeParamsT, that implements a trait CmdCtxTypeParams, with associated types to the original type parameters.

  4. One of those type parameters is Output: OutputWrite<_>.

  5. In the code, CliOutput<W> implements OutputWrite, but Rust somehow wants W to be 'static, and I don't know where that comes from.

    The 'static requirement didn't use to be there when multiple type parameters were taken in, but I'm not sure if it's because my "TypeParamsCollector" is required to be 'static, or just some other mistake.

  6. In the code, I don't think I've got Output: .. + 'static anywhere.

  7. I've got a bound where CmdCtxTypeParams: 'static, but from my understanding, the 'static there is for the CmdCtxTypeParamsT, not for the associated type.

  8. I've tried tracing wherever Output / CmdCtxTypeParamsT::Output is used, and as far as I can tell, its lifetime is 'ctx.

It may be a Rust concept misunderstanding, or it may be a coding error, but I can't tell.

Could someone help trace / explain the issue?

Thank you :man_bowing:

Unfortunately, I can't run your project at the moment and the codebase is to big to give a quick answer (including some codegen at compile time that makes it pretty difficult to answer by simply looking at it).

However, I have a qualified guess on what's going on based on what you say and the error you get, so please forgive me for not having the opportunity to try it out, Maybe it can point you in the right direction.

You run the test using #[tokio::test], and while this creates a single threaded runtime, anything in your codebase that spawns a future on the runtime will still require that future to be Send.

Essentially, I think you get an error similar to this Rust Playground.

That also explains why you don't see any 'static bounds related to the buffer argument in your codebase. It most likely comes from something requiring Send + 'static in Tokio way down the call stack.

Now, if this indeed stems from Tokio, they do provide a solution to avoid that requirement for futures that you know will execute on the current thread. See LocalSet for more information.

1 Like

Heya, thanks for taking a look :man_bowing:

I thought it might be from tokio -- I have indeed run into the 'static requirement coming from there -- but I managed to reproduce this with much less code (though likely not minimal).

:playground_slide: playground

It's still a lot of code, so here's are the interesting parts:

/// Trait that tracks all associated types
/// i.e. the "collection" of type parameters grouped into one.
trait TypeParamsT {
    type AppError;
    type Input;
    type Output;
}

The same trait, but with constraints, and a blanket implementation. Line 56 (second line of the blanket impl's where) shows that when adding the 'static constraint on T and compiling, the 'static requirement is propagated onto T::Output.

trait TypeParamsConstrained:
    TypeParamsT<
        AppError = <Self as TypeParamsConstrained>::AppError,
        Input = <Self as TypeParamsConstrained>::Input,
        Output = <Self as TypeParamsConstrained>::Output,
    >
{
    type AppError: std::error::Error + 'static;
    type Input: Input;
    type Output: Output;
}

impl<T> TypeParamsConstrained for T
where
    T: TypeParamsT,
    T: 'static, // line 56. comment this out and the error goes away
    T::AppError: std::error::Error + 'static,
    T::Input: Input,
    T::Output: Output,
{
    type AppError = T::AppError;
    type Input = T::Input;
    type Output = T::Output;
}

I'm not completely sure that the error in the playground is the same issue, since I didn't expect the borrow to need to last beyond line 222, but anyway.

The 'static requirement exists in the bigger project's CmdCtxTypeParamsConstrained umbrella impl, so I'll chip away at removing it, and hopefully that'll solve the issue.

shows that when adding the 'static constraint on T and compiling, the 'static requirement is propagated onto T::Output.

It is true that if a trait has no parameters, and the implementing type is 'static, then there's no way for a (non-generic) associated type to be non-'static, and the compiler exploits this. If that's what's causing your problem, you may be on the right track.

As far as the playground goes, the bound is directly causing the error. On the other hand only having a 'static bound on T::Output is also an error, so this probably doesn't mean much.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.