Argument requires that `'x` must outlive `'a`

Hi !

I'm entering in the lifetime world. Until today I had avoided them. But, I have to face them in generic rewrite of my code.

There is a reproduction of my problem:

use std::sync::{Arc, RwLock};

struct SoldierId(usize);
struct Soldier;
struct World(Vec<Soldier>);
struct State(Arc<RwLock<World>>);
struct Context {
    state: Arc<State>,
}

trait IntoSubject<'a, T> {
    fn into_subject(&self, world: &'a World) -> &'a T;
}

impl<'a> IntoSubject<'a, Soldier> for SoldierId {
    fn into_subject(&self, world: &'a World) -> &'a Soldier {
        &world.0[self.0]
    }
}

struct Processor<'a> {
    ctx: &'a Context,
}

impl<'a> Processor<'a> {
    fn process<I, T: 'a>(&self, i: I)
    where
        I: IntoSubject<'a, T>,
    {
        let world = self.ctx.state.0.read().unwrap();
        let _subject = i.into_subject(&world);
    }
}

fn main() {
    let objects = vec![];
    let state = Arc::new(State(Arc::new(RwLock::new(World(objects)))));
    let ctx = Context { state };
    let processor = Processor { ctx: &ctx };
    processor.process(SoldierId(0));
}
error[E0597]: `world` does not live long enough
  --> src/main.rs:31:39
   |
25 | impl<'a> Processor<'a> {
   |      -- lifetime `'a` defined here
...
30 |         let world = self.ctx.state.0.read().unwrap();
   |             ----- binding `world` declared here
31 |         let _subject = i.into_subject(&world);
   |                        ---------------^^^^^^-
   |                        |              |
   |                        |              borrowed value does not live long enough
   |                        argument requires that `world` is borrowed for `'a`
32 |     }
   |     - `world` dropped here while still borrowed

In this context, my subject (T) is owned by World. So, in Process::process which contain World (thought State then Context), the life of T must be the same as World. So, I specfy 'a. Error say &world is dropped (l. 32) but still borrowed. How to indicate than T no need to be dropped after world ? Maybe I'm talking nonsense :smiley:

Thanks in advance !

Probably you don’t need the lifetime as a parameter in IntoSubject but could just require the method to be generic over all lifetimes?

- trait IntoSubject<'a, T> {
+ trait IntoSubject<T> {
-     fn into_subject(&self, world: &'a World) -> &'a T;
+     fn into_subject<'a>(&self, world: &'a World) -> &'a T;
  }
use std::sync::{Arc, RwLock};

struct SoldierId(usize);
struct Soldier;
struct World(Vec<Soldier>);
struct State(Arc<RwLock<World>>);
struct Context {
    state: Arc<State>,
}

trait IntoSubject<T> {
    fn into_subject<'a>(&self, world: &'a World) -> &'a T;
}

impl IntoSubject<Soldier> for SoldierId {
    fn into_subject<'a>(&self, world: &'a World) -> &'a Soldier {
        &world.0[self.0]
    }
}

struct Processor<'a> {
    ctx: &'a Context,
}

impl<'a> Processor<'a> {
    fn process<I, T>(&self, i: I)
    where
        I: IntoSubject<T>,
    {
        let world = self.ctx.state.0.read().unwrap();
        let _subject = i.into_subject(&world);
    }
}

fn main() {
    let objects = vec![];
    let state = Arc::new(State(Arc::new(RwLock::new(World(objects)))));
    let ctx = Context { state };
    let processor = Processor { ctx: &ctx };
    processor.process(SoldierId(0));
}

feel free to provide more context if this clashes with other use-cases of the IntoSubject trait :wink:


For a similar effect but without having to change the trait, a higher-ranked trait bound can also solve your problem here, introducing a generic for<'b> … to generalize your I: IntoSubject<'a, T> constraint:

impl<'a> Processor<'a> {
    fn process<I, T>(&self, i: I)
    where
-       I: IntoSubject<'a, T>,
+       for<'b> I: IntoSubject<'b, T>,
    {
        let world = self.ctx.state.0.read().unwrap();
        let _subject = i.into_subject(&world);
    }
}
use std::sync::{Arc, RwLock};

struct SoldierId(usize);
struct Soldier;
struct World(Vec<Soldier>);
struct State(Arc<RwLock<World>>);
struct Context {
    state: Arc<State>,
}

trait IntoSubject<'a, T> {
    fn into_subject(&self, world: &'a World) -> &'a T;
}

impl<'a> IntoSubject<'a, Soldier> for SoldierId {
    fn into_subject(&self, world: &'a World) -> &'a Soldier {
        &world.0[self.0]
    }
}

struct Processor<'a> {
    ctx: &'a Context,
}

impl<'a> Processor<'a> {
    fn process<I, T>(&self, i: I)
    where
        for<'b> I: IntoSubject<'b, T>,
    {
        let world = self.ctx.state.0.read().unwrap();
        let _subject = i.into_subject(&world);
    }
}

fn main() {
    let objects = vec![];
    let state = Arc::new(State(Arc::new(RwLock::new(World(objects)))));
    let ctx = Context { state };
    let processor = Processor { ctx: &ctx };
    processor.process(SoldierId(0));
}
1 Like

This reply is mostly about how to think about lifetimes/borrow checking, rather than how to solve your problem (which has already been covered).

You seem to be reasoning about Rust lifetimes ('a things) as if they represented when values are destructed, but (despite the unfortunate naming) that is not the case. Rust lifetimes are generally about the duration of some borrow. They are also a type-level property, not a value-level property. And the World type doesn't "have a [Rust] lifetime".

Moroever, borrow checking is a pass-or-fail test, it cannot change the semantics of the code. So there's no way to use a lifetime annotation to change where a value may drop, say.

At a high level, the borrow checker

  • Figures out where in the control flow every place (such as variables) are borrowed, and how[1]
  • In a way that respects any lifetime annotations (including those implied by calling methods etc)
  • And checks every use of every place to see if the use conflicts with being borrowed

Lexical scopes and drop scopes are not themselves associated with a lifetime. Potentially running a destructor (e.g. due to going out of scope) sometimes keeps borrows alive, if there's a lifetime in the type of the value being destructed. But more typically, going out of scope is relevant in a borrow checker error situation due to being a use which conflicts with the value itself being borrowed.

I hope that perspective helps you reason about borrow checker errors.


In your code, it is only possible to call <I as IntoSubject<'a, T>>::into_subject, because that's the only bound you provided. So the borrow checker determines that the &world must borrow world for (at least) 'a.

The problem here is that all lifetimes from an outer scope like 'a are valid for some duration longer than the function call.[2] So world ends up being borrowed when it goes out of scope. In fact, you can never borrow a local for a such a lifetime lifetime, because all values are either moved or go out of scope before the end of the function.[3]

for<'b> I: IntoSubject<'b, T> is a solution because it requires the bound be met for all lifetimes -- including lifetimes shorter than the function body, like the duration you need for &world. That's how we deal with bounds involving the inferred lifetimes shorter than our function body, which cannot be individually named.

This idea can be summed up as "you cannot borrow locals for nameable lifetimes", with enough care around understanding what is meant by "nameable". Another way to look at it is that callers choose the outer lifetimes.[4]


  1. shared borrow or exclusive borrow ↩︎

  2. You don't know exactly how long, but it has to be longer than the function call. ↩︎

  3. You can think of returning a value as moving the value right before the function ends. ↩︎

  4. They also have no way to choose a lifetime shorter than the call, but since they have the choice, that's irrelevant to borrow checking the body. ↩︎

3 Likes

Hi !

Thanks a lot, there are no clashes with other usage of IntoSubject (yet ?). So I can use your solution.

I think I'm starting to understand. Introduce the lifetime (on the function or on the I: IntoSubject indicate this lifetime is different from my world.

I'm still studying your response quinedot (English is not my native lang). Thanks a lot of both of you !

2 Likes