Lifetime issues when moving code into a function

I think what I need to do is refer to a lifetime that is inside a function :grimacing:, but I can't figure it out.

Playground

Basically I have many structs with a lifetime with a couple of methods.

struct Example<'a> {
    ..
}

impl<'a> Example<'a> {
    fn new() -> Self { .. }
    fn example(&self, arg: &'a str) { .. }
}

I want to add an abstraction layer by using a trait for these methods

trait Behaviour<'a> {
    fn new() -> Self;
    fn behaviour(&self, arg: &'a str);
}

This all works fine until I started using it, I tried to write a generic function like this.

fn run<'a, B: Behaviour<'a>>(r: &mut Runner) {
    r.run(|ctx| {
        let arg = String::from("test");
        let ex = B::new();
        ctx.frobnicate(|| ex.behaviour(&arg));
    });
}

And I get the following error

|     fn run<'a, B: Behaviour<'a>>(r: &mut Runner) {
|            -- lifetime `'a` defined here

|             ctx.frobnicate(|| ex.behaviour(&arg));
|                            -- --------------^^^-
|                            |  |             |
|                            |  |             borrowed value does not live long enough
|                            |  argument requires that `arg` is borrowed for `'a`
|                            value captured here
|         });
|         - `arg` dropped here while still borrowed

Is there anything I can do? I am able to modify the trait and the calling code but I can't change the Runner or the Example struct.

did you try Rc and RefCell?

https://stackoverflow.com/a/41506280/883571

The problem here

fn run<'a, B: Behaviour<'a>>(r: &mut Runner) {
    r.run(|ctx| {
        let arg = String::from("test");
        let ex = B::new();
        ctx.frobnicate(|| ex.behaviour(&arg));
    });
}

is that 'a is a lifetime that’s longer that the entire duration of the function call, whereas the &arg can only live much shorter. The other test cases use an appropriately short lifetime, but that’s hard to specify, because that lifetime cannot appear in the function signature.

The only solution is to use higher ranked bounds to quantify over all lifetimes (or all sufficiently small lifetimes). However, the way things stand, the type B changes, too, with the lifetime 'a. There is not a single type B that could get a for<'a> B: Behavior<'a> bound. We can work around this problem by mocking up higher-ranked types (also known as “type constructors”) with a trait. The basic idea is something like

trait BehaviourTypeConstructorFor<'a> {
    type Behaviour: Behaviour<'a>;
}
trait BehaviourTypeConstructor: for<'a> BehaviourTypeConstructorFor<'a> {}
impl<T: for<'a> BehaviourTypeConstructorFor<'a>> BehaviourTypeConstructor for T {}
type ConstructBehaviour<'a, C> = <C as BehaviourTypeConstructorFor<'a>>::Behaviour;

which can be implemented by a helper type, representing the Example type constructor, e.g. as follows:

struct ExampleCon;
impl<'a> BehaviourTypeConstructorFor<'a> for ExampleCon {
    type Behaviour = Example<'a>;
}

Then the generic function can look like

fn run<B: BehaviourTypeConstructor>(r: &mut Runner) {
    r.run(|ctx| {
        let arg = String::from("test");
        let ex = ConstructBehaviour::<'_, B>::new();
        ctx.frobnicate(|| ex.behaviour(&arg));
    });
}

and everything works, i.e. you can call run::<ExampleCon>(&mut r);

That’s a bit tedious, but I don’t know a better way, and maybe it fits your use-case.

For future-proofing and/or to support slightly more involved / more generic cases, it might be a good idea to modify the traits slightly in order to account for the fact that other implementors of Behavior<'a> might not support all lifetime 'a, but only sufficiently small ones. The modification would look as follows:

trait BehaviourTypeConstructorFor<'a, _OutlivesBound = &'a Self> {
    type Behaviour: Behaviour<'a>;
}

this additional parameter is not intended to be explicitly used, but simply gives rise to an implicit Self: 'a bound, which allows for implementations such as

struct Example2<'a, T> {
    marker: std::marker::PhantomData<&'a T>,
}
impl<'a, T> Behaviour<'a> for Example2<'a, T> { … }

with an implicit T: 'a bound. A call like

struct Example2Con<T>(std::marker::PhantomData<fn() -> T>);
impl<'a, T> BehaviourTypeConstructorFor<'a> for Example2Con<T> {
    type Behaviour = Example2<'a, T>;
}
run::<Example2Con<T>>(r);

won’t compile for that type without that additional _OutlivesBound parameter; feel free to try it out:

Rust Playground

5 Likes

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.